【发布时间】: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 检查堆栈。
但是我找不到任何关于这个清理过程的文档,你知道我该如何深入挖掘吗?
【问题讨论】:
-
调用对流可能包含一个保留区域,用于保存额外的、被调用者保存的寄存器。像这种方式的常见优化 ESP 只需要为小函数增加一次,而不是被调用的函数也必须这样做。
-
我不完全理解您所说的“这种方式 ESP 只需要为小函数递增一次,而不是被调用函数也必须这样做”是什么意思。我尝试在不允许优化的情况下编译相同的代码(
-O0标志),但堆栈帧始终相同,所以仍然是这种情况吗? -
这意味着这是调用约定的一部分。被调用者保证调用者保留了一些空间,并且可以使用它而无需再次猜测。不要将编译器优化(您正在考虑内联)与接口设计中的优化(仅适用于非内联函数调用)混淆。
-
@Ext3h 这不是调用约定——它与
gets清理有关。如果您将gets替换为对其他glibc函数的调用,您将不会获得相同的效果。 (事实上,没有调用约定规定您必须将atexit或_dl_fini放在堆栈上) -
@ИванКарамазов 这似乎不是有据可查的行为。如果您查看
gets的实现here,那么您可以看到对_IO_acquire_lock的调用——它使用gcc 的__attribute__(cleanup,我认为 是导致这种堆栈操作的原因——但是我不确定。
标签: c memory-management gdb stack function-call