操作系统实验三:实模式与保护模式间自由跳转+使用Bochs调试的简单介绍
我的一点使用Bochs调试经验:编写操作系统,调试是必不可少的。调试操作系统,不可能像是用vs或其他IDE下使用鼠标点击设置断点调试那么简单与直观。但是一旦你有了几次使用Bochs调试的经历后,就会发现在命令行下调试也是一件轻松愉快的事。
下面介绍使用Bochs调试最基本的几个命令:
指令 举例 说明
b addr b 0x7c00 在指定内存物理地址设置断点
info b info b 查看已经设置了的断点信息
d num d 1 删除断点,num为使用 info b 显示的断点序号
c c 继续执行,直到遇到下一个断点
s s 单步执行
n n 单步执行(遇到函数跳过)
u start end u 0x7c00 0x7ca0 反汇编一段内存
sreg sreg 查看寄存器信息(查看GDT与LDT是否加载正确,段寄存器的值等)
r r 查看通用寄存器信息
b addr b 0x7c00 在指定内存物理地址设置断点
info b info b 查看已经设置了的断点信息
d num d 1 删除断点,num为使用 info b 显示的断点序号
c c 继续执行,直到遇到下一个断点
s s 单步执行
n n 单步执行(遇到函数跳过)
u start end u 0x7c00 0x7ca0 反汇编一段内存
sreg sreg 查看寄存器信息(查看GDT与LDT是否加载正确,段寄存器的值等)
r r 查看通用寄存器信息
调试示范:
首先请到Bochs的官方网站下载Bochs,目前最新版本为Bochs-2.4.2。地址:http://
下载安装好后,到安装文件夹Bochs-2.4.2中打开dlxlinux文件夹(需要完全安装),点击bochsrc.bxrc文件或run.bat文件,可以运行测试用的简易linux版。
在显示到 dlx login: 时,输入root,即可进入linux命令行模式。
安装测试没问题后,可以研究一下bochsrc.bxrc文件,因为我们调试需要配置这个文件。
我们的宿主程序run.c其实已经自动生成了一个bochs的配置文件,但是后缀名不同,为.src,但其实是差不多的。
也可以直接在操作系统实验文件夹下建立一个文本文件命名为run.bxrc(注意后缀名)并保存如下内容:
megs: 32
romimage: file=./BIOS-bochs-latest
vgaromimage: file=./VGABIOS-lgpl-latest
floppya: 1_44=pm.img, status=inserted
boot: a
log: bochsout.txt
mouse: enabled=0
keyboard_mapping: enabled=1, map=x11-pc-us.map
记得将BIOS-bochs-latest、VGABIOS-lgpl-latest、x11-pc-us.map三个文件复制到实验文件夹,也可以在配置文件run.bxrc中使用绝对路径指向这三个文件的原来位置。romimage: file=./BIOS-bochs-latest
vgaromimage: file=./VGABIOS-lgpl-latest
floppya: 1_44=pm.img, status=inserted
boot: a
log: bochsout.txt
mouse: enabled=0
keyboard_mapping: enabled=1, map=x11-pc-us.map
下面运行安装文件夹Bochs-2.4.2中bochsdbg.exe。
一开始,就出现了Bochs的控制台,并且弹出了叫Bochs start menu 的窗口,点击Load按钮,然后选则配置文件run.bxrc,再点击Bochs start menu 的窗口中的Start按钮。
就可以看到命令行提示符<bochs:1>以及虚拟机窗口了。
在命令行提示符输入设置断点,然后查看断点,最后执行:
<bochs:1> b 0x7c00
<bochs:2> info b
Num Type Disp Enb Address
1 pbreakpoint keep y 0x00007c00
<bochs:3> c
(0) Breakpoint 1, 0x0000000000007c00 in ?? ()
Next at t=14041584
(0) [0x00007c00] 0000:7c00 (unk. ctxt): mov ax, cs ; 8cc8
<bochs:4>
读者可以自己试试其他指令。<bochs:2> info b
Num Type Disp Enb Address
1 pbreakpoint keep y 0x00007c00
<bochs:3> c
(0) Breakpoint 1, 0x0000000000007c00 in ?? ()
Next at t=14041584
(0) [0x00007c00] 0000:7c00 (unk. ctxt): mov ax, cs ; 8cc8
<bochs:4>
另外提示一下,在实验并调试发现错误后,可以不关闭bochsdbg.exe的命令行窗口与虚拟机窗口,直接用宿主程序run.exe再次编译修改后的实验代码。然后在调试的虚拟机窗口点击Restart按钮,并在调试命令行输入继续执行命令:c,虚拟机就会重新加载新的软盘镜像。如此,之前设置的断点依然保留着,不必再次重新运行bochsdbg.exe与设置断点了。
下面是此时实验的内容了
此次实验,是在 操作系统实验二:从实模式跳转到保护模式 基础上的进一步实验。实验实现的内容很简单,一开始进入实模式,然后跳转到保护模式,然后再跳转回实模式,然后再跳转到保护模式,如此不断循环。
从实模式调转到保护模式步骤:
1.准备好GDT
2.使用lgdt指令加载gdtr
3.打开地址线A20
4.设置cr0的PE位为1
5.使用cli指令关闭中断
6.根据GDT选择子跳转到32位代码段,此时进入保护模式
从保护模式转到实模式调步骤:
1.跳转到16位代码段
2.加载适合的GDT选择子到有关段寄存器
3.设置cr0的PE位为0
4.跳转到16位代码段,此时进入实模式
5.关闭地址线A20
6.使用sti指令打开中断
实验流程简要说明:
(pm16.c部分)
1.清屏
2.跳到第9步
3.加载适合的描述符选择子到有关段寄存器
4.设置cr0的PE位为0
5.一个跳转,真正回到实模式
6.关闭 A20 地址线
7.开中断
8.调用延时函数
9.显示字符串This is real model!
10.设置下一次显示字符串的位置
11.加载 GDTR
12.关中断
13.打开地址线A20
14.置cr0的PE位为1
15.跳转到32位代码段,进入保护模式
(pm32.c部分)
16.调用延时函数
17.显示字符串I am now in protect model^_^
18.保存最后的光标位置
19.跳转到(pm16.c)第3步
实验代码如下:
code:run.c(与操作系统实验二中的run.c一样,所以在这里就不再重复了)
code:pm.h
程序代码:
//文件: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-1-24 //定义GDT属性 #define DA_32 0x4000 //32位段 #define DA_DRW 0x92 //存在的可读写数据段属性值 #define DA_CR 0x9A //存在的可执行可读代码段属性值 //GDT 选择子,根据pm16.c中的GDT界限设置偏移量值 #define SelectorNormal 8*1 //返回实模式需要用到的选择子 #define SelectorSode32 8*2 //指向32位段处 #define SelectorVideo 8*3 //指向显存首地址 #define SelectorSode16 8*4 //指向16位代码段 #define SelectorData32 8*5 //指向32位段处 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) AVL(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 } \
code:pm16.c
程序代码:
//文件:pm16.c //功能:设置GDT,在GDT中初始化32位代码段,然后切换到保护模式,跳转到32位代码段 // 当从32位代码段保护模式跳转回来后,切换到实模式,显示字符串,再切换到保护模式 //说明:16位部分引导程序放在镜像引导扇区的前半部分,0~255字节中,程序大小不能超过这个限制 //运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序 //提示:请先用yc09编译run.c文件,生成run.exe程序 // 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果 // 若在_BackToRealModel标签前添加或删除代码,会导致保护模式无法正确跳转回来, // 此时需要反汇编pm16.c的代码,查找到_BackToRealModel所在地址,修改pm32.c中的跳转值 //作者:miao //时间:2010-1-24 #define YCBIT 16 //告诉编译器,以16位格式编译程序 #define YCORG 0x7c00 //告诉编译器,在7c00处加载程序 #include "pm.h" //GDT界限 DESCRIPTOR label_gdt[] = { // 段基址 段界限 属性 Descriptor(0, 0, 0), Descriptor(0, 0xfffff, DA_DRW), //Normal 描述符,返回实模式前要加载的 Descriptor(0x7d00, 0xfffff, DA_CR | DA_32), //32位代码段(pm32.c),可执行可读 Descriptor(0xb8000, 0xffff, DA_DRW), //显存地址段,可读可写 Descriptor(0x7c00, 0xfffff, DA_CR), //16位代码段(pm16.c),可执行可读 Descriptor(0x7d00, 0xfffff, DA_DRW | DA_32), //令32位代码段(pm32.c)的变量可以读写 }; #pragma pack(1) struct GDT_PTR { unsigned short size; void *addr; }GDT_PTR GdtPtr = { sizeof(label_gdt), (char*)label_gdt}; //段界限,基地址 #pragma pack() #define MsgLngth 24 //串长度 char Msg[MsgLngth] = "This is real model! "; char row = 0; //行数 char line = 0; //列数 asm void Delay(); //延时函数 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 //最后一列 mov dh, 1fh //最后一行(右下角) int 10h //显示中断 jmp _Start16 //跳过下面从保护模式切换到实模式的代码 //保护模式代码执行完后直接跳转到此处 _BackToRealModel: mov ax, SelectorNormal //加载适合的描述符选择子到有关段寄存器 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax //准备切换到实模式,清除cr0的PE位 mov eax, cr0 and al, 11111110b mov cr0, eax jmp 0:_RealEntry //这一跳,段地址被设置成正确的值,真正回到实模式 _RealEntry: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, YCORG //关闭 A20 地址线 in al, 92h and al, 11111101b out 92h, al sti //开中断 call Delay //调用延时函数 _Start16: mov ax, &Msg mov bp, ax //es:bp=串地址 mov ah, 13h //AH:13显示字符串 mov al, 1h //AH = 13, AL = 01h mov bx, 0014h //页号为0(BH = 0) 蓝底红字(BL = 14h) mov cx, MsgLngth //CX = 串长度 mov dl, line //起始列 mov dh, row //起始行 int 10h //显示中断 add line, MsgLngth //下一次显示字符串是当前列的后MsgLngth - 3列 cmp line, 0x50 - MsgLngth //判断列数是否过大 jng _LineNotEnd mov line, 0 //列数过大,列数归零 add row, 1 //行数加一 cmp row, 24 jge _DeadLoop //行数超过24行,跳转到一个死循环 _LineNotEnd: 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 SelectorSode32:0x0 //死循环 _DeadLoop: jmp _DeadLoop } //延时函数 asm void Delay() { mov ax, 0x0 _Loop1: inc ax mov cx, 0xFFFF _Loop2: loop _Loop2 cmp ax, 0xFF jne _Loop1 ret }
code:pm32.c
程序代码:
//文件:pm16.c //功能:保护模式下32位代码段,功能为显示一个字符串,然后跳转到16位代码段(pm16)切换到实模式 //说明:32位部分引导程序放在镜像引导扇区的后半部分,256~509字节中,程序大小不能超过这个限制 //运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序 //提示:请先用yc09编译run.c文件,生成run.exe程序 // 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果,点击回车再次编译运行 //作者:miao //时间:2010-1-24 #define YCBIT 32 //告诉编译器,以32位格式编译程序 #define YCORG 0x0 //此值会对在编译时对变量函数等产生地址基址偏移量,简单起便,设置为0 #include "pm.h" char Msg1[] = "I am now in protect model^_^"; int printPlace = ((80 * 1 + 0) * 2); //目的数据偏移。屏幕第1行, 第0列。 //延时函数 asm void Delay() { mov ecx, 0xFFFFFF _Loop2: loop _Loop2 ret } //32 位代码段. 由实模式跳入(可执行可读) asm void main() { mov eax, SelectorVideo mov gs, ax //视频段选择子(目的) mov eax, SelectorData32 //令32位代码段的变量(printPlace)可以读写 mov ds, ax call Delay //调用延时函数 //下面显示一个字符串(显示已经到达保护模式信息) mov ah, 14h //蓝底红字(AL = 14h) mov esi, &Msg1 //源数据偏移 mov edi, printPlace //根据保存的光标位置,继续显示字符 //循环逐个将字符串输出 _DispStr: mov al, cs:[esi]//因为可读,才能用cs指向当前段的Msg1字符串 inc esi cmp al, '\0' //判断是否字符串结束 jz _stop mov gs:[edi], ax add edi, 2 jmp _DispStr _stop: //显示完毕 add edi, 2 mov printPlace, edi //保存最后的光标位置 //通过反汇编,知道pm16.c的_BackToRealModel在内存的0x7c18处 jmp dword SelectorSode16:0x7c18 - 0x7c00 }
ps:之前一直在说yc90编译器,结果今天才发现说错了,应该是yc09。前两个实验的帖子,因为时间过了,无法修正过来……