首先以汇编级调试的方式启动bochs,引导Linux 0.11,在0.11下编译和运行test.c。它是个无限循环的程序,永远不会主动退出。然后在调试器中动过查看各项系统参数,从逻辑地址,LDT表,GDT表,线性地址到页表,计算出变量i的物理地址。最后通过直接修改物理内存的方式让test.c退出运行,test.c的代码如下:
#include <stdio.h>
int i = 0x12345678;
int main(void)
{
printf("The logical/virtual address of i is 0x%08x", &i);
fflush(stdout);
while (i)
;
return 0;
}
准备
编译好Linux 0.11后,首先通过运行./dbg-asm启动调试器,此时Bochs的窗口处于黑屏状态,而命令行窗口显示:
========================================================================
Bochs x86 Emulator 2.3.7
Build from CVS snapshot, on June 3, 2008
========================================================================
00000000000i[ ] reading configuration from ./bochs/bochsrc.bxrc
00000000000i[ ] installing x module as the Bochs GUI
00000000000i[ ] using log file ./bochsout.txt
Next at t=0
(0) [0xfffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b ; ea5be000f0
<bochs:1>_
"Next at t=0"表示下面的指令是Bochs启动后要执行的第一条软件指令。单步跟踪进去就能看见BIOS的代码。不过这不是本实验需要的。直接输入命令"c",continue程序的运行,Bochs一如既往地启动了Linux 0.11。
在Linux 0.11下输入test.c,编译test,运行之,打印如下信息:
The logical/virtual address of i is 0x00003004
只要test不变,0x00003004这个值在任何人的机器上都是一样的。即使在同一个机器上多次运行test,也是一样的。test是一个死循环,只会不停占用CPU,不会退出。
暂停
当test运行的时候,在命令行窗口按“ctrl+c”,Bochs会暂停运行,进入调试状态。绝大多数情况下都会停在test内,显示类似如下信息:
(0) [0x00fc8031] 000f:00000031 (unk. ctxt): cmp dword ptr ds:0x3004, 0x00000000 ; 833d0430000000
其中加粗的“000f”如果是0008,则说明中断在了内核里。那么就要c,然后在ctrl+c,直到变为"000f"为止。如果显示的下一条指令不是"cmp ...",就用"n"命令单步运行几步,直到停在"cmp ..."。
使用命令"u /7",显示从当前位置开始7条指令的反汇编代码,如下:
0000031: ( ): cmp dword ptr ds:0x3004, 0x00000000 ; 833d0430000000
10000038: ( ): jz .+0x00000002 ; 7402
1000003a: ( ): jmp .+0xfffffff5 ; ebf5
1000003c: ( ): xor eax, eax ; 31c0
1000003e: ( ): jmp .+0x00000000 ; eb00
10000040: ( ): leave ; c9
10000041: ( ): ret ; c3
这就是test.c中从while开始一直到return的汇编代码。变量i保存在ds:0x3004这个地址,并不停地和0进行比较,直到它为0,才会跳出循环。
现在,开始寻找ds:0x3004对应的物理地址。
段表
ds:0x3004是虚拟地址,ds表明这个地址属于ds段。首先要找到段表,然后通过ds的值在段表中找到ds段的具体信息,才能继续进行地址翻译。每个在IA-32上运行的应用程序都有一个段表,叫LDT,段的信息叫段描述符。
LDT在哪里呢?ldtr寄存器时线索的起点,通过它可以在GDT(全局描述符表)中找到LDT的物理地址。
用"sreg"命令:
cs:s=0x000f, dl=0x00000002, dh=0x10c0fa00, valid=1
ds:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=3
ss:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
es:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
fs:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
gs:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
ldtr:s=0x0068, dl=0xc2d00068, dh=0x000082f9, valid=1
tr:s=0x0060, dl=0x52e80068, dh=0x00008bfd, valid=1
gdtr:base=0x00005cc8, limit=0x7ff
idtr:base=0x000054c8, limit=0x7ff
可以看到看到ldtr的值是0x0068=0000000001101000(二进制),表示LDT表存放在GDT表的1101(二进制)=13(十进制)号位置(每位数据的意义参考后文描述的段选择子)。而GDT的位置已经由gdtr明确给出,在物理地址的0x00005cc8。用“xp /32w 0x00005cc8”查看该地址开始,32个字的内容,及GDT表的前16项。如下: