是否可以在 Linux 上预测 C 语言的堆栈溢出?

发布于 2024-07-11 16:31:49 字数 679 浏览 5 评论 0原文

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

There are certain conditions that can cause stack overflows on an x86 Linux system:

  • struct my_big_object[HUGE_NUMBER] on the stack. Walking through it eventually causes SIGSEGV.
  • The alloca() routine (like malloc(), but uses the stack, automatically frees itself, and also blows up with SIGSEGV if it's too big). Update: alloca() isn't formally deprecated as I originally stated; 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). 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);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

筑梦 2024-07-18 16:31:49

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

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

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. For a programmatic approach, check out 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;
}
鹊巢 2024-07-18 16:31:49

已弃用的 alloca() 例程(与 malloc() 类似,但使用堆栈,
自动释放自己,如果太大的话也会用 SIGSEGV 炸毁)。

为什么 alloca 被弃用?

无论如何,在你的情况下,alloca 与 malloc 相比要快多少? (值得吗?)

如果没有足够的空间,你不会从 alloca 中得到 null 吗?
(和 malloc 一样?)

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

/约翰

The deprecated alloca() routine (like malloc(), but uses the stack,
automatically frees itself, and also blows up with SIGSEGV if it's too big).

Why is alloca deprecated?

Anyhow, how much faster in your case is alloca vs malloc? (Is it worth it?)

And don't you get null back from alloca if there is not enought space left?
(the same way as malloc?)

And when your code crash, where does it crash? is it in alloca or is in doStuff()?

/Johan

悲凉≈ 2024-07-18 16:31:49

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

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

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!

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 (i.e. backed by the pagefile), since it believes that stack accesses will generally march downwards from the top; 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!

怂人 2024-07-18 16:31:49

alloca() 将在失败时返回 NULL,我相信 alloca(0) 的行为是未定义的并且是平台变体。 如果你在 do_something() 之前检查这一点,你就永远不会被 SEGV 击中。

我有几个问题:

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

这个问题很有趣,但引起了人们的注意。 它抬起了我的方钉圆孔仪表上的指针。

alloca() is going to return NULL on failure, I believe the behavior of alloca(0) is undefined and platform variant. If you check for that prior to do_something(), you should never be hit with a 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?
  2. If the function calling alloca() blocks, would protecting the same amount of heap via mlock() / mlockall() guarantee close to the same access performance (i.e. "Don't swap me, bro!") over time? If your using a more aggressive 'rt' scheduler, its recommended to call those anyway.

The question is interesting but raises an eyebrow. It raises the needle on my square-peg-round-hole-o-meter.

千柳 2024-07-18 16:31:49

您没有过多说明为什么要在堆栈上分配,但如果堆栈内存模型有吸引力,您也可以在堆上实现堆栈分配。 在程序开始时分配一大块内存,并保留指向该内存的堆栈,该堆栈与常规堆栈上的帧相对应。 您只需要记住在函数返回时弹出您的私有堆栈指针即可。

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.

柠栀 2024-07-18 16:31:49

一些编译器,例如 Open Watcom C/C++,支持 stackavail() 函数,可以让您执行以下操作正是如此

Several compilers, for example Open Watcom C/C++, support stackavail() function that lets you do exactly that

一枫情书 2024-07-18 16:31:49

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

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

You can use GNU libsigsegv to handle a page fault, including cases where a stack overflow occurs (from its website):

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; then only it can longjmp away.

写下不归期 2024-07-18 16:31:49

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

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

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

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

The alloca function is not deprecated. However, it is not in POSIX and it is also machine- and compiler-dependent. 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."

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."

The performance of malloc was actually mentioned on the Stackoverflow Podcast #36.

(I know this is not a proper answer to your question, but I thought it might be useful anyway.)

棒棒糖 2024-07-18 16:31:49

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

关于堆栈问题,您可以尝试从检测这些溢出的固定池中动态分配对象。 通过一个简单的宏向导,您可以使其在调试时运行,并在发布时运行真正的代码,从而知道(至少对于您正在执行的场景)您没有花费太多。 这里有更多信息和链接 示例实现。

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.

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.

城歌 2024-07-18 16:31:49

我想不出什么好办法。 也许可以通过使用 getrlimit() (之前建议)和一些指针算术? 但首先要问自己是否真的想要这个。

void *closeToBase;

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

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

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

There isn't a nice way I can think of. Maybe it is possible by using getrlimit() (suggested before) and some pointer arithmetic? But first ask yourself if you really want this.

void *closeToBase;

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

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

Personally, I'd not do this. Allocate big things on the heap, the stack was not meant for it.

音栖息无 2024-07-18 16:31:49

堆栈区域的末尾由操作系统动态确定。 尽管您可以通过以高度依赖于操作系统的方式查看虚拟内存区域 (VMA) 来找到堆栈的“静态”边界(请参阅 libsigsegv/src/),您还必须考虑

The end of the stack area is determined dynamically by the 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

烟火散人牵绊 2024-07-18 16:31:49

如果这是显而易见的情况,我深表歉意,但您可以轻松地编写一个函数来测试特定的堆栈分配大小,只需尝试分配(该大小)并捕获堆栈溢出异常。 如果您愿意,可以将其放入函数中,并使用一些预先确定的数学函数来计算函数堆栈开销。 例如:

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

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. 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文