使用setrlimit()设置堆栈大小并引发堆栈溢出/段错误。

14
在下面的示例中,我尝试将堆栈大小设置为1kb。
为什么现在可以在foo()中分配大小为8kb的int数组,放在栈上?
#include <stdio.h>
#include <sys/resource.h>

void foo(void);

int main() {
 struct rlimit lim = {1024, 1024};

 if (setrlimit(RLIMIT_STACK, &lim) == -1)
  return 1;

 foo();

 return 0;
}

void foo() {
 unsigned ints[2048];

 printf("foo: %u\n", ints[2047]=42);
}

谢谢,我现在上瘾于找出为什么man(2)中的setrlimit不像广告中那样工作。幸运的是,gcc允许您指定堆栈大小 :) - Tim Post
一个问题被收藏的次数比它被点赞的次数更多——这很有趣。 - Pascal Cuoq
2个回答

9

这个限制立即设置,但只有在尝试分配新堆栈或尝试增加现有堆栈时才进行检查。在内核源代码中搜索RLIMIT_STACK (或使用LXR标识符搜索) 可以得知。

显然,堆栈的初始大小是为文件名+环境变量字符串+参数字符串所需的大小加上一些额外的页面而分配的,这些页面在setup_arg_pages上分配(在2.6.33版本中为20页1,2,在2.6.34版本中为128 KB 3)。

总之:

initial stack size = MIN(size for filename + arg strings + env strings + extra pages, MAX(size for filename + arg strings + env strings, RLIMIT_STACK))

where

size for filename + arg strings + env strings <= MAX(ARG_MAX(32 pages), RLIMIT_STACK/4)

此外,带有Ingo Molnar的exec-shield补丁(Fedora,Ubuntu等)的内核具有额外的EXEC_STACK_BIAS“(2MB更多以覆盖随机化效应。)”,请参见对acct_stack_growth()中新函数over_stack_limit()的调用([Ubuntu1][Ubuntu2][Ubuntu3])。我编辑了原始程序以显示此内容:
#include <stdio.h>
#include <sys/resource.h>

void foo(void);

int main(int argc, char *argv[]) {
        struct rlimit lim = {1, 1};


        if (argc > 1 && argv[1][0] == '-' && argv[1][8]=='l') {
                printf("limiting stack size\n");
                if (setrlimit(RLIMIT_STACK, &lim) == -1) {
                        printf("rlimit failed\n");
                        return 1;
                }
        }

        foo();

        return 0;
}

void foo() {
        unsigned ints[32768];

        printf("foo: %u\n", ints[2047]=42);
}

这导致:
$./rl
foo: 42
$./rl -l
limiting stack size
Segmentation fault
$  

1
不,实际上我能够扩展现有的堆栈。现在我就像一只不放弃这个问题的骨头的狗。 - Tim Post
再次出现相同的行为,我们无法重现setrlimit()在设置2k堆栈限制时返回成功的问题 :) - Tim Post
@Tim Post:你能试试我的修改后的测试吗? - ninjalj
我必须使用值unsigned ints[522100];在我的系统上生成“分段错误”。但是:它不会在每次运行时发生-看起来是随机的。 - tur1ng
1
尝试在Stack Clash修复之后设置rlimit_stack可能会导致失败或相关问题。还请参阅Red Hat Issue 1463241 - jww
显示剩余7条评论

4
我认为setrlimit移动"资源指针",但在执行新的程序副本之前不会应用新的限制。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>

void foo(int chk) {
  unsigned ints[2048];
  ints[2047] = 42;
  printf("foo %d: %u\n", chk, ints[2047]);
}

int main(int argc, char **argv) {
  char *newarg[] = { "argv[0]", "one", "two" };
  char *newenv[] = { NULL };
  struct rlimit lim;

  newarg[0] = argv[0];
  getrlimit(RLIMIT_STACK, &lim);
  printf("lim: %d / %d\n", (int)lim.rlim_cur, (int)lim.rlim_max);
  switch (argc) {
    case 1: /* first call from command line */
      lim.rlim_cur = 65536;
      lim.rlim_max = 65536;
      if (setrlimit(RLIMIT_STACK, &lim) == -1) return EXIT_FAILURE;
      newarg[2] = NULL;
      foo(1);
      execve(argv[0], newarg, newenv);
      break;
    case 2: /* second call */
      lim.rlim_cur = 1024;
      lim.rlim_max = 1024;
      if (setrlimit(RLIMIT_STACK, &lim) == -1) return EXIT_FAILURE;
      foo(2);
      execve(argv[0], newarg, newenv);
      break;
    default: /* third call */
      foo(3);
      break;
  }
  return 0;
}

以下是翻译内容:

并进行一次测试:

$ ./a.out 
lim: 8388608 / -1
foo 1: 42
lim: 65536 / 65536
foo 2: 42
被终止

为什么在打印极限之前(在调用foo之前)进程会被杀死,我不知道。


我怀疑这个问题类似,所以尝试使用 fork() 进行测试,但没有任何区别。我不明白为什么 setrlimit() 只影响通过 exec 生成的进程而不是父进程,但事实似乎确实如此。 - Tim Post
@pmg,你使用的内核版本/操作系统特殊配置是什么?我认为我们可能在谈论一个不断变化的目标 :) 我也无法在2.6.31(Ubuntu)上重现你的结果。 - Tim Post
@pmg:实际上,第三次运行可能会在创建auxv数组、env指针、argv指针和argc时崩溃。 - ninjalj
@ninjalj:通常情况下,程序在第三次运行时(在主函数的printf前)会崩溃并显示“已杀死”消息;但我也遇到过几次在主函数的printf前后出现“段错误”而导致程序崩溃的情况。 - pmg
在内核版本为2.6.18的CentOS 5.8上,我使用RLIMIT_AS,因为似乎RLIMIT_DATA没有任何效果。 - Daniel
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接