MIT6.828-Lab1-Part3-Exercise10-通过嵌套调用程序熟悉栈生长过程

 

通过test_backtrace的嵌套调用,熟悉栈生长过程。

通过嵌套调用程序熟悉栈生长过程

为了能够更好的了解在x86上的C程序调用过程的细节,首先找到在 obj/kern/kern.asm中test_backtrace 子程序的地址, 设置断点,并且探讨一下在内核启动后,这个程序被调用时发生了什么。对于这个循环嵌套调用的程序test_backtrace,它一共压入了多少信息到堆栈之中。并且它们都代表什么含义?

kernel.asm

结合 test_backtrace 的汇编代码来理解 %esp%ebp 的变化过程

// Test the stack backtrace function (lab 1 only)
void
test_backtrace(int x)
{
## 在执行第一条语句之前,需要为了进入此子程序做准备
#  ebp 压栈,由于栈是向下生长且 esp 指向栈底,所以这条指令执行完后 new esp = old esp - 4
# -4 是考虑32-bit操作系统,每一条指令长度为32bits,需要占用4个地址空间
f010|0040:				55						push		%ebp
# 将此时的 new esp 值赋予 ebp,因此 ebp = new esp
f010|0041:				89 e5					mov			%esp, %ebp
# 压栈,esp再减4
f010|0043:				56						push		%esi
# 压栈,esp再减4
f010|0044:				53						push		%ebx
.........
		cprintf("entering test_backtrace %d\n", x);
f010|0053:				83 ec 08			sub			$0x8, %esp
}

接下来我们通过gdb的 info registers 命令查看关键指令执行完后的 %esp%ebp

入栈

kernel.asm 中可以看到 test_backtrace 的进入地址为 0xf010|0040 ,因此在此处加断点,具体的调试结果为:

命令 %esp %ebp
–断点0xf0100040 0xf010ffdc 0xf010fff8
push %ebp 0xf010ffd8 = (0xf010ffdc-4);
此时在这个地址内存放的是ebp的值0xf010fff8
0xf010fff8
mov %esp,%ebp 0xf010ffd8 0xf010ffd8 = %esp
push %esi 0xf010ffd4 = (0xf010ffd8-4) 0xf010ffd8
push %ebx 0xf010ffd0 = (0xf010ffd4-4) 0xf010ffd8
这之中的指令会打印”entering …5”  
–断点0xf0100040 0xf010ffbc 0xf010ffd8
push %ebp 0xf010ffb8 = (0xf010ffbc-4) 0xf010ffd8
mov %esp,%ebp 0xf010ffb8 0xf010ffb8 = %esp
push %esi 0xf010ffb4 = (0xf010ffb8-4) 0xf010ffb8
这之中的指令会打印”entering …4”  
–断点0xf0100040 0xf010ff9c 0xf010ffb8
push %ebp 0xf010ff98 = (0xf010ff9c-4) 0xf010ffb8
mov %esp,%ebp 0xf010ff98 0xf010ff98 = %esp
push %esi 0xf010ff94 = (0xf010ff98-4) 0xf010ff98
这之中的指令会打印”entering …3”  
这之中的指令会打印”entering …2”  
这之中的指令会打印”entering …1”  
–断点0xf0100040 0xf010ff3c 0xf010ff58
push %ebp 0xf010ff38 = (0xf010ff3c-4);
此时在这个地址内存放的是ebp的值0xf010ff58
0xf010ff58
mov %esp,%ebp 0xf010ff38 0xf010ff38 = %esp

注意上面一直按c进入断点,打印过 entrying test_backtrace 1 后,此时的断点位置在 test_backtrace(0),此后不会再嵌套循环了,不会再经过目前加在 test_backtrace 位置上的断点了。因此要慢慢调试退栈。

出栈

现在的关键点在 ` return 语句附近所在的指令地址,也可以在 kernel.asm` 找到

f0100093:				5d						pop %ebp
f0100094:				c3						ret
命令 %esp %ebp
这之中的指令会打印”entering …0”
也会
打印”leaving …0”
 
–断点0xf0100093 0xf010ff38 0xf010ff38
pop %ebp 0xf010ff3c = (0xf010ff38+4);
将之前存放在0xf010ff38地址内的0xf010ff58弹出并存储到ebp
0xf010ff58
ret 0xf010ff40 0xf010ff58
打印”leaving …1”  
–断点0xf0100093 0xf010ff58(esp慢慢退栈到此处) 0xf010ff58
pop %ebp 0xf010ff5c = (0xf010ff58+4);
将之前存放在0xf010ff58地址内的0xf010ff78弹出并存储到ebp
0xf010ff78
ret 0xf010ff60 0xf010ff78
打印”leaving …2”  
打印”leaving …3”  
打印”leaving …4”  
打印”leaving …5”  
之后就不会再进入这两个断点,回到init.c继续执行后面的语句  

参考资料

  1. cnblog-fatsheep9146