函数调用的汇编底层退出原理

以一个最简单的加法函数为例: int add(int a, int b) { return a + b; } 会有如下汇编: push ebp ; 保存当前函数的帧指针 mov ebp,esp ; 将当前栈指针保存到帧指针中 sub

以一个最简单的加法函数为例:

int add(int a, int b) {
    return a + b;
}

会有如下汇编:

push        ebp         ; 保存当前函数的帧指针
mov         ebp,esp     ; 将当前栈指针保存到帧指针中
sub         esp,8       ; 分配 8 字节的栈空间
push        ebx         ; 保存 ebx 寄存器的值,因为它是被调用函数所使用的寄存器之一
mov         eax,dword ptr [a] ; 取出第一个参数 a 的值
add         eax,dword ptr [b] ; 将第二个参数 b 的值加到 a 上
pop         ebx         ; 恢复 ebx 寄存器的值
mov         esp,ebp     ; 恢复栈指针
pop         ebp         ; 恢复帧指针
ret                     ; 返回调用者

解释如下:

  1. push ebp:将当前函数的帧指针保存到栈中,以便在函数退出时恢复它的值。(这个栈是一个单独的内存空间,等于把ebp内容放到栈中)

  2. mov ebp, esp:将当前栈指针保存到帧指针中,以便在函数执行过程中可以访问和操作栈中的数据。(把esp放到ebp中)

  3. sub esp, 8:为函数分配 8 字节的栈空间,用于存储局部变量。

  4. push ebx:将 ebx 寄存器的值保存到栈中,因为它是被调用函数所使用的寄存器之一。

  5. mov eax, dword ptr [a]:将第一个参数 a 的值从栈中取出并存储到 eax 寄存器中。

  6. add eax, dword ptr [b]:将第二个参数 b 的值从栈中取出并加到 eax 寄存器中,得到函数的返回值

  7. pop ebx:恢复 ebx 寄存器的值。

  8. mov esp, ebp:恢复栈指针,以便将栈指针回退到函数调用前的位置。

  9. pop ebp:恢复帧指针,以便将帧指针回退到函数调用前的位置。

  10. ret:将返回地址从栈中取出并跳转到该地址,以便继续执行调用该函数的代码。

因此,C++ 函数调用的汇编底层进入和退出原理可以总结为:

  1. 函数被调用时,将返回地址和帧指针保存到栈中,并为函数分配栈空间。

  2. 函数执行过程中,使用帧指针和栈指针访问和操作栈中的数据。

  3. 函数执行完成时,恢复栈和帧指针,将返回地址从栈中取出并跳转到该地址,以便继续执行调用该函数的代码。

要是一个程序卡住了,怎么得到调用栈?

  1. gdb
    ps aux | grep your_program_name
    gdb -p <PID>
    (gdb) bt

  2. pstack工具
    pstack <PID>

  3. 使用 kill -SIGQUIT
    kill -SIGQUIT <PID>
    捕获信号,并且将调用栈信息打印到标准输出(stdout)或日志文件中。这种方式适合应用程序内部已经处理 SIGQUIT 信号的情况(如 Java、Go 等)。

  4. perf、strace等工具

Comment