繁体   English   中英

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

[英]Is it possible to predict a stack overflow in C on Linux?

某些条件可能导致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);
}

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

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;
}

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

为什么不赞成alloca?

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

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

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

/约翰

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

我有一些问题:

  1. 为什么,为什么,你需要在堆栈上大的东西? 大多数系统的默认大小是8M,那还是太小了?
  2. 如果函数调用alloca()阻塞,将通过mlock()/ mlockall()保护相同数量的堆保证接近相同的访问性能(即“不要交换我,兄弟!”)随着时间的推移? 如果你使用更积极的'rt'调度程序,建议你调用它们。

问题很有意思,但引起了人们的注意。 它在我的方形钉 - 圆孔 - o米上抬起针。

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

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

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

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

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

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

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

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

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

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

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

即使这不是你问题的直接答案,我希望你知道valgrind的存在 - 在Linux上运行时检测这些问题的一个很棒的工具。

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

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

void *closeToBase;

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

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

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

抱歉,如果这说明显而易见,但你可以通过尝试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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM