以一个最简单的加法函数为例:
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 ; 返回调用者
解释如下:
push ebp
:将当前函数的帧指针保存到栈中,以便在函数退出时恢复它的值。(这个栈是一个单独的内存空间,等于把ebp内容放到栈中)mov ebp, esp
:将当前栈指针保存到帧指针中,以便在函数执行过程中可以访问和操作栈中的数据。(把esp放到ebp中)sub esp, 8
:为函数分配 8 字节的栈空间,用于存储局部变量。push ebx
:将 ebx 寄存器的值保存到栈中,因为它是被调用函数所使用的寄存器之一。mov eax, dword ptr [a]
:将第一个参数 a 的值从栈中取出并存储到 eax 寄存器中。add eax, dword ptr [b]
:将第二个参数 b 的值从栈中取出并加到 eax 寄存器中,得到函数的返回值。pop ebx
:恢复 ebx 寄存器的值。mov esp, ebp
:恢复栈指针,以便将栈指针回退到函数调用前的位置。pop ebp
:恢复帧指针,以便将帧指针回退到函数调用前的位置。ret
:将返回地址从栈中取出并跳转到该地址,以便继续执行调用该函数的代码。
因此,C++ 函数调用的汇编底层进入和退出原理可以总结为:
函数被调用时,将返回地址和帧指针保存到栈中,并为函数分配栈空间。
函数执行过程中,使用帧指针和栈指针访问和操作栈中的数据。
函数执行完成时,恢复栈和帧指针,将返回地址从栈中取出并跳转到该地址,以便继续执行调用该函数的代码。
要是一个程序卡住了,怎么得到调用栈?
gdb
ps aux | grep your_program_name
gdb -p <PID>
(gdb) bt
pstack工具
pstack <PID>
使用 kill -SIGQUIT
kill -SIGQUIT <PID>
捕获信号,并且将调用栈信息打印到标准输出(stdout)或日志文件中。这种方式适合应用程序内部已经处理 SIGQUIT 信号的情况(如 Java、Go 等)。perf、strace等工具