是否有可能在Linux上预测C中的堆栈溢出?

时间:2009-01-09 06:08:13

标签: c linux x86 alloca

某些条件可能导致x86 Linux系统上的堆栈溢出:

    堆栈上的
  • struct my_big_object[HUGE_NUMBER]。走过它最终导致SIGSEGV
  • alloca()例程(如malloc(),但使用堆栈,自动释放自身,如果它太大,也会被SIGSEGV炸毁。 更新:alloca()未按我原先的说法正式弃用;它只是气馁

有没有办法以编程方式检测本地堆栈是否足够大于给定对象?我知道堆栈大小可以通过ulimit调整,所以我希望有一种方法(但可能是不可移植的)。理想情况下,我希望能够做到这样的事情:

int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
    char *foo = alloca(object_size);
    do_stuff(foo);
}
else
{
    char *foo = malloc(object_size);
    do_stuff(foo);
    free(foo);
}

12 个答案:

答案 0 :(得分:5)

您可以通过查找进程堆栈空间的大小然后减去使用的数量来确定进程可用的堆栈空间。

ulimit -s

显示了linux系统上的堆栈大小。对于程序化方法,请查看getrlimit()。然后,要确定当前堆栈深度,请从一个到底部减去指向堆栈顶部的指针。例如(代码未经测试):

unsigned char *bottom_of_stack_ptr;

void call_function(int argc, char *argv) {
    unsigned char top_of_stack;
    unsigned int depth = (&top_of_stack > bottom_of_stack_ptr) ? 
        &top_of_stack-bottom_of_stack_ptr : 
        bottom_of_stack_ptr-&top_of_stack;

    if( depth+100 < PROGRAMMATICALLY_DETERMINED_STACK_SIZE ) {
        ...
    }
}

int main(int argc, char *argv) {
    unsigned char bottom_of_stack;
    bottom_of_stack_ptr = &bottom_of_stack;
    my_function();
    return 0;
}

答案 1 :(得分:1)

  

不推荐使用的alloca()例程(如malloc(),但使用堆栈,   自动释放自己,如果它太大,也会被SIGSEGV炸掉。

为什么不赞成使用alloca?

无论如何,在你的情况下,alloca和malloc的速度有多快? (这值得吗?)

如果没有剩余的空间,你不会从alloca返回null吗? (和malloc一样?)

当你的代码崩溃时,它会在哪里崩溃?是在alloca中还是在doStuff()中?

/约翰

答案 2 :(得分:1)

alloca()将在失败时返回NULL,我相信alloca(0)的行为是未定义的和平台变体。如果你在do_something()之前检查它,你就不应该用SEGV命中。

我有几个问题:

  1. 为什么,为什么,你需要一些大的东西吗?大多数系统的默认大小是8M,这仍然太小了?
  2. 如果调用alloca()的函数阻塞,将通过mlock()/ mlockall()保护相同数量的堆保证接近相同的访问性能(即“不要交换我,兄弟!”)随着时间的推移?如果您使用更具侵略性的“rt”调度程序,建议您再调用它们。
  3. 这个问题很有意思,但引起了人们的注意。它在我的方形钉 - 圆孔 - o米上抬起针。

答案 3 :(得分:1)

alloca函数已弃用。但是,它不在POSIX中,它还依赖于机器和编译器。 alloca的Linux手册页指出“对于某些应用程序,与使用malloc相比,它的使用可以提高效率,在某些情况下,它还可以简化使用longjmp()或siglongjmp()的应用程序中的内存释放。否则,不鼓励使用它。“

该联机帮助页还说“如果无法扩展堆栈帧,则没有错误指示。但是,在分配失败后,程序可能会收到SIGSEGV。”

实际上在Stackoverflow Podcast #36上提到了malloc的性能。

(我知道这不是你问题的正确答案,但我认为它可能有用。)

答案 4 :(得分:1)

不确定这是否适用于Linux,但在Windows上,即使成功,也可能会遇到大量堆栈分配的访问冲突!

这是因为默认情况下,Windows'VMM实际上只将前几个(不确定多少)4096字节的堆栈RAM页面标记为可分页(即由页面文件支持),因为它认为堆栈访问通常是从顶部向下行进;随着访问越来越接近当前的“边界”,下页和下页被标记为可分页。但这意味着远低于堆栈顶部的早期内存读/写将触发访问冲突,因为该内存实际上尚未分配!

答案 5 :(得分:1)

您没有详细说明为什么要在堆栈上进行分配,但如果它是具有吸引力的堆栈内存模型,您也可以在堆上实现堆栈分配。在程序开头分配一大块内存,并保留一堆指针,这些指针对应于常规堆栈上的帧。您只需要记住在函数返回时弹出私有堆栈指针。

答案 6 :(得分:1)

一些编译器,例如Open Watcom C/C++,支持stackavail()函数,可以让你做到这一点

答案 7 :(得分:1)

您可以使用GNU libsigsegv 处理页面错误,包括发生堆栈溢出的情况(来自其网站):

  

在某些应用程序中,堆栈溢出处理程序执行一些清理或通知用户,然后立即终止应用程序。在其他应用程序中,堆栈溢出处理程序会长回到应用程序的中心点。该库支持这两种用途。在第二种情况下,处理程序必须确保恢复正常的信号掩码(因为在执行处理程序时许多信号被阻塞),并且还必须调用sigsegv_leave_handler()来传输控制;然后只有它可以长途跋涉。

答案 8 :(得分:0)

即使这不是您问题的直接答案,我希望您知道valgrind的存在 - 这是一个在Linux上运行时检测此类问题的绝佳工具。

关于堆栈问题,您可以尝试从检测到这些溢出的固定池动态分配对象。使用简单的宏模板,您可以在调试时运行,实时代码在发布时运行,因此知道(至少对于您正在执行的方案)您没有花太多时间。 Here's more info and a link示例实现。

答案 9 :(得分:0)

我无法想到这么好的方式。也许可以通过使用getrlimit()(之前建议)和一些指针算法?但首先要问问自己,你是否真的想要这个。

void *closeToBase;

main () {
  int closeToBase;
  stackTop = &closeToBase;
}

int stackHasRoomFor(int bytes) {
  int currentTop;
  return getrlimit(...) - (¤tTop  - closeToBase) > bytes + SomeExtra;
}

就个人而言,我不会这样做。在堆上分配大量的东西,堆栈不适合它。

答案 10 :(得分:0)

堆栈区域的末尾由OS动态确定。虽然您可以通过以高度依赖OS的方式查看虚拟内存区域(VMA)来查找堆栈的“静态”边界(请参阅libsigsegv/src/中的stackvma *文件),但您还需要考虑

答案 11 :(得分:-2)

道歉,如果这说明显而易见,但你可以通过尝试alloca(那个大小)并捕获堆栈溢出异常,轻松地编写一个函数来测试特定的堆栈分配大小。如果你想要,你可以将它放入一个函数中,并使用一些预先确定的函数堆栈开销数学。例如:

bool CanFitOnStack( size_t num_bytes )
{
    int stack_offset_for_function = 4; // <- Determine this
    try
    {
        alloca( num_bytes - stack_offset_for_function );
    }
    catch ( ... )
    {
        return false;
    }

    return true;
}