操作系统版本

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:1414     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    0x7fffffffe6d0rsp            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,%eax16    }   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    0x7fffffffe6d0rsp            0x7fffffffe6c8    0x7fffffffe6c8edi            0x4    4(gdb) x/20x 0x7fffffffe6a0 //从0x7fffffffe6a0开始以16进制打印20条信息            0x7fffffffe6a0:    0x00000000    0x00000000    0x00000000    0x000000000x7fffffffe6b0:    0x00400520    0x00000000    0x004003e0    0x000000000x7fffffffe6c0:    0xffffe7b0    0x00007fff    0x00400519    0x000000000x7fffffffe6d0:    0x00000000    0x00000000    0xf7a303d5    0x00007fff0x7fffffffe6e0:    0x00000000    0x00000000    0xffffe7b8    0x00007fff

能够看到当初寄存器edi的值是4,rsp的值是0x7fffffffe6c8,怎么变了呢?认真看上面的内存信息在0x7fffffffe6c8到0x7fffffffe6d0之前压入了一个地址0x00400519往上翻翻是不是fib函数下一条要执行的汇编指令的地址呢。而rsp代表的是栈顶的地址,也就跟着扩容到了0x7fffffffe6c。接下来打印一下以后汇编指令,应该曾经跳到fib函数外部了

(gdb) disassemble /rmDump 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     else8     {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,%eax10     }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 edirbp            0x7fffffffe6c0    0x7fffffffe6c0rsp            0x7fffffffe6c0    0x7fffffffe6c0edi            0x4    4 (gdb) x/20x 0x7fffffffe6a0      0x7fffffffe6a0:    0x00000000    0x00000000    0x00000000    0x000000000x7fffffffe6b0:    0x00400520    0x00000000    0x004003e0    0x000000000x7fffffffe6c0:    0xffffe6d0    0x00007fff    0x00400519    0x000000000x7fffffffe6d0:    0x00000000    0x00000000    0xf7a303d5    0x00007fff0x7fffffffe6e0:    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 rbxrbp            0x7fffffffe6c0    0x7fffffffe6c0rsp            0x7fffffffe6a0    0x7fffffffe6a0edi            0x4    4rbx            0x0    0(gdb) x/20x 0x7fffffffe6a0          0x7fffffffe6a0:    0x00000000    0x00000000    0x00000000    0x000000040x7fffffffe6b0:    0x00400520    0x00000000    0x00000000    0x000000000x7fffffffe6c0:    0xffffe6d0    0x00007fff    0x00400519    0x000000000x7fffffffe6d0:    0x00000000    0x00000000    0xf7a303d5    0x00007fff0x7fffffffe6e0:    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 eaxrbp            0x7fffffffe6c0    0x7fffffffe6c0rsp            0x7fffffffe698    0x7fffffffe698edi            0x3    3rbx            0x0    0eax            0x3    3(gdb) x/20x 0x7fffffffe690              0x7fffffffe690:    0x00000001    0x00000000    0x004004f3    0x000000000x7fffffffe6a0:    0x00000000    0x00000000    0x00000000    0x000000040x7fffffffe6b0:    0x00400520    0x00000000    0x00000000    0x000000000x7fffffffe6c0:    0xffffe6d0    0x00007fff    0x00400519    0x000000000x7fffffffe6d0:    0x00000000    0x00000000    0xf7a303d5    0x00007fff

能够看到以后edi,eax的值是3,在0x7fffffffe698到0x7fffffffe6a0之间压入了这次函数调用的下一条指令地址,以后rsp是0x7fffffffe698。

查看汇编指令信息之后发现跟之前根本一样,初始化frame之后,以后寄存器和内存信息如下

(gdb) info registers rbp rsp edi rbx eaxrbp            0x7fffffffe690    0x7fffffffe690rsp            0x7fffffffe690    0x7fffffffe690edi            0x3    3rbx            0x0    0eax            0x3    3(gdb) x/20x 0x7fffffffe690              0x7fffffffe690:    0xffffe6c0    0x00007fff    0x004004f3    0x000000000x7fffffffe6a0:    0x00000000    0x00000000    0x00000000    0x000000040x7fffffffe6b0:    0x00400520    0x00000000    0x00000000    0x000000000x7fffffffe6c0:    0xffffe6d0    0x00007fff    0x00400519    0x000000000x7fffffffe6d0:    0x00000000    0x00000000    0xf7a303d5    0x00007fff

跟下面一样,将之前的rbp压栈,同步rsp信息至rbp,接下来持续下面的三步打印一下寄存器和内存信息

