【问题标题】:Why is the stack size different in different runs of the same program?为什么同一程序的不同运行时堆栈大小不同?
【发布时间】:2015-06-15 11:52:48
【问题描述】:

考虑以下程序。它从命令行获取一个参数,然后将递归函数展开到该限制。

#include <stdio.h>
#include <stdlib.h>

int rec(int x, int limit) {
  if (x == limit) {
    return limit;
  }
  int r = rec(x + 1, limit);
  return r - 1;
}

int main(int arc, char* argv[]) {
  int result, limit;
  limit = atoi(argv[1]);
  printf("stack: %p\n", &result);
  result = rec(0, limit);
  printf("%d\n", result);
}

如果我编译它,我希望它会在固定输入参数限制下用完堆栈。其他事情发生了。

dejan@raven:~/test/stack$ gcc stack.c
dejan@raven:~/test/stack$ ./a.out 174580
stack: 0x7fff42fd58f0
Segmentation fault (core dumped)
dejan@raven:~/test/stack$ ./a.out 174580
stack: 0x7ffdd2dd8b20
0

在两次不同的运行中,堆栈大小似乎不同。这似乎不是编译器问题,因为同样的事情发生在 clang 中,并且反汇编不涉及任何奇怪的事情。

为什么不同运行的堆栈大小不同?

【问题讨论】:

  • @Barmar 我不关心堆栈的位置,只关心大小。随机化将如何影响堆栈的大小?
  • 我来这里是和@Barmar 说的一样;但是,我什至不确定程序中声明的第一个变量的地址是否与堆栈位置有很大关系;您的编译器甚至可能不会为该变量分配它自己的空间,而只是将%EAX 中的结果直接传递给printf。
  • @user2357112 在他的程序中没有什么是不确定的,它应该表现一致。它不依赖于系统的任何其他状态。
  • @MarcusMüller 是的,但它应该每次都通过相同的%EAX。

标签: c linux stack


【解决方案1】:

添加 SIGSEGV 处理程序:

void handler( int sig )
{
    char buffer[ 1024 ]
    sprintf( buffer, "/path/to/pmap %d", getpid() );
    system( buffer );
    exit( 0 );
}

int main( int argc, char *argv[] )
{
    signal( SIGSEGV, handler );
       .
       .
       .

这样,您的进程将在 SEGV 时发出其地址空间的映射,而不是生成核心文件。

请注意,通常这是一种非常危险的做事方式。这不是真正的异步信号安全。但是您并没有做任何会导致僵局造成真正损害的事情。

【讨论】:

  • 我不是想找出一种方法来处理段错误,问题是关于堆栈大小。
  • @DejanJovanović ... 正是由这个pmap 调用打印出来的。
  • 显示 pmap 的输出将显示整个进程的地址空间。地址空间映射中的信息将显示诸如可能的地址空间随机化之类的内容,可以为您提供答案。
【解决方案2】:

我已将/proc/self/maps 解析器添加到您的程序中(与@AndrewHenle 建议的方法相同,但我在程序开始时执行此操作,并且不调用pmap):

char* get_stack_bounds() {
    FILE* maps = fopen("/proc/self/maps", "r");
    static char line[256];

    while(!feof(maps)) {
        fgets(line, 255, maps);
        if(strstr(line, "[stack]")) {
            char* space = strchr(line, ' ');
            *space = '\0';
            fclose(maps);
            return line;
        }
    }

    fclose(maps);
    return NULL;
}

unsigned long get_stack_right() {
    char* bounds = get_stack_bounds();
    bounds = strchr(bounds, '-') + 1;
    return strtol(bounds, NULL, 16);
}

并在main()开头转储一些信息:

printf("&result: %p delta: %ld\n", &result, 
     get_stack_right() - ((unsigned long) &result));

以下是一些结果:

> ./a.out 104747
&result: 0x7fff3347c7f8 delta: 6152
0
> ./a.out 174580
&result: 0x7fffe43c9b38 delta: 5320
0
> ./a.out 174580
&result: 0x7fff26ad2b28 delta: 9432
Segmentation fault (core dumped)
> ./a.out 174580
&result: 0x7fff145aa5a8 delta: 6744
0
> ./a.out 174580
&result: 0x7fff74fff0b8 delta: 12104
Segmentation fault (core dumped)

我认为delta(即result地址与堆栈基地址之间的差异)与分段错误之间的相关性是显而易见的。


您应该注意main() 不是程序中运行的第一个函数,实际入口点将是来自 crt1.o(或其他)的_start(),因此初始堆栈大小可能不同。

实际问题是Address space layout randomization 。 以下是来自fs/binfmt_elf_fdpic.c 关于其用法的评论:

/* In some cases (e.g. Hyper-Threading), we want to avoid L1 evictions
 * by the processes running on the same package. One thing we can do is
 * to shuffle the initial stack for them, so we give the architecture
 * an opportunity to do so here.
 */
sp = arch_align_stack(bprm->p);

这是arch_align_stack() 在 x86 上的实现:

unsigned long arch_align_stack(unsigned long sp)
{
    if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
        sp -= get_random_int() % 8192;
    return sp & ~0xf;
}

【讨论】:

  • 这很有用!知道非确定性增量可能来自哪里吗?这是错误还是功能?
猜你喜欢
  • 1970-01-01
  • 2022-06-19
  • 1970-01-01
  • 2013-06-26
  • 1970-01-01
  • 2016-10-15
  • 2014-06-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多