简体   繁体   English

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

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

There are certain conditions that can cause stack overflows on an x86 Linux system: 某些条件可能导致x86 Linux系统上的堆栈溢出:

  • struct my_big_object[HUGE_NUMBER] on the stack. 在堆栈上struct my_big_object[HUGE_NUMBER] Walking through it eventually causes SIGSEGV . 走过它最终导致SIGSEGV
  • The alloca() routine (like malloc() , but uses the stack, automatically frees itself, and also blows up with SIGSEGV if it's too big). alloca()例程(如malloc() ,但使用堆栈,自动释放自己,如果它太大,也会SIGSEGV )。 Update: alloca() isn't formally deprecated as I originally stated; 更新:alloca()未按我原先的说法正式弃用; it is merely discouraged . 它只是气馁

Is there a way to programmatically detect if the local stack is big enough for a given object? 有没有办法以编程方式检测本地堆栈是否足够大于给定对象? I know the stack size is adjustable via ulimit , so I have hope there is a way (however non-portable it may be). 我知道堆栈大小可以通过ulimit调整,所以我希望有一种方法(但可能是非便携式)。 Ideally, I would like to be able to do something like this: 理想情况下,我希望能够做到这样的事情:

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

You can determine the stack space the process has available by finding the size of a process' stack space and then subtracting the amount used. 您可以通过查找进程堆栈空间的大小然后减去使用的数量来确定进程可用的堆栈空间。

ulimit -s

shows the stack size on a linux system. 显示了linux系统上的堆栈大小。 For a programmatic approach, check out getrlimit() . 对于程序化方法,请查看getrlimit() Then, to determine the current stack depth, subtract a pointer to the top of the stack from one to the bottom. 然后,要确定当前堆栈深度,请从一个到底部减去指向堆栈顶部的指针。 For example (code untested): 例如(代码未经测试):

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

The deprecated alloca() routine (like malloc(), but uses the stack, automatically frees itself, and also blows up with SIGSEGV if it's too big). 不推荐使用的alloca()例程(比如malloc(),但是使用堆栈,自动释放自己,如果它太大,也会使用SIGSEGV)。

Why is alloca deprecated? 为什么不赞成alloca?

Anyhow, how much faster in your case is alloca vs malloc? 无论如何,在你的情况下,alloca和malloc的速度有多快? (Is it worth it?) (这值得么?)

And don't you get null back from alloca if there is not enought space left? 如果没有剩余的空间,你不会从alloca返回null吗? (the same way as malloc?) (和malloc一样?)

And when your code crash, where does it crash? 当你的代码崩溃时,它会在哪里崩溃? is it in alloca or is in doStuff()? 它是在alloca中还是在doStuff()中?

/Johan /约翰

alloca() is going to return NULL on failure, I believe the behavior of alloca(0) is undefined and platform variant. alloca()将在失败时返回NULL,我相信alloca(0)的行为是未定义的和平台变体。 If you check for that prior to do_something(), you should never be hit with a SEGV. 如果你在do_something()之前检查它,你永远不应该用SEGV命中。

I have a couple of questions: 我有一些问题:

  1. Why, oh why, do you need something that big on the stack? 为什么,为什么,你需要在堆栈上大的东西? The default size on most systems is 8M, that's still too small? 大多数系统的默认大小是8M,那还是太小了?
  2. If the function calling alloca() blocks, would protecting the same amount of heap via mlock() / mlockall() guarantee close to the same access performance (ie "Don't swap me, bro!") over time? 如果函数调用alloca()阻塞,将通过mlock()/ mlockall()保护相同数量的堆保证接近相同的访问性能(即“不要交换我,兄弟!”)随着时间的推移? If your using a more aggressive 'rt' scheduler, its recommended to call those anyway. 如果你使用更积极的'rt'调度程序,建议你调用它们。

The question is interesting but raises an eyebrow. 问题很有意思,但引起了人们的注意。 It raises the needle on my square-peg-round-hole-o-meter. 它在我的方形钉 - 圆孔 - o米上抬起针。

The alloca function is not deprecated. 推荐使用alloca函数。 However, it is not in POSIX and it is also machine- and compiler-dependent. 但是,它不在POSIX中,它还依赖于机器和编译器。 The Linux man-page for alloca notes that "for certain applications, its use can improve efficiency compared to the use of malloc, and in certain cases it can also simplify memory deallocation in applications that use longjmp() or siglongjmp(). Otherwise, its use is discouraged." alloca的Linux手册页指出“对于某些应用程序,与使用malloc相比,它的使用可以提高效率,在某些情况下,它还可以简化使用longjmp()或siglongjmp()的应用程序中的内存释放。否则,不鼓励使用它。“

The manpage also says that "there is no error indication if the stack frame cannot be extended. However, after a failed allocation, the program is likely to receive a SIGSEGV." 该联机帮助页还说“如果无法扩展堆栈帧,则没有错误指示。但是,在分配失败后,程序可能会收到SIGSEGV。”

The performance of malloc was actually mentioned on the Stackoverflow Podcast #36 . 实际上在Stackoverflow播客#36上提到了malloc的性能。

(I know this is not a proper answer to your question, but I thought it might be useful anyway.) (我知道这不是你问题的正确答案,但我认为它可能有用。)

Not sure if this applies on Linux, but on Windows it's possible to run into access violations with large stack allocations even if they succeed! 不确定这是否适用于Linux,但在Windows上, 即使成功,也可能会遇到大量堆栈分配的访问冲突

This is because by default, Windows' VMM only actually marks the top few (not sure how many exactly) 4096-byte pages of stack RAM as pageable (ie backed by the pagefile), since it believes that stack accesses will generally march downwards from the top; 这是因为默认情况下,Windows的VMM实际上只将前几个(不确定多少)4096字节的堆栈RAM页面标记为可分页(即由页面文件支持),因为它认为堆栈访问通常会从顶端; as accesses get closer and closer to the current "boundary", lower and lower pages are marked as pageable. 随着访问越来越接近当前的“边界”,下页和下页被标记为可分页。 But this means that an early memory read/write far below the top of the stack will trigger an access violation as that memory is not actually allocated yet! 但这意味着远低于堆栈顶部的早期内存读/写将触发访问冲突,因为该内存尚未实际分配!

You don't say much about why you want to allocate on the stack, but if it is the stack memory model which is appealing, you could implement stack allocation on the heap as well. 您没有详细说明为什么要在堆栈上分配,但如果它是吸引人的堆栈内存模型,您也可以在堆上实现堆栈分配。 Allocate a large chunk of memory at the beginning of the program and keep a stack of pointers to this which would correspond to frames on the regular stack. 在程序开头分配一大块内存,并保留一堆指针,这些指针对应于常规堆栈上的帧。 You just need to remember to pop your private stack pointer when the function returns. 您只需要记住在函数返回时弹出私有堆栈指针。

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

You can use GNU libsigsegv to handle a page fault, including cases where a stack overflow occurs (from its website): 您可以使用GNU libsigsegv处理页面错误,包括发生堆栈溢出的情况(来自其网站):

In some applications, the stack overflow handler performs some cleanup or notifies the user and then immediately terminates the application. 在某些应用程序中,堆栈溢出处理程序执行一些清理或通知用户,然后立即终止应用程序。 In other applications, the stack overflow handler longjmps back to a central point in the application. 在其他应用程序中,堆栈溢出处理程序会长回到应用程序的中心点。 This library supports both uses. 该库支持这两种用途。 In the second case, the handler must ensure to restore the normal signal mask (because many signals are blocked while the handler is executed), and must also call sigsegv_leave_handler() to transfer control; 在第二种情况下,处理程序必须确保恢复正常的信号掩码(因为在执行处理程序时许多信号被阻塞),并且还必须调用sigsegv_leave_handler()来传输控制; then only it can longjmp away. 然后只有它可以长途跋涉。

The end of the stack area is determined dynamically by the OS. 堆栈区域的末尾由OS动态确定。 Although you can find the "static" bounds of the stack by looking at the virtual memory areas (VMAs) in a highly OS dependent way (see the stackvma* files in libsigsegv/src/ ), you will additionally have to consider 虽然您可以通过以高度OS依赖的方式查看虚拟内存区域(VMA)来查找堆栈的“静态”边界(请参阅libsigsegv / src /中的stackvma *文件),您还需要考虑

Even if this isn't a direct answer to your question, I hope you're aware of the existence of valgrind - a wonderful tool for detecting such problems in runtime, on Linux. 即使这不是你问题的直接答案,我希望你知道valgrind的存在 - 在Linux上运行时检测这些问题的一个很棒的工具。

Regarding the stack problem, you can attempt allocating objects dynamically from a fixed pool that detects these overflows. 关于堆栈问题,您可以尝试从检测到这些溢出的固定池动态分配对象。 With a simple macro-wizardry you can make this run at debug time, with real code running at release time, and thus know (at least for the scenarios you're executing) that you're not taking too much. 使用简单的宏模板,您可以在调试时运行,实时代码在发布时运行,因此知道(至少对于您正在执行的方案)您没有花太多时间。 Here's more info and a link to a sample implementation. 这里有更多信息和示例实现的链接

There isn't a nice way I can think of. 我无法想到这么好的方式。 Maybe it is possible by using getrlimit() (suggested before) and some pointer arithmetic? 也许可以通过使用getrlimit()(之前建议)和一些指针算法? But first ask yourself if you really want this. 但首先要问问自己,你是否真的想要这个。

void *closeToBase;

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

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

Personally, I'd not do this. 就个人而言,我不会这样做。 Allocate big things on the heap, the stack was not meant for it. 在堆上分配大量的东西,堆栈不适合它。

Apologies if this is stating the obvious, but you could easily write a function to test for a specific stack allocation size by just trying the alloca (of that size) and catching a stack overflow exception. 抱歉,如果这说明显而易见,但你可以通过尝试alloca(那个大小)并捕获堆栈溢出异常,轻松编写一个函数来测试特定的堆栈分配大小。 If you wanted you could put it into a function, with some pre-determined math for the function stack overhead. 如果你想要,你可以将它放入一个函数中,并使用一些预先确定的函数堆栈开销数学。 Eg: 例如:

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