共计 11706 个字符,预计需要花费 30 分钟才能阅读完成。
操作系统版本
Linux e982ba054bfa 4.9.125-linuxkit x86_64 x86_64 x86_64 GNU/Linux
gcc version 6.4.0 运行与 mac 下的 docker
执行代码
// 递归函数调用实现斐波那契
int fib(int n)
{if (n <= 2)
{return 1;}
else
{return fib(n - 1) + fib(n - 2);
}
}
int main()
{fib(4);
return 0;
}
编译
gcc fib.c -g -o fib //- g 选项使指标文件 fib 蕴含程序的调试信息
开始
gdb fib
(gdb) start // 拉起被调试程序,并执行至 main 函数的开始地位
Temporary breakpoint 1 at 0x40050f: file fib.c, line 14.
Starting program: /home/work/fib
Temporary breakpoint 1, main () at fib.c:14
14 fib(4);
查看以后栈层信息及 rbp,rsp 的值
(gdb) info f // 打印出以后栈层的信息
Stack level 0, frame at 0x7fffffffe6e0:
rip = 0x40050f in main (fib.c:14); saved rip = 0x7ffff7a303d5
source language c.
Arglist at 0x7fffffffe6d0, args:
Locals at 0x7fffffffe6d0, Previous frame's sp is 0x7fffffffe6e0
Saved registers:
rbp at 0x7fffffffe6d0, rip at 0x7fffffffe6d8
(gdb) info registers rbp rsp // 以后 rbp rsp 的值
rbp 0x7fffffffe6d0 0x7fffffffe6d0
rsp 0x7fffffffe6d0 0x7fffffffe6d0
查看以后函数的汇编信息, 能够看到以后汇编指令执行到 0x000000000040050f(貌似我的跟他人的如同不一样, 具体能够以本人的为准), 我这里曾经执行完 main 的 frame 初始化了
Dump of assembler code for function main:
13 {
0x000000000040050b <+0>: 55 push %rbp // 将 rbp 寄存器的值压栈
0x000000000040050c <+1>: 48 89 e5 mov %rsp,%rbp // 将 rsp 寄存器的值赋给 rbp 寄存器,初始化以后函数的栈底
14 fib(4);
=> 0x000000000040050f <+4>: bf 04 00 00 00 mov $0x4,%edi
0x0000000000400514 <+9>: e8 b4 ff ff ff callq 0x4004cd <fib>
15 return 0;
0x0000000000400519 <+14>: b8 00 00 00 00 mov $0x0,%eax
16 }
0x000000000040051e <+19>: 5d pop %rbp
0x000000000040051f <+20>: c3 retq
End of assembler dump.
在下面的代码能够看到接下来将要执行的两步别离是
- 0x000000000040050f 把 4 付给 edi 寄存器
- 0x0000000000400514 fib 函数调用
好了执行一下
(gdb) si 2 //si 是汇编级别的执行下一条,si 2 执行 2 条到 fib 中
查看一下以后寄存器及内存信息
(gdb) info registers rbp rsp edi
rbp 0x7fffffffe6d0 0x7fffffffe6d0
rsp 0x7fffffffe6c8 0x7fffffffe6c8
edi 0x4 4
(gdb) x/20x 0x7fffffffe6a0 // 从 0x7fffffffe6a0 开始以 16 进制打印 20 条信息
0x7fffffffe6a0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe6b0: 0x00400520 0x00000000 0x004003e0 0x00000000
0x7fffffffe6c0: 0xffffe7b0 0x00007fff 0x00400519 0x00000000
0x7fffffffe6d0: 0x00000000 0x00000000 0xf7a303d5 0x00007fff
0x7fffffffe6e0: 0x00000000 0x00000000 0xffffe7b8 0x00007fff
能够看到当初寄存器 edi 的值是 4,rsp 的值是 0x7fffffffe6c8, 怎么变了呢?认真看上面的内存信息在 0x7fffffffe6c8 到 0x7fffffffe6d0 之前压入了一个地址 0x00400519 往上翻翻是不是 fib 函数下一条要执行的汇编指令的地址呢。而 rsp 代表的是栈顶的地址,也就跟着扩容到了 0x7fffffffe6c。接下来打印一下以后汇编指令,应该曾经跳到 fib 函数外部了
(gdb) disassemble /rm
Dump of assembler code for function fib:
2 {
=> 0x00000000004004cd <+0>: 55 push %rbp
0x00000000004004ce <+1>: 48 89 e5 mov %rsp,%rbp
0x00000000004004d1 <+4>: 53 push %rbx
0x00000000004004d2 <+5>: 48 83 ec 18 sub $0x18,%rsp
0x00000000004004d6 <+9>: 89 7d ec mov %edi,-0x14(%rbp)
3 if (n <= 2)
0x00000000004004d9 <+12>: 83 7d ec 02 cmpl $0x2,-0x14(%rbp)
0x00000000004004dd <+16>: 7f 07 jg 0x4004e6 <fib+25>
4 {
5 return 1;
0x00000000004004df <+18>: b8 01 00 00 00 mov $0x1,%eax
0x00000000004004e4 <+23>: eb 1e jmp 0x400504 <fib+55>
6 }
7 else
8 {9 return fib(n - 1) + fib(n - 2);
0x00000000004004e6 <+25>: 8b 45 ec mov -0x14(%rbp),%eax
0x00000000004004e9 <+28>: 83 e8 01 sub $0x1,%eax
0x00000000004004ec <+31>: 89 c7 mov %eax,%edi
0x00000000004004ee <+33>: e8 da ff ff ff callq 0x4004cd <fib>
0x00000000004004f3 <+38>: 89 c3 mov %eax,%ebx
0x00000000004004f5 <+40>: 8b 45 ec mov -0x14(%rbp),%eax
0x00000000004004f8 <+43>: 83 e8 02 sub $0x2,%eax
0x00000000004004fb <+46>: 89 c7 mov %eax,%edi
0x00000000004004fd <+48>: e8 cb ff ff ff callq 0x4004cd <fib>
0x0000000000400502 <+53>: 01 d8 add %ebx,%eax
10 }
11 }
0x0000000000400504 <+55>: 48 83 c4 18 add $0x18,%rsp
0x0000000000400508 <+59>: 5b pop %rbx
0x0000000000400509 <+60>: 5d pop %rbp
0x000000000040050a <+61>: c3 retq
End of assembler dump.
接下来先进行 fib 的 frame 初始化, 而后看一下以后寄存器的状况和内存信息
(gdb) si 2
(gdb) info registers rbp rsp edi
rbp 0x7fffffffe6c0 0x7fffffffe6c0
rsp 0x7fffffffe6c0 0x7fffffffe6c0
edi 0x4 4
(gdb) x/20x 0x7fffffffe6a0
0x7fffffffe6a0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe6b0: 0x00400520 0x00000000 0x004003e0 0x00000000
0x7fffffffe6c0: 0xffffe6d0 0x00007fff 0x00400519 0x00000000
0x7fffffffe6d0: 0x00000000 0x00000000 0xf7a303d5 0x00007fff
0x7fffffffe6e0: 0x00000000 0x00000000 0xffffe7b8 0x00007fff
能够看到 edi 的值没有变动,rbp 和 rsp 的值都变成了 0x7fffffffe6c0, 认真看上面的内存信息在 0x7fffffffe6c0 到 0x7fffffffe6c8 的地位压入了 main 的 rbp 的值 0xffffe6d0,rsp 跟着扩容,接下来把 rsp 的值赋给 rbp。fib 的 frame 初始化实现。
接下来在往下走三步
- push %rbx // 讲 rbx 寄存器的值压栈,rsp-8
- sub $0x18,%rsp //rsp-0x18(24)
- mov %edi,-0x14(%rbp) // 把 edi 寄存器的值 (4) 放到距 rbp 寄存器寄存的地址偏移 -0x14(20)位的中央
打印一下寄存器和内存信息
(gdb) si 3
(gdb) info registers rbp rsp edi rbx
rbp 0x7fffffffe6c0 0x7fffffffe6c0
rsp 0x7fffffffe6a0 0x7fffffffe6a0
edi 0x4 4
rbx 0x0 0
(gdb) x/20x 0x7fffffffe6a0
0x7fffffffe6a0: 0x00000000 0x00000000 0x00000000 0x00000004
0x7fffffffe6b0: 0x00400520 0x00000000 0x00000000 0x00000000
0x7fffffffe6c0: 0xffffe6d0 0x00007fff 0x00400519 0x00000000
0x7fffffffe6d0: 0x00000000 0x00000000 0xf7a303d5 0x00007fff
0x7fffffffe6e0: 0x00000000 0x00000000 0xffffe7b8 0x00007fff
在往下走
- cmpl $0x2,-0x14(%rbp) // 2 和 -0x14(%rbp)的值(4)相减,大于等于 0 执行下一条否则执行 jg 指令,这里显然不合乎
- jg 0x4004e6 <fib+25> // 这个就是要跳转的中央了,
return fib(n - 1) + fib(n - 2);
=> 0x00000000004004e6 <+25>: 8b 45 ec mov -0x14(%rbp),%eax
0x00000000004004e9 <+28>: 83 e8 01 sub $0x1,%eax
0x00000000004004ec <+31>: 89 c7 mov %eax,%edi
0x00000000004004ee <+33>: e8 da ff ff ff callq 0x4004cd <fib>
0x00000000004004f3 <+38>: 89 c3 mov %eax,%ebx
0x00000000004004f5 <+40>: 8b 45 ec mov -0x14(%rbp),%eax
0x00000000004004f8 <+43>: 83 e8 02 sub $0x2,%eax
0x00000000004004fb <+46>: 89 c7 mov %eax,%edi
0x00000000004004fd <+48>: e8 cb ff ff ff callq 0x4004cd <fib>
0x0000000000400502 <+53>: 01 d8 add %ebx,%eax
- mov -0x14(%rbp),%eax // 把 -0x14(%rbp)的值(4)赋给 eax 寄存器
- sub $0x1,%eax //eax 的值 -1
- mov %eax,%edi // 把 eax 的值赋给 edi
- callq 0x4004cd <fib> // 调用 fib 函数, 留神这个地址跟之前的一样
再查看一下寄存器信息和内存信息
(gdb) info registers rbp rsp edi rbx eax
rbp 0x7fffffffe6c0 0x7fffffffe6c0
rsp 0x7fffffffe698 0x7fffffffe698
edi 0x3 3
rbx 0x0 0
eax 0x3 3
(gdb) x/20x 0x7fffffffe690
0x7fffffffe690: 0x00000001 0x00000000 0x004004f3 0x00000000
0x7fffffffe6a0: 0x00000000 0x00000000 0x00000000 0x00000004
0x7fffffffe6b0: 0x00400520 0x00000000 0x00000000 0x00000000
0x7fffffffe6c0: 0xffffe6d0 0x00007fff 0x00400519 0x00000000
0x7fffffffe6d0: 0x00000000 0x00000000 0xf7a303d5 0x00007fff
能够看到以后 edi,eax 的值是 3, 在 0x7fffffffe698 到 0x7fffffffe6a0 之间压入了这次函数调用的下一条指令地址,以后 rsp 是 0x7fffffffe698。
查看汇编指令信息之后发现跟之前根本一样, 初始化 frame 之后, 以后寄存器和内存信息如下
(gdb) info registers rbp rsp edi rbx eax
rbp 0x7fffffffe690 0x7fffffffe690
rsp 0x7fffffffe690 0x7fffffffe690
edi 0x3 3
rbx 0x0 0
eax 0x3 3
(gdb) x/20x 0x7fffffffe690
0x7fffffffe690: 0xffffe6c0 0x00007fff 0x004004f3 0x00000000
0x7fffffffe6a0: 0x00000000 0x00000000 0x00000000 0x00000004
0x7fffffffe6b0: 0x00400520 0x00000000 0x00000000 0x00000000
0x7fffffffe6c0: 0xffffe6d0 0x00007fff 0x00400519 0x00000000
0x7fffffffe6d0: 0x00000000 0x00000000 0xf7a303d5 0x00007fff
跟下面一样, 将之前的 rbp 压栈, 同步 rsp 信息至 rbp, 接下来持续下面的三步打印一下寄存器和内存信息
(gdb) info registers rbp rsp edi rbx eax
rbp 0x7fffffffe690 0x7fffffffe690
rsp 0x7fffffffe670 0x7fffffffe670
edi 0x3 3
rbx 0x0 0
eax 0x3 3
(gdb) x/20x 0x7fffffffe660
0x7fffffffe660: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe670: 0x00000000 0x00000000 0x00000000 0x00000003
0x7fffffffe680: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe690: 0xffffe6c0 0x00007fff 0x004004f3 0x00000000
0x7fffffffe6a0: 0x00000000 0x00000000 0x00000000 0x00000004
将 3 压入 0x7fffffffe678 到 0x7fffffffe680 之间, 以后 rsp = rsp – push rbx (8)– 0x18 = 0x7fffffffe670。
持续往下走显然 3 也不满足条件, 持续下面的步骤
- mov -0x14(%rbp),%eax // 把 -0x14(%rbp)的值(3)赋给 eax 寄存器
- sub $0x1,%eax //eax 的值 -1
- mov %eax,%edi // 把 eax 的值赋给 edi
- callq 0x4004cd <fib> // 调用 fib 函数
查看一下寄存器信息和内存信息
(gdb) info registers rbp rsp edi rbx eax
rbp 0x7fffffffe690 0x7fffffffe690
rsp 0x7fffffffe668 0x7fffffffe668
edi 0x2 2
rbx 0x0 0
eax 0x2 2
(gdb) x/20x 0x7fffffffe660
0x7fffffffe660: 0x00000000 0x00000000 0x004004f3 0x00000000
0x7fffffffe670: 0x00000000 0x00000000 0x00000000 0x00000003
0x7fffffffe680: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe690: 0xffffe6c0 0x00007fff 0x004004f3 0x00000000
0x7fffffffe6a0: 0x00000000 0x00000000 0x00000000 0x00000004
同样将 0x004004f3 压栈,rsp-8
初始化 frame 和参数之后查问寄存器信息和内存信息
(gdb) info registers rbp rsp edi rbx eax
rbp 0x7fffffffe660 0x7fffffffe660
rsp 0x7fffffffe640 0x7fffffffe640
edi 0x2 2
rbx 0x0 0
eax 0x2 2
(gdb) x/20x 0x7fffffffe640
0x7fffffffe640: 0x00000002 0x00000000 0x00000000 0x00000002
0x7fffffffe650: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe660: 0xffffe690 0x00007fff 0x004004f3 0x00000000
0x7fffffffe670: 0x00000000 0x00000000 0x00000000 0x00000003
0x7fffffffe680: 0x00000000 0x00000000 0x00000000 0x00000000
合乎预期,持续往下走,2- 2 显然满足条件
return 1; => 0x00000000004004df <+18>: b8 01 00 00 00 mov $0x1,%eax 0x00000000004004e4 <+23>: eb 1e jmp 0x400504 <fib+55>
- mov $0x1,%eax // 把 1 赋给 eax 寄存器
- jmp 0x400504 <fib+55> // 跳转
=> 0x0000000000400504 <+55>: 48 83 c4 18 add $0x18,%rsp 0x0000000000400508 <+59>: 5b pop %rbx 0x0000000000400509 <+60>: 5d pop %rbp 0x000000000040050a <+61>: c3 retq
- add $0x18,%rsp //rsp+0x18(24) = 0x7fffffffe658
- pop %rbx // rsp+8 = 0x7fffffffe660
- pop %rbp //rbp = 0xffffe690 ; rsp + 8 = 0x7fffffffe668
- retq // rsp + 8 = 0x7fffffffe670 , 跳转到 0x004004f3
在查看下信息
(gdb) info registers rbp rsp edi rbx eax
rbp 0x7fffffffe690 0x7fffffffe690
rsp 0x7fffffffe670 0x7fffffffe670
edi 0x2 2
rbx 0x1 0
eax 0x1 1
(gdb) x/20x 0x7fffffffe640
0x7fffffffe640: 0x00000002 0x00000000 0x00000000 0x00000002
0x7fffffffe650: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe660: 0xffffe690 0x00007fff 0x004004f3 0x00000000
0x7fffffffe670: 0x00000000 0x00000000 0x00000000 0x00000003
0x7fffffffe680: 0x00000000 0x00000000 0x00000000 0x00000000
跳回来,记得这个 fib(3)那个
=> 0x00000000004004f3 <+38>: 89 c3 mov %eax,%ebx
0x00000000004004f5 <+40>: 8b 45 ec mov -0x14(%rbp),%eax
0x00000000004004f8 <+43>: 83 e8 02 sub $0x2,%eax
0x00000000004004fb <+46>: 89 c7 mov %eax,%edi
0x00000000004004fd <+48>: e8 cb ff ff ff callq 0x4004cd <fib>
0x0000000000400502 <+53>: 01 d8 add %ebx,%eax
- mov %eax,%ebx //eax 的值赋给 ebx
- mov -0x14(%rbp),%eax //-0x14(%rbp)地位的值赋给 eax
- sub $0x2,%eax//eax(3)-2
- mov %eax,%edi //eax 赋给 edi
- callq 0x4004cd <fib> // 调用函数(又来了)
对照一下内存信息
(gdb) x/20x 0x7fffffffe660
0x7fffffffe660: 0xffffe690 0x00007fff 0x00400502 0x00000000
0x7fffffffe670: 0x00000000 0x00000000 0x00000000 0x00000003
0x7fffffffe680: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffe690: 0xffffe6c0 0x00007fff 0x004004f3 0x00000000
0x7fffffffe6a0: 0x00000000 0x00000000 0x00000000 0x00000004
开始新一轮的调用, 把下一条指令地址 0x00400502 压栈。rsp-8,初始化 frame 和参数之后查问寄存器信息和内存信息
(gdb) info registers rbp rsp edi rbx eax
rbp 0x7fffffffe660 0x7fffffffe660
rsp 0x7fffffffe640 0x7fffffffe640
edi 0x1 1
rbx 0x1 1
eax 0x1 1
(gdb) x/20x 0x7fffffffe640
0x7fffffffe640: 0x00000002 0x00000000 0x00000000 0x00000001
0x7fffffffe650: 0x00000000 0x00000000 0x00000001 0x00000000
0x7fffffffe660: 0xffffe690 0x00007fff 0x00400502 0x00000000
0x7fffffffe670: 0x00000000 0x00000000 0x00000000 0x00000003
0x7fffffffe680: 0x00000000 0x00000000 0x00000000 0x00000000
- push %rbx //rbx 的值压栈 这次有值了 1, rsp-8
- sub $0x18,%rsp //rsp-0x18(24)
- mov %edi,-0x14(%rbp) //edi 的值赋给 -0x14(%rbp)
持续往下走,1- 2 显然满足条件
return 1;
=> 0x00000000004004df <+18>: b8 01 00 00 00 mov $0x1,%eax
0x00000000004004e4 <+23>: eb 1e jmp 0x400504 <fib+55>
- mov $0x1,%eax // 把 1 赋给 eax 寄存器
- jmp 0x400504 <fib+55> // 跳转
=> 0x0000000000400504 <+55>: 48 83 c4 18 add $0x18,%rsp
0x0000000000400508 <+59>: 5b pop %rbx
0x0000000000400509 <+60>: 5d pop %rbp
0x000000000040050a <+61>: c3 retq
- add $0x18,%rsp //rsp+0x18(24) = 0x7fffffffe658
- pop %rbx // rsp+8 = 0x7fffffffe660
- pop %rbp //rbp = 0xffffe690 ; rsp + 8 = 0x7fffffffe668
- retq // rsp + 8 = 0x7fffffffe670 , 跳转到 0x00400502
在查看下信息
(gdb) x/20x 0x7fffffffe640
0x7fffffffe640: 0x00000002 0x00000000 0x00000000 0x00000001
0x7fffffffe650: 0x00000000 0x00000000 0x00000001 0x00000000
0x7fffffffe660: 0xffffe690 0x00007fff 0x00400502 0x00000000
0x7fffffffe670: 0x00000000 0x00000000 0x00000000 0x00000003
0x7fffffffe680: 0x00000000 0x00000000 0x00000000 0x00000000
回来持续往下走
0x0000000000400502 <+53>: 01 d8 add %ebx,%eax
10 }
11 }
0x0000000000400504 <+55>: 48 83 c4 18 add $0x18,%rsp
0x0000000000400508 <+59>: 5b pop %rbx
0x0000000000400509 <+60>: 5d pop %rbp
0x000000000040050a <+61>: c3 retq
- add %ebx,%eax // eax(2) = ebx(1)+eax(1)
- add $0x18,%rsp //rsp+0x18= 0x7fffffffe658
- pop %rbx //rsp+8,rbx=0
- pop %rbp //rsp+8,rbp=0xffffe690
- retq //rsp+8 , 跳转 0x00400502
查看信息
(gdb) x/20x 0x7fffffffe640
0x7fffffffe640: 0x00000002 0x00000000 0x00000000 0x00000001
0x7fffffffe650: 0x00000000 0x00000000 0x00000001 0x00000000
0x7fffffffe660: 0xffffe690 0x00007fff 0x00400502 0x00000000
0x7fffffffe670: 0x00000000 0x00000000 0x00000000 0x00000003
0x7fffffffe680: 0x00000000 0x00000000 0x00000000 0x00000000
前面流程根本一样就不在往下写了。
总结一下
- 栈是 FILO(first in last out),先进后出。main 函数先进栈, 所以最初进去。
- %ESP – 堆栈指针
- 这个 32 位寄存器由多个 CPU 指令(PUSH,POP,CALL 和 RET 等)隐式操作,它总是指向堆栈上应用的最初一个元素(不是第一个自在元素)
- “堆栈顶部”是一个占用地位,而不是一个闲暇地位,并且位于最低内存地址。
- %EBP – 基准指针
- 该 32 位寄存器用于援用以后堆栈帧中的所有函数参数和局部变量。与%esp 寄存器不同,根本指针仅被显式操作。这有时被称为“帧指针”。
- %EIP – 指令指针
- 它保留要执行的下一个 CPU 指令的地址,并作为 CALL 指令的一部分保留到堆栈中。同样,任何“跳转”指令都会间接批改%EIP。
- 英特尔汇编程序世界中的每个人都应用 Intel 表示法,但 GNU C 编译器应用他们称之为“AT&T 语法”的向后兼容性。这对咱们来说仿佛是一个十分愚昧的想法,但这是生存中的事实。两种符号之间存在较小的符号差别,但到目前为止最令人讨厌的是 AT&T 语法会反转源和指标操作数。要将立刻值 4 挪动到 EAX 寄存器:原文地址:http://www.unixwiz.net/techtips/win32-callconv-asm.html
mov $ 4,%eax // AT&T 表示法 mov eax,4 // Intel 表示法