通过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继续执行后面的语句 |