我只是在学习汇编中的功能和堆栈框架等,因此我在运行递归算法以查看会发生什么时一直在看gdb中的堆栈框架。
如果我在C中运行一些递归代码,则堆栈看起来就像我期望的那样-每次调用函数时在堆栈上的一个对象。在递归阶乘函数的最低递归级别上,堆栈框架如下所示:(这是gdb中的回溯,在函数的第一行带有一个断点。)
(gdb) bt
#0 factorial (n=1) at recursion.c:20
#1 0x00005555555551c7 in factorial (n=2) at recursion.c:21
#2 0x00005555555551c7 in factorial (n=3) at recursion.c:21
#3 0x00005555555551c7 in factorial (n=4) at recursion.c:21
#4 0x00005555555551c7 in factorial (n=5) at recursion.c:21
#5 0x00005555555551c7 in factorial (n=6) at recursion.c:21
#6 0x00005555555551c7 in factorial (n=7) at recursion.c:21
#7 0x00005555555551c7 in factorial (n=8) at recursion.c:21
#8 0x00005555555551c7 in factorial (n=9) at recursion.c:21
#9 0x00005555555551c7 in factorial (n=10) at recursion.c:21
#10 0x000055555555517f in main (argc=2, args=0x7fffffffe768) at recursion.c:13
我的C代码是这样的:
int factorial (int n)
{
if (n <= 1) return 1;
return n * factorial(n-1);
}
现在,我在汇编中也这样做(我从Rey Seyfarth的书“ 64位汇编编程简介”中复制了此代码,因此我认为它是正确的),无论递归的深度如何,堆栈框架看起来都像这样:(第50行是call fact
行)。
(gdb) bt
#0 fact () at fact.asm:40
#1 0x00000000004011a8 in greater () at fact.asm:50
#2 0x0000000000000000 in ?? ()
阶乘函数的代码是这样的-在这种情况下,断点位于sub rsp, 16
行:
fact: ; recursive function
n equ 8
push rbp
mov rbp, rsp
sub rsp, 16 ; make room for n
cmp rdi, 1 ; end recursion if n=1
jg greater
mov eax, 1
leave
ret
greater:
mov [rsp+n], rdi ; save n
dec rdi ; call fact with n-1
call fact
mov rdi, [rsp+n] ; restore original n
imul rax, rdi
leave
ret
实际上,在这种情况下,回溯的输出确实使我感到困惑。如果在调用事实函数(dec rdi
)之前将断点放在行上,则结果通常是这样的:
(gdb) bt
#0 greater () at fact.asm:49
#1 0x0000000000000000 in ?? ()
但是事实上,这是第五次:
(gdb) bt
#0 greater () at fact.asm:49
#1 0x00007ffff7f94be0 in ?? () from /usr/lib/libc.so.6
#2 0x0000000000000006 in ?? ()
#3 0x00007fffffffe5f0 in ?? ()
#4 0x00000000004011a8 in greater () at fact.asm:50
#5 0x0000000000000000 in ?? ()
然后在第七次通话中,这是
(gdb) bt
#0 greater () at fact.asm:49
#1 0x0000003000000008 in ?? ()
#2 0x0000000000000004 in ?? ()
#3 0x00007fffffffe5b0 in ?? ()
#4 0x00000000004011a8 in greater () at fact.asm:50
#5 0x0000000000000000 in ?? ()
我的问题:
为什么堆栈的行为与C语言不同?
为什么我最后得到的东西,似乎是垃圾,偶尔输出?
谢谢!
答案 0 :(得分:1)
为什么堆栈的行为与C语言不同?
堆栈本身 的行为完全相同 -处理器不在乎程序是编译的C还是手写的程序集。
不是的行为是GDB对栈是什么的解释。
在x86_64
上(与SPARC
不同),除非您知道当前调用堆栈链中的每个函数如何对其进行调整,否则无法正确地展开堆栈。
GDB使用展开描述符,编译器正是为此目的将其写入输出。这是blog post的解包过程。
您的C程序具有展开描述符(使用readelf -wf a.out
查看它们),但是您的汇编程序没有。
为什么我有时会输出最后似乎是垃圾的东西?
在没有展开描述符的情况下,GDB尝试应用启发式方法以尽其所能,并在遇到无法向上移动的堆栈级别时放弃。究竟发生在哪里取决于堆栈的内容,但实际上并不重要:GDB有效地查看了垃圾数据(因为它不知道在哪里正确查找)。
P.S。您可以只用少数几个CFI directives来扩充汇编程序,以创建适当的展开描述符,然后GDB会很乐意对其进行处理,除非它看起来像YASM doesn't support CFI。将程序集重写为GAS语法,然后在其中添加CFI指令当然是很简单的。