#2
marschant2011-04-06 08:34
|
编写操作系统,调试是必不可少的。调试操作系统,不可能像是用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 } \
//功能: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
}
//功能:设置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
}
//功能:保护模式下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。前两个实验的帖子,因为时间过了,无法修正过来……