(gdb) info registers rbp rsp edi rbx eaxrbp            0x7fffffffe690    0x7fffffffe690rsp            0x7fffffffe670    0x7fffffffe670edi            0x3    3rbx            0x0    0eax            0x3    3(gdb) x/20x 0x7fffffffe6600x7fffffffe660:    0x00000000    0x00000000    0x00000000    0x000000000x7fffffffe670:    0x00000000    0x00000000    0x00000000    0x000000030x7fffffffe680:    0x00000000    0x00000000    0x00000000    0x000000000x7fffffffe690:    0xffffe6c0    0x00007fff    0x004004f3    0x000000000x7fffffffe6a0:    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 eaxrbp            0x7fffffffe690    0x7fffffffe690rsp            0x7fffffffe668    0x7fffffffe668edi            0x2    2rbx            0x0    0eax            0x2    2(gdb) x/20x 0x7fffffffe660              0x7fffffffe660:    0x00000000    0x00000000    0x004004f3    0x000000000x7fffffffe670:    0x00000000    0x00000000    0x00000000    0x000000030x7fffffffe680:    0x00000000    0x00000000    0x00000000    0x000000000x7fffffffe690:    0xffffe6c0    0x00007fff    0x004004f3    0x000000000x7fffffffe6a0:    0x00000000    0x00000000    0x00000000    0x00000004

同样将0x004004f3压栈,rsp-8

初始化frame和参数之后查问寄存器信息和内存信息

(gdb) info registers rbp rsp edi rbx eaxrbp            0x7fffffffe660    0x7fffffffe660rsp            0x7fffffffe640    0x7fffffffe640edi            0x2    2rbx            0x0    0eax            0x2    2(gdb) x/20x 0x7fffffffe640              0x7fffffffe640:    0x00000002    0x00000000    0x00000000    0x000000020x7fffffffe650:    0x00000000    0x00000000    0x00000000    0x000000000x7fffffffe660:    0xffffe690    0x00007fff    0x004004f3    0x000000000x7fffffffe670:    0x00000000    0x00000000    0x00000000    0x000000030x7fffffffe680:    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 eaxrbp            0x7fffffffe690    0x7fffffffe690rsp            0x7fffffffe670    0x7fffffffe670edi            0x2    2rbx            0x1    0eax            0x1    1(gdb) x/20x 0x7fffffffe6400x7fffffffe640:    0x00000002    0x00000000    0x00000000    0x000000020x7fffffffe650:    0x00000000    0x00000000    0x00000000    0x000000000x7fffffffe660:    0xffffe690    0x00007fff    0x004004f3    0x000000000x7fffffffe670:    0x00000000    0x00000000    0x00000000    0x000000030x7fffffffe680:    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    0x000000000x7fffffffe670:    0x00000000    0x00000000    0x00000000    0x000000030x7fffffffe680:    0x00000000    0x00000000    0x00000000    0x000000000x7fffffffe690:    0xffffe6c0    0x00007fff    0x004004f3    0x000000000x7fffffffe6a0:    0x00000000    0x00000000    0x00000000    0x00000004

开始新一轮的调用,把下一条指令地址0x00400502压栈。rsp-8,初始化frame和参数之后查问寄存器信息和内存信息

(gdb) info registers rbp rsp edi rbx eaxrbp            0x7fffffffe660    0x7fffffffe660rsp            0x7fffffffe640    0x7fffffffe640edi            0x1    1rbx            0x1    1eax            0x1    1(gdb) x/20x 0x7fffffffe640              0x7fffffffe640:    0x00000002    0x00000000    0x00000000    0x000000010x7fffffffe650:    0x00000000    0x00000000    0x00000001    0x000000000x7fffffffe660:    0xffffe690    0x00007fff    0x00400502    0x000000000x7fffffffe670:    0x00000000    0x00000000    0x00000000    0x000000030x7fffffffe680:    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 0x7fffffffe6400x7fffffffe640:    0x00000002    0x00000000    0x00000000    0x000000010x7fffffffe650:    0x00000000    0x00000000    0x00000001    0x000000000x7fffffffe660:    0xffffe690    0x00007fff    0x00400502    0x000000000x7fffffffe670:    0x00000000    0x00000000    0x00000000    0x000000030x7fffffffe680:    0x00000000    0x00000000    0x00000000    0x00000000

回来持续往下走

0x0000000000400502 <+53>:    01 d8    add    %ebx,%eax10     }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    0x000000010x7fffffffe650:    0x00000000    0x00000000    0x00000001    0x000000000x7fffffffe660:    0xffffe690    0x00007fff    0x00400502    0x000000000x7fffffffe670:    0x00000000    0x00000000    0x00000000    0x000000030x7fffffffe680:    0x00000000    0x00000000    0x00000000    0x00000000

前面流程根本一样就不在往下写了。

总结一下

  1. 栈是FILO(first in last out),先进后出。main函数先进栈,所以最初进去。
  2. %ESP - 堆栈指针
  3. 这个32位寄存器由多个CPU指令(PUSH,POP,CALL和RET等)隐式操作,它总是指向堆栈上应用的最初一个元素(不是第一个自在元素)
  4. “堆栈顶部”是一个占用地位,而不是一个闲暇地位,并且位于最低内存地址。
  5. %EBP - 基准指针
  6. 该32位寄存器用于援用以后堆栈帧中的所有函数参数和局部变量。与%esp寄存器不同,根本指针仅被显式操作。这有时被称为“帧指针”。
  7. %EIP - 指令指针
  8. 它保留要执行的下一个CPU指令的地址,并作为CALL 指令的一部分保留到堆栈中。同样,任何“跳转”指令都会间接批改%EIP。
  9. 英特尔汇编程序世界中的每个人都应用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表示法