环境部署
-
复制项目到本地
在目录6.828下面使用命令:git clone git://github.com/mit-pdos/xv6-public.git
将项目复制到本地。之后使用make
即可。 -
使用gdb调试
打开两个终端,都进入6.828/xv6-public目录下。第一个终端使用命令make qemu-gdb
,第二个使用命令gdb
[1]。在运行gdb的终端里面打断点0x10000c,输入c命令运行。并使用info reg
查看各寄存器值,使用x/24x $esp
查看栈里面的内容。
[1]:如果使用gdb调试的时候不成功,那么可以尝试在~/.gdbinit文件里面加入下面的内容:
add-auto-load-safe-path /home/(你的用户名)/6.828/xv6-public/.gdbinit
set auto-load safe-path /
栈内容解析
通过上面的调试,可以得到当指令执行到0x10000c处时,各寄存器的值为:
eax 0x0 0
ecx 0x0 0
edx 0x1f0 496
ebx 0x10074 65652
esp 0x7bdc 0x7bdc
ebp 0x7bf8 0x7bf8
esi 0x10074 65652
edi 0x0 0
eip 0x10000c 0x10000c
eflags 0x46 [ PF ZF ]
cs 0x8 8
ss 0x10 16
ds 0x10 16
es 0x10 16
fs 0x0 0
gs 0x0 0
栈里面的内容为:
0x7bdc: 0x00007d8d 0x00000000 0x00000000 0x00000000
0x7bec: 0x00000000 0x00000000 0x00000000 0x00000000
0x7bfc: 0x00007c4d 0x8ec031fa 0x8ec08ed8 0xa864e4d0
0x7c0c: 0xb0fa7502 0xe464e6d1 0x7502a864 0xe6dfb0fa
0x7c1c: 0x16010f60 0x200f7c78 0xc88366c0 0xc0220f01
0x7c2c: 0x087c31ea 0x10b86600 0x8ed88e00 0x66d08ec0
接下来分析栈里面的内容。
注意:栈是向下增长的。
首先需要了解的是gcc函数调用的规则,详见:gcc x86 calling conventions里面的“gcc x86 calling conventions”一节。
在bootblock.asm中:
call bootmain
7c48: e8 ee 00 00 00 call 7d3b <bootmain>
# If bootmain returns (it shouldn't), trigger a Bochs
# breakpoint if running under Bochs, then loop.
movw $0x8a00, %ax # 0x8a00 -> port 0x8a00
7c4d: 66 b8 00 8a mov $0x8a00,%ax
调用bootmain函数的语句地址为0x7c48,call bootmain下一条指令的地址为7c4d,所以当执行call bootmain时,程序会将7c4d压入栈中。
在bootmain函数里面:
00007d3b <bootmain>:
{
7d3b: 55 push %ebp
7d3c: 89 e5 mov %esp,%ebp
7d3e: 57 push %edi
7d3f: 56 push %esi
7d40: 53 push %ebx
可以看到首先压入了寄存器ebp,接着压入了edi,esi,ebx。
在7d87处,有:
entry();
7d87: ff 15 18 00 01 00 call *0x10018
紧接着转向执行entry()函数。
这一句语句执行完毕后,EIP变为了0x10000c(为什么不是0x10018我也不是很清楚)。所以函数执行的流程大概就是这样,现在来看看栈里面的内容:
0x7bdc: 0x00007d8d 0x00000000 0x00000000 0x00000000
0x7bec: 0x00000000 0x00000000 0x00000000 0x00000000
0x7bfc: 0x00007c4d 0x8ec031fa 0x8ec08ed8 0xa864e4d0
0x7c0c: 0xb0fa7502 0xe464e6d1 0x7502a864 0xe6dfb0fa
0x7c1c: 0x16010f60 0x200f7c78 0xc88366c0 0xc0220f01
0x7c2c: 0x087c31ea 0x10b86600 0x8ed88e00 0x66d08ec0
可以看到,在0x7bfc处有一个7c4d的数,可以确定这就是call bootmain后压入的返回地址,7dec那一行的最右边那一个为ebp的值,因为进入bootmain函数后第一个压入的是%ebp的值。
另外,在bootmain函数里面可以看到:
entry();
7d87: ff 15 18 00 01 00 call *0x10018
7d8d: eb d5 jmp 7d64 <bootmain+0x29>
entry()函数的返回地址为0x7d8d,由此可以得到栈里面7bdc那一行最左边的数即为entry()的返回地址。综上,大概可以得到一个下面的函数帧栈:
+-----------+ ---0x7c00
|0x7c4d | ---0x7bfc(bootmain返回地址)
|0x0000 | ---0x7bf8(bootmain的ebp值)
|0x0000 | ---0x7bf4
|0x0000 | ---0x7bf0
|0x0000 | ---0x7bec
|0x0000 | ---0x7be8
|0x0000 | ---0x7be4
|0x0000 | ---0x7be0
|0x7d8d | ---0x7bdc(entry的返回地址)(当前的%esp指向这里,使用info reg即可查看)
| | ---0x7bd8
| |
| |
| |
使用info reg
得到:
eax 0x0 0
ecx 0x0 0
edx 0x1f0 496
ebx 0x10074 65652
esp 0x7bdc 0x7bdc
ebp 0x7bf8 0x7bf8
esi 0x10074 65652
edi 0x0 0
eip 0x10000c 0x10000c
eflags 0x46 [ PF ZF ]
cs 0x8 8
ss 0x10 16
ds 0x10 16
es 0x10 16
fs 0x0 0
gs 0x0 0
可以清楚地看到,当前ebp(entry函数里面)指向boomain的ebp,bootmain的ebp指向0x0。
栈从0x7c00开始,是因为在bootasm.S中:
# Set up the stack pointer and call into C.
movl $start, %esp
call bootmain
在调用bootmain函数之前,已经设置好了栈($start的值为0x7c00)。