在这个简单的函数调用堆栈中发生了啥?

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_EBPbuffer 之间总是有两个奇怪的地址xb7ec63000xb7ff1040(参见下面的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) ,在getpathleave 指令处设置断点,并将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 如您所见,0xb7ec63650xb7ff1040 仍然存在。

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 摆脱它们。

【讨论】:

以上是关于在这个简单的函数调用堆栈中发生了啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何判断调用函数时引用了啥? [复制]

是啥导致这个指针的值从一个简单的函数调用发生变化?

当您在 php 中调用函数时,内部会发生啥

当执行 import vue from 'vue' 时发生了啥?

C++11 中的“转换构造函数”发生了啥变化? [复制]

这个函数顶部发生了啥