注册 登录
编程论坛 操作系统内核开发

操作系统实验三:实模式与保护模式间自由跳转+使用Bochs调试的简单介绍

miaowangjian 发布于 2010-02-04 05:33, 3093 次点击
我的一点使用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                     查看通用寄存器信息


调试示范:
    首先请到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中使用绝对路径指向这三个文件的原来位置。

    下面运行安装文件夹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>
    读者可以自己试试其他指令。

    另外提示一下,在实验并调试发现错误后,可以不关闭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。前两个实验的帖子,因为时间过了,无法修正过来……
1 回复
#2
marschant2011-04-06 08:34
顶啊,写得很好 ,学习了
1