Homework: boot xv6(MIT6.828 LEC2作业)

地址:Homework: boot xv6

环境部署

  1. 复制项目到本地
    在目录6.828下面使用命令:git clone git://github.com/mit-pdos/xv6-public.git将项目复制到本地。之后使用make即可。

  2. 使用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)。

发布了47 篇原创文章 · 获赞 2 · 访问量 5756

猜你喜欢

转载自blog.csdn.net/wysiwygo/article/details/104107968