操作系统实验五:保护模式之初步认知门任务(求助:如何使用其他指令替代或模拟retf指令)
2010-02-03 21:15
387 查看
向汇编高手求助:
此次实验中需要用到retf指令在调用门任务后返回。可是yc09编译器不支持retf指令。而我使用ret指令无法正确跳转返回。
因为对汇编不熟,胡乱试了许多方法后依然无法解决,无奈之下,只好在编译好代码后直接修改机器指令码。希望有路过的高手、大牛等告说我在不换编译器的前提下如何解决这问题。
使用retf指令报错信息:
pm32.c(123) : error C2065: 'retf' : undeclared identifier --209
百度了一下,有人解释说ret与retf的区别如下。
ret -> pop ip
retf -> pop ip
pop cs
我试着用 pop ip 与 pop cs 代替retf,编译器却对这两个指令报错。
报错信息:
pm32.c(123) : error C2065: 'ip' : undeclared identifier --205
pm32.c(124) : error C4606: impossible combination of opcode and oprands
此次实验内容:在保护模式下,根据在GDT注册好的门任务,无特权转换的跳转到门任务下,打印一条信息后返回。
使用门任务步骤:
1.编写一个为测试门任务而是用的测试函数,功能为显示一个字符串。(编写好后最好测试一下知否能直接调用)
2.在GDT上注册好门任务:需要设置 选择子、偏移量、参数个数、属性 这4个参数。
(因为门任务与GDT的数据结构不相同,所以需要在pm.h里新定义一个宏函数。)
3.定义一个选择子指向GDT中注册的门任务。
4.在保护模式下,使用call通过门任务选择子调用门任务。
实验代码如下:
code:run.c(新增一个修改ret为retf机器指令的函数:void ret_To_retf(byte *imgBuffer,int size),并在CompileFile函数调用)
code:pm.h(新增了一个宏(Gate),在pm32.c中GDT用到)
code:pm16.c
code:pm32.c
想要实验以上代码的朋友请注意:
1.到杨晓兵大大的博客上下载安装yc09编译器,安装只需一分钟左右。
2.将此次试验中的run.c、pm.h、pm16.c、pm32.c代码拷贝到某个实验用的文件夹内。
3.在安装yc09的目录YC09/example文件夹内找到bochs.exe、BIOS-bochs-latest、VGABIOS-elpin-2.40、x11-pc-us.map四个文件拷贝到试验用的文件夹内。
4.使用yc09编译运行run.c
此次实验中需要用到retf指令在调用门任务后返回。可是yc09编译器不支持retf指令。而我使用ret指令无法正确跳转返回。
因为对汇编不熟,胡乱试了许多方法后依然无法解决,无奈之下,只好在编译好代码后直接修改机器指令码。希望有路过的高手、大牛等告说我在不换编译器的前提下如何解决这问题。
使用retf指令报错信息:
pm32.c(123) : error C2065: 'retf' : undeclared identifier --209
百度了一下,有人解释说ret与retf的区别如下。
ret -> pop ip
retf -> pop ip
pop cs
我试着用 pop ip 与 pop cs 代替retf,编译器却对这两个指令报错。
报错信息:
pm32.c(123) : error C2065: 'ip' : undeclared identifier --205
pm32.c(124) : error C4606: impossible combination of opcode and oprands
此次实验内容:在保护模式下,根据在GDT注册好的门任务,无特权转换的跳转到门任务下,打印一条信息后返回。
使用门任务步骤:
1.编写一个为测试门任务而是用的测试函数,功能为显示一个字符串。(编写好后最好测试一下知否能直接调用)
2.在GDT上注册好门任务:需要设置 选择子、偏移量、参数个数、属性 这4个参数。
(因为门任务与GDT的数据结构不相同,所以需要在pm.h里新定义一个宏函数。)
3.定义一个选择子指向GDT中注册的门任务。
4.在保护模式下,使用call通过门任务选择子调用门任务。
实验代码如下:
code:run.c(新增一个修改ret为retf机器指令的函数:void ret_To_retf(byte *imgBuffer,int size),并在CompileFile函数调用)
//文件:run.c //功能:编译操作系统的实验代码并创建img,生成bochs配置文件,运行bochs。 //说明:实验代码由16位部分引导程序与32位部分引导程序组成。 // 16位部分引导程序放在引导扇区的前半部分,0~79字节 // 32位部分引导程序放在引导扇区的后半部分,80~509字节 // 510、511字节放引导程序结束标记:0x55、0xaa //运行:请使用yc09编译器编译运行。 //作者:miao //时间:2010-2-3 #define FDISK_SIZE 1474560 //镜像大小:1.4MB //虚拟机设置 char *pmSrc = "megs: 32 /n" "romimage: file=BIOS-bochs-latest, address=0xf0000 /n" "vgaromimage: VGABIOS-elpin-2.40 /n" "floppya: 1_44=pm.img, status=inserted /n" "boot: a /n" "log: pm.out /n" "mouse: enabled=0 /n" "keyboard_mapping: enabled=1, map=x11-pc-us.map /n"; //因为yc09编译器不支持长返回指令 retf,所以使用这个函数作为临时解决方法。 //在汇编代码中需要用到 retf指令 时,使用 ret nop nop 这个三个连续指令代替, //在编译代码后,调用此函数将ret指令的机器指令改为retf的机器指令。 //imgBuffer:编译后的二进制文件 size:文件的字节大小 //汇编与机器指令对照:ret == 0xc3 retf == 0xcb nop == 0x90 void ret_To_retf(byte *imgBuffer,int size) { if(size<4) return; size-=2; while(--size) if(imgBuffer[size] == 0xc3) if(imgBuffer[size+1] == 0x90 && imgBuffer[size+2] == 0x90) { imgBuffer[size] = 0xcb; printf("将一个ret改为了retf./n"); } } //编译指定代码文件并放入镜像指定位置 //filename:要编译的文件名 imgBuffer:保存到的镜像缓冲区 //startIndex:指定起始位置 limitSize:编译后程序限定大小 //isneed:若为true,就调用ret_To_retf函数检测代码并将ret替换为retf的机器码 int CompileFile(char *fileName, byte *imgBuffer, int startIndex, int limitSize, bool isneed) { char *tempBuffer; //保存部分引导程序的临时缓冲区 //编译此部分引导程序,结果放到tempBuffer中 int length = YC_CompileCpp(&tempBuffer, fileName, 0, 0); if(length <= 0 || length >= limitSize) { printf("文件: %s 中存在一些错误或文件过大(超过%d字节):%d字节/n", fileName,limitSize,length); return 1; } printf("文件: %s 编译成功,大小为:%d字节。/n", fileName, length); if(isneed) ret_To_retf(imgBuffer + startIndex,length); //将1此部分引导程序放到镜像引导扇区缓冲区指定起始位置 memcpy(imgBuffer + startIndex, tempBuffer, length); free(tempBuffer); return 0; } int main(int argc, char **argv) { char * filePath = argv[0]; //当前文件夹路径 char fileName[MAX_PATH]; //用于缓存各个文件名 //将可执行文件的完整路径去掉文件名,保留文件夹路径 for( int i = strlen(filePath);filePath[i] != '//';i--) filePath[i] = '/0'; byte *imgBuffer = new byte[FDISK_SIZE];//镜像缓冲区 _start: //编译16位部分引导程序并放在引导扇区的前半部分,0~79字节 if(CompileFile("pm16.c", imgBuffer, 0, 80,false)) goto _restart; //编译32位部分引导程序并放在引导扇区的后半部分,80~509字节 if(CompileFile("pm32.c", imgBuffer, 80, 512-80-2,true)) goto _restart; //0000H-01FFH 为FAT引导扇区[第0扇区] 以55 AA标志结束 长度为200H(512)字节 imgBuffer[510] = 0x55; imgBuffer[511] = 0xaa;//标记软盘引导结尾 //创建操作系统镜像pm.img if(YC_writefile("pm.img", imgBuffer, FDISK_SIZE) != FDISK_SIZE) { printf("写: %s 文件过程中出现错误。/r/n", fileName); goto _restart; } printf("/n%s 创建成功。/n", fileName); //生成操作系统虚拟机配置文件pm.src YC_writefile("pm.src", pmSrc, strlen(pmSrc)); //运行虚拟机 YC_WinExec(strcat(strcpy(fileName, filePath), "bochs.exe"), "-q -f pm.src"); _restart: printf("/n点击回车重新编译运行!/n/n/n"); while(getchar() != '/n'); goto _start; return 0; }
code:pm.h(新增了一个宏(Gate),在pm32.c中GDT用到)
//文件:pm.h //功能:pm16.c与pm32.c的公共头文件 //运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序 //提示:请先用yc09编译run.c文件,生成run.exe程序 // 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果,点击回车再次编译运行 //作者:miao //时间:2010-2-3 //定义GDT属性 #define DA_32 0x4000 //32位段 #define DA_DRW 0x92 //存在的可读写数据段属性值 #define DA_DRWA 0x93 //存在的已访问可读写数据段类型值 #define DA_CR 0x9A //存在的可执行可读代码段属性值 #define DA_C 0x98 //存在的可执行可读代码段属性值 //定义LDT属性 #define DA_LDT 0x82 //局部描述符表类型值 #define SA_TIL 0x4 //将TI位置1,表示是LDT选择子 //定义门属性 #define DA_386CGate 0x8c //386调用门类型 #define DA_DPL0 0x00 //DPL = 0 typedef unsigned int t_32; //4字节 typedef unsigned short t_16; //2字节 typedef unsigned char t_8; //1字节 typedef int t_bool;//4字节 typedef unsigned int t_port;//4字节 //存储段描述符/系统段描述符 struct DESCRIPTOR //共 8 个字节 { t_16 limit_low; //Limit 2字节 t_16 base_low; //Base 2字节 t_8 base_mid; //Base 1字节 t_8 attr1; //P(1) DPL(2) DT(1) TYPE(4) 1字节 t_8 limit_high_attr2; //G(1) D(1) 0(1) ***L(1) LimitHigh(4) 1字节 t_8 base_high; //Base 1字节 }; #define Descriptor(bas,len,attr) { / (len) & 0xffff, / (bas) & 0xffff, / ((bas)>>16)&0xff, / (attr) & 0xff, / (((attr)>>8) &0xf0) + (((len)>>16) & 0x0f), / ((bas) >> 24) & 0xff } / #define Gate(slector,offset,dCount,attr) { / (offset) & 0xffff, / slector, / (dCount)&0x1f , / attr, / ((offset)>>16) &0xff, / ((offset) >> 24) & 0xff } /
code:pm16.c
//文件:pm16.c //功能:切换到保护模式,跳转到32位代码段 //说明:我试图仅在引导扇区编写保护模式的相关实验,因此将这个程序精简了很多。 // 它只负责跳转到保护模式,其他的工作都在pm32.c下完成。 // pm16.c只占引导扇区的前半部分0~79字节。 // pm32.c部分会加载到内存0x7c50处。 //运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序 //提示:请先用yc09编译run.c文件,生成run.exe程序 // 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果,点击回车再次编译运行 //作者:miao //时间:2010-1-30 #define YCBIT 16 //告诉编译器,以16位格式编译程序 #define YCORG 0x7c00 //告诉编译器,在7c00处加载程序 #include "pm.h" //GDT界限,只负责跳转到保护模式,到时会加载新的GDT DESCRIPTOR label_gdt[] = { // 段基址 段界限 属性 Descriptor(0, 0, 0), Descriptor(0x7c50, 0xfffff, DA_CR | DA_32), //32位代码段(pm32.c),可执行可读 }; //GDT 选择子,根据GDT界限设置偏移量值 #define SelectorCode32 8*1 //指向32位段处 #pragma pack(1) struct GDT_PTR { unsigned short size; void *addr; }; #pragma pack() GDT_PTR GdtPtr = {sizeof(label_gdt), (char*)label_gdt}; //段界限,基地址 asm void main() { mov ax, cs mov ds, ax mov es, ax //清屏 mov ah, 06h //屏幕初始化或上卷 mov aL, 00h //AH = 6, AL = 0h mov bx, 1110h //蓝色底色 mov cx, 0 //左上角: (0, 0) mov dl, 4fh //第0列 mov dh, 1fh //第0行 int 10h //显示中断 lgdt GdtPtr //加载 GDTR cli //关中断 //打开地址线A20 in al, 92h or al, 00000010b out 92h, al //准备切换到保护模式,置cr0的PE位为1 mov eax, cr0 or eax, 1 mov cr0, eax //真正进入保护模式 jmp dword SelectorCode32:0x0 }
code:pm32.c
//文件:pm32.c //功能:保护模式下32位代码段,功能为加载新的GDT,调用门任务,初始化LDT,跳入LDT局部任务 //说明:32位部分引导程序放在镜像引导扇区的后半部分,80~509字节中,程序大小不能超过这个限制 //运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序 //提示:因为yc09无法识别retf指令,所以用ref指令代替,并且在后面加上两个nop指令。 // 运行run.exe时会自动将ret的机器指令码改为retf的机器指令码。 // 请先用yc09编译run.c文件,生成run.exe程序 // 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果 ,点击回车再次编译运行 //作者:miao //时间:2010-1-33 #define YCBIT 32 //告诉编译器,以32位格式编译程序 #define YCORG 0x0 //此值会对在编译时对变量函数等产生地址基址偏移量,简单起便,设置为0 #include "pm.h" #define ProtecAddr 0x7c50 //进入保护模式后的程序基址 asm void LDTCode();//局部代码段, 由32 位代码段跳入 asm void CodeDest();//门任务 asm void DispStr();//显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置 //LDT界限 DESCRIPTOR label_ldt[] = { // 段基址 段界限 属性 Descriptor(ProtecAddr, 0xfffff, DA_CR | DA_32), //32位代码段(pm32.c),可执行可读 }; //LDT 选择子,根据pm32.c中的LDT界限设置偏移量值 #define SelectorLDTCodeA 8*0+SA_TIL //指向32位段局部任务处 //GDT 选择子,根据pm32.c中的GDT界限设置偏移量值 #define SelectorCode32 8*1 //指向32位段处代码段,可执行可读 #define SelectorVideo 8*2 //指向显存首地址 #define SelectorData32 8*3 //指向32位段处,这样,在程序中的变量就可以读写了 #define SelectorLDT 8*4 //指向LDT,通过这个跳转到局部任务 //门选择子 #define SelectorCallGateTest 8*5 // //GDT界限,注意,这个与pm16.c中的GDT不同,从pm16.c跳转过来后会立即载入这个新的GDT DESCRIPTOR label_gdt[] = { // 段基址 段界限 属性 Descriptor(0, 0, 0), Descriptor(ProtecAddr, 0xfffff, DA_CR | DA_32), //32位代码段(pm32.c),可执行可读 Descriptor(0xb8000, 0xffff, DA_DRW), //显存地址段,可读可写 Descriptor(ProtecAddr, 0xfffff, DA_DRW | DA_32), //令32位代码段(pm32.c)的变量可以读写 Descriptor(0, 0xfffff, DA_LDT),//局部描述符,段基址和32位代码段相同,调用时需要加上偏移量 // 选择子 偏移量 参数个数 属性 Gate(SelectorCode32, (t_32)&CodeDest, 0, DA_386CGate | DA_DPL0), //使用SelectorCode32选择子,所以内存地址:基址(ProtecAddr) + 函数地址偏移量 }; #pragma pack(1) struct GDT_PTR { t_16 size; void *addr; }GdtPtr = {sizeof(label_gdt), (char*)&label_gdt + ProtecAddr}; //段界限,基地址 #pragma pack() char Msg1[] = "This is protect model."; char Msg2[] = "This is gate task."; char Msg3[] = "This is local model."; //32 位代码段. 由实模式跳入 asm void main() { lgdt cs:GdtPtr //加载新的GDTR mov eax, SelectorVideo mov gs, ax //视频段选择子(目的) mov eax, SelectorData32 //令32位代码段的变量(printPlace)可以读写 mov ds, ax //下面显示一个字符串(显示已经到达保护模式信息) mov esi, &Msg1 //源数据偏移 mov edi, ((80 * 0 + 0) * 2) //目的数据偏移。屏幕第0行, 第0列。 call DispStr //call SelectorCode32:&CodeDest //先测试一下,直接跳转到测试函数 CodeDest() call SelectorCallGateTest:0 //测试通过,直接通过门跳转到测试函数 CodeDest() //在GDT的上设置好LDT的基地址,然后加载局部描述符(LDT) xor eax, eax mov eax, &label_ldt + ProtecAddr mov word label_gdt+SelectorLDT+2, ax shr eax, 16 mov byte label_gdt+SelectorLDT+4, al mov byte label_gdt+SelectorLDT+7, ah mov ax, SelectorLDT lldt ax jmp SelectorLDTCodeA:&LDTCode //跳转到局部任务 } //门测试函数 asm void CodeDest() { //下面显示一个字符串(显示已经到达门任务信息) mov esi, &Msg2 //源数据偏移 mov edi, ((80 * 1 + 0) * 2) //目的数据偏移。屏幕第2行, 第0列。 call DispStr ret //因为yc09不支持 retf指令, nop //所以用这3个指令代替, nop //在编译后检测并替换为 retf 的机器指令。 } //局部代码段, 由32 位代码段跳入 asm void LDTCode() { //下面显示一个字符串(显示已经到达保护模式信息) mov esi, &Msg3 //源数据偏移 mov edi, ((80 * 2 + 0) * 2) //目的数据偏移。屏幕第1行, 第0列。 call DispStr _dead: jmp _dead } //显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置 asm void DispStr() { mov ah, 14h //蓝底红字(ah = 14h) //循环逐个将字符串输出 _DispStr: mov al, ds:[esi]//因为可读,才能用cs指向当前段的Msg1字符串 inc esi cmp al, '/0' //判断是否字符串结束 jz _stop mov gs:[edi], ax add edi, 2 jmp _DispStr _stop: //显示完毕 ret }
想要实验以上代码的朋友请注意:
1.到杨晓兵大大的博客上下载安装yc09编译器,安装只需一分钟左右。
2.将此次试验中的run.c、pm.h、pm16.c、pm32.c代码拷贝到某个实验用的文件夹内。
3.在安装yc09的目录YC09/example文件夹内找到bochs.exe、BIOS-bochs-latest、VGABIOS-elpin-2.40、x11-pc-us.map四个文件拷贝到试验用的文件夹内。
4.使用yc09编译运行run.c
相关文章推荐
- OpenCV五学习: 如何使用命令来启动或关闭OpenCV的CPU指令系统CV_SSE2,CV_SSSE4和其他优化
- 如何使用Java Singleton模式
- 如何使用设计模式来构造系统--(8)
- GridView控件使用(在GridView中放入其他控件的情况如何取得当前行)
- 如何使用apache的 work模式还是 prefork 模式
- React反模式 —— 如何不使用JSX地动态显示组件
- (转转转)AndroidStudio下如何输出jar包给其他开发环境使用
- 结合实际问题浅谈如何使用蒙特卡罗算法模拟投资分析
- 关于android系统的分辨率:使用其他设备模拟该分辨率
- 如何在C#中使用 Win32和其他库
- 小tips:JS严格模式(use strict)下不能使用arguments.callee的替代方案
- 【转】如何使用windows的计划任务?计划任务?
- 详解μC/OS-II如何检测任务堆栈实际使用情况——即如何设置ucosii任务堆栈大小
- C#如何HttpWebRequest模拟登陆,获取服务端返回Cookie以便登录请求后使用
- Excel如何在被保护的情况下使用自动筛选
- 如何使用设计模式来构造系统--(3)
- 如何正确学习和使用设计模式
- 只能在保护模式下执行的指令
- 如何在C#中使用 Win32和其他库
- 使用htmlunit工具来实现对新浪的模拟登录获取cookie操作(也可适用其他网站,最好是无验证码的)