在这个简单的函数调用堆栈中发生了啥?
Posted
技术标签:
【中文标题】在这个简单的函数调用堆栈中发生了啥?【英文标题】:What happened in this simple function call stack?在这个简单的函数调用堆栈中发生了什么? 【发布时间】:2021-10-11 22:53:48 【问题描述】:假设我们有以下超级简单的程序ex.c
:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void getpath()
char buffer[8];
gets(buffer);
int main(int argc, char **argv)
getpath();
在返回 main() 函数之前,我希望在堆栈中看到的内容类似于
buffer[0-3]
buffer[4-7]
SAVED_EBP
SAVED_EIP
在SAVED_EBP
和buffer
之间总是有两个奇怪的地址xb7ec6300
和0xb7ff1040
(参见下面的gdb() 会话),我尝试了不同的缓冲区长度,因为我认为这是由于编译器完成的某种填充,但是,它们总是在那里。
我的问题是?这些地址是什么?为什么总是分配它们?
getpath()
大会:
(gdb) disass getpath
Dump of assembler code for function getpath:
0x080483c4 <getpath+0>: push ebp
0x080483c5 <getpath+1>: mov ebp,esp
0x080483c7 <getpath+3>: sub esp,0x28
0x080483ca <getpath+6>: lea eax,[ebp-0x10]
0x080483cd <getpath+9>: mov DWORD PTR [esp],eax
0x080483d0 <getpath+12>: call 0x80482e8 <gets@plt>
0x080483d5 <getpath+17>: leave
0x080483d6 <getpath+18>: ret
End of assembler dump.
编译后 (gcc -o ex ex.c
) ,在getpath
的leave
指令处设置断点,并将AAAAAAA
作为输入:
(gdb) x/12x $sp
0xbffffc80: 0xbffffc98 0x0804959c 0xbffffcb8 0x08048419
0xbffffc90: 0xb7fd8304 0xb7fd7ff4 0x41414141 0x00414141
0xbffffca0: 0xb7ec6365 0xb7ff1040 0xbffffcb8 0x080483e2
(gdb) x/1x 0xb7ec6365
0xb7ec6365 <__cxa_atexit+53>: 0x5b10c483
(gdb) x/1x 0xb7ff1040
0xb7ff1040 <_dl_fini>: 0x57e58955
(gdb) info frame
Stack level 0, frame at 0xbffffcb0:
eip = 0x80483d5 in getpath; saved eip 0x80483e2
called by frame at 0xbffffcc0
Arglist at 0xbffffca8, args:
Locals at 0xbffffca8, Previous frame's sp is 0xbffffcb0
Saved registers:
ebp at 0xbffffca8, eip at 0xbffffcac
更新
感谢@Daniel Kleinstein!所以显然责任是gets()
,我们可以在这里看到:
我写了两个琐碎的程序,唯一的区别是使用gets()
:
gets.c
:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
char getpath()
char buffer[4];
gets(buffer);
int main(int argc, char **argv)
getpath();
nogets.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void getpath()
char buffer[4] = 65,65,65,65;
int main(int argc, char **argv)
getpath();
然后我们用gdb
运行这两个程序并设置一个breakpoint
对应getpath
中的leave
指令(就像我们之前所做的那样),我们用命令x/12x $sp
检查堆栈。
gets.c STACK FRAME
如您所见,0xb7ec6365
和 0xb7ff1040
仍然存在。
nogets.c STACK FRAME
但是我找不到任何关于这个清理过程的文档,你知道我该如何深入挖掘吗?
【问题讨论】:
调用对流可能包含一个保留区域,用于保存额外的、被调用者保存的寄存器。像这种方式的常见优化 ESP 只需要为小函数增加一次,而不是被调用的函数也必须这样做。 我不完全理解您所说的“这种方式 ESP 只需要为小函数递增一次,而不是被调用函数也必须这样做”是什么意思。我尝试在不允许优化的情况下编译相同的代码(-O0
标志),但堆栈帧始终相同,所以仍然是这种情况吗?
这意味着这是调用约定的一部分。被调用者保证调用者保留了一些空间,并且可以使用它而无需再次猜测。不要将编译器优化(您正在考虑内联)与接口设计中的优化(仅适用于非内联函数调用)混淆。
@Ext3h 这不是调用约定——它与gets
清理有关。如果您将gets
替换为对其他glibc
函数的调用,您将不会获得相同的效果。 (事实上,没有调用约定规定您必须将atexit
或_dl_fini
放在堆栈上)
@ИванКарамазов 这似乎不是有据可查的行为。如果您查看gets
的实现here,那么您可以看到对_IO_acquire_lock
的调用——它使用gcc 的__attribute__(cleanup
,我认为 是导致这种堆栈操作的原因——但是我不确定。
【参考方案1】:
我认为这是由于编译器进行了某种填充,
它们实际上只是编译器添加的填充以保持堆栈 32 字节对齐。您可以使用-mpreferred-stack-boundary=2
摆脱它们。
【讨论】:
以上是关于在这个简单的函数调用堆栈中发生了啥?的主要内容,如果未能解决你的问题,请参考以下文章