如何正确设置sigaltstack?(关于it技术的问题)

5

我看过至少三种设置 sigaltstack() 替代栈的方法,我想知道哪一种是最佳方法:

方法一

stack_t sigstk;
sigstk.ss_size = 0;
sigstk.ss_flags = 0;
sigstk.ss_sp = mmap (NULL, SIGSTKSZ, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (sigstk.ss_sp != MAP_FAILED) {
    sigstk.ss_size = SIGSTKSZ;
    if (sigaltstack (&sigstk, 0) < 0) {
        sigstk.ss_size = 0;
        printf ("sigaltstack errno=%d\n", errno);
    }
} else {
    printf ("malloc (SIGSTKSZ) failed!\n");
}

方法二(我们已经使用了一段时间,但在泄漏检测(leaks命令)中分配的内存会出现)

stack_t sigstk;
sigstk.ss_size = 0;
sigstk.ss_flags = 0;
sigstk.ss_sp = malloc (SIGSTKSZ);
if (sigstk.ss_sp != NULL) {
    sigstk.ss_size = SIGSTKSZ;
    if (sigaltstack (&sigstk, 0) < 0) {
        sigstk.ss_size = 0;
        free (sigstk.ss_sp);
        printf ("sigaltstack errno=%d\n", errno);
    }
} else {
    printf ("malloc (SIGSTKSZ) failed!\n");
}

第三种方法

stack_t sigstk;
static char ssp[SIGSTKSZ];
sigstk.ss_size = SIGSTKSZ;
sigstk.ss_flags = 0;
sigstk.ss_sp = ssp;
sigstk.ss_size = SIGSTKSZ;
if (sigaltstack (&sigstk, 0) < 0) {
    sigstk.ss_size = 0;
    free (sigstk.ss_sp);
    printf ("sigaltstack errno=%d\n", errno);
}

感谢Ákos(Mac OS X 10.8.2)的帮助。
3个回答

6

方法一是最好的。原因是位置。假设您使用第二种方法,您的代码流程如下:

void *blah = malloc (...)
...
stack_t sigstk;
sigstk.ss_size = 0;
sigstk.ss_flags = 0;
sigstk.ss_sp = malloc (SIGSTKSZ);

现在如果您在信号处理程序中耗尽了堆栈空间会发生什么?您的堆栈将向下增长并干扰 blah 指向的内存。如果您在某个地方进行浅层递归,这很容易发生。问题#3也有同样的问题。
相反,请使用mmap,因为它从不同的池分配,远离数据堆,并且设置警戒页面是一个好主意:
char* mem = mmap (NULL, 
                  SIGSTKSZ + 2*getpagesize(), 
                  PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 
                  -1, 0);
mprotect(mem, getpagesize(), PROT_NONE);
mprotect(mem + getpagesize() + SIGSTKSZ, getpagesize(), PROT_NONE);
sigstk.ss_sp = mem + getpagesize();
...

现在如果发生堆栈溢出,你将会收到一个 SIGSEGV 信号,这比随机内存覆盖容易调试多了。 :)
第二个原因被计为泄漏的原因可能是误报。你正在使用的泄漏检测工具很可能用自己的变量覆盖了 malloc 库函数,这也是使用 mmap 而不是 malloc 的另一个原因。

1
我不太熟悉sigaltstack(),但我一直在查阅man页面。你的3种方法中,ss_sp结构成员的空间分配方式似乎有所不同:使用mmap()、使用传统的malloc()或从堆栈中分配空间。
如果我正确理解了系统,那么绝对不要选择第三种方法:从堆栈中分配。退出函数后,堆栈空间将立即被回收并重新分配(即改变),这将破坏sigaltstack()的功能。
因此,我建议使用传统的malloc()。如果您喜欢语法,可以选择mmap()。据我所知,向mmap()传递NULL地址等效于malloc()

2
就我所理解的,方法#3中的“static”存储类说明符将不允许变量超出其作用域。 - Ákos
啊,我没有注意到静态限定符。谢谢你的澄清。在这种情况下,所有方法都似乎更或多或少有效,取决于个人喜好。或者你对某种方法有特定的问题吗? - Multimedia Mike
你是如何诊断泄漏的?通过Valgrind或其他工具吗?泄漏是在什么时候报告的?在程序终止时吗?我会期望是这样的。看起来正确的清理方法是在建立新的信号栈上下文时保存旧的指针,退出前将旧的指针与新的指针交换,然后释放(free())原始malloc()指针。 - Multimedia Mike
泄漏检测器('leaks'命令)在卸载我们的最后一个模块时被调用;我们将其实现为全局静态类实例(在模块卸载时被销毁)。我不太想交换备用堆栈;这可能会导致不必要的副作用。mmap解决方案在泄漏检测中没有显示,所以我们现在正在使用它。 - Ákos
似乎在全局范围内声明一块字节块会起到同样的作用(而不是在函数中本地声明),并且不会被视为泄漏。 - Multimedia Mike
显示剩余2条评论

0

你主要关注的似乎是在malloc情况下的泄漏检查 - 如果你有一个详尽的泄漏检查器(需要在退出之前释放使用malloc分配的所有内容)。如果是这种情况,你可以通过记住旧的sigstack来恢复它来清理事情。

stack_t sigstk, orig_sigstk;

setup_alt_sigstack() {
    sigstk.ss_size = 0;
    sigstk.ss_flags = 0;
    sigstk.ss_sp = malloc (SIGSTKSZ);
    if (sigstk.ss_sp != NULL) {
        sigstk.ss_size = SIGSTKSZ;
        if (sigaltstack (&sigstk, &orig_sigstack) < 0) {
            sigstk.ss_size = 0;
            free (sigstk.ss_sp);
            printf ("sigaltstack errno=%d\n", errno);
        }
    } else {
        printf ("malloc (SIGSTKSZ) failed!\n");
    }

restore_orig_sigstack() {
    if (sigaltstack (&orig_sigstack, 0) < 0) {
        printf ("sigaltstack errno=%d\n", errno);
    }
    sigstk.ss_size = 0;
    free (sigstk.ss_sp);
}

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