曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 7798|回复: 0

[信号] sigaltstack() -- 替换信号处理函数栈

[复制链接]

4632

主题

5574

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
32811
发表于 2012-8-8 15:02:40 | 显示全部楼层 |阅读模式
sigaltstack() 函数原型如下:
[C++] 纯文本查看 复制代码
#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oss);

该函数设计内存方面的知识,可以参考下图:

一般情况下,信号处理函数被调用时,内核会在进程的栈上为其创建一个栈帧。但是这里就会有一个问题,如果栈的增长到达了栈的资源限制值(RLIMIT_STACK,使用 ulimit 命令可以查看,一般为 8M),或是栈已经长得太大(没有 RLIMIT_STACK 的限制),以致到达了映射内存(mapped memory)边界(见上图),那么此时信号处理函数就没法得到栈帧的分配。

在一个进程的栈增长超过到最大的允许值时,内核会向该进程发送一个 SIGSEGV 信号(段错误)。如果我们在该进程里已经设置了一个捕捉 SIGSEGV 信号的处理函数,,那么此时由于进程的栈已经耗尽,因此该信号得不到处理,因此进程就会被结束掉( 这也就是 SIGSEGV 信号的默认处理方式)。

假如说,我们一定需要在这种极端的情况下处理 SIGSEGV 信号,那么还是有办法的,也就是使用 sigaltstack() 函数来实现,可用下面的步骤:

1. 分配一块内存区,当然是从堆中分配,这块内存区就称为“可替换信号栈”(alternate signal stack),顾名思义,我们就是希望将信号处理函数的栈挪到堆中,而不和进程共用一块栈区。

2. 使用 sigaltstack() 系统调用通知内核“可替换信号栈”已经建立。

3. 接着建立信号处理函数,此时需要对 sigaction() 函数的 sa_flags 成员设立 SA_ONSTACK 标志,该标志告诉内核信号处理函数的栈帧就在“可替换信号栈”上建立。

回到 sigaltstack() 函数,该函数的第 1 个参数 sigstack 是一个 stack_t 结构的指针,该结构存储了一个“可替换信号栈” 的位置及属性信息。第 2 个参数 old_sigstack 也是一个 stack_t 类型指针,它用来返回上一次建立的“可替换信号栈”的信息(如果有的话)。stack_t 结构类型定义如下:
[C++] 纯文本查看 复制代码
typedef struct {
               void  *ss_sp;     /* Base address of stack */
               int    ss_flags;  /* Flags */
               size_t ss_size;   /* Number of bytes in stack */
           } stack_t;

这两个参数都可以设置为 NULL。比如说,如果只是想找出已有的“可替换信号栈”而不需要去改变它,那么可以将 sigstack 这个参数指定为 NULL 。

stack_t 结构中的 ss_sp 成员指定了“可替换信号栈”的起始地址(内核在分配这个地址时会根据不同的硬件平台而自动对齐的,这个无需担心),ss_size 成员指出了该栈的大小。

一般的,“可替换信号栈” 既可以动态分配,也可以静态分配。通常,我们可以利用 SIGSTKSZ 这个常数(一般为 8192)指定该栈的大小,另一个常数 MINSIGSTKSZ 则表示该栈最小分配需求(该值一般为 2048)。

需要注意的是,内核并不会再重新划分“可替换信号栈”的大小。如果所分配的栈溢出了,那么结果将是错乱的。但是这种情况通常也无需担心,因为我们一般只用“可替换信号栈”来处理一些标准栈(主进程所用的栈)溢出这种特殊情况,因此在这种情形下,我们往往只是在 SIGSEGV 信号处理函数里做些清理,或是结束进程,抑或是使用非局部跳转用以解除标准栈溢出的问题,因此在“可替换信号栈”中也就只会分配一个或者少数几个栈帧,故而不用太担心会在此间造成溢出问题。

stack_t 结构中的 ss_flags 成员可以使用下面两个值:

SS_ONSTACK
如果在从当前建立的“可替换信号栈”(old_sigstack)中获取相关信息时设置该标志,那么表示进程当前正在“可替换信号栈”中执行,如果此时试图去建立一个新的“可替换信号栈”,那么会遇到 EPERM (禁止该动作) 的错误。

SS_DISABLE
如果在返回的 old_sigstack 中看到此标志,那么说明当前没有已建立的“可替换信号栈”。如果在 sigstack 中指定该标志,那么当前禁止建立“可替换信号栈”。

下面测试代码演示了 sigaltstack() 函数的使用(运行之前需要修改一下资源限制:ulimit -s unlimited):
[C++] 纯文本查看 复制代码
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

static void sigseg_handler(int sig)
{
    int x;
    printf ("Caught signal %d (%s)\n", sig, strsignal(sig));
    printf ("Top of handler stack near %10p\n", (void *)&x);
    fflush(NULL);

    _exit(EXIT_FAILURE);    //在段错误后不能从此处返回
}

static void overflow_stack(int call_num)
{
    char a[100000];        //撑大栈帧

    printf ("Call %4d - top of stack near %10p\n", call_num, &a[0]);
    
    overflow_stack(call_num + 1);
}

int main(int argc, char *argv[])
{
    stack_t sigstack;
    struct sigaction sa;
    int j;
    
    printf ("Top of standard stack is near %10p\n", (void *)&j);

    //分配可变栈并通知内核
    
    sigstack.ss_sp = malloc(SIGSTKSZ);
    if (sigstack.ss_sp == NULL) {
       perror ("malloc");
       exit (EXIT_FAILURE);
    }
    sigstack.ss_size = SIGSTKSZ;
    sigstack.ss_flags = 0;
    if (sigaltstack(&sigstack, NULL) == -1) {
       perror ("sigaltstack");
       exit (EXIT_FAILURE);
    }
    printf ("Alternate stack is at    %10p-%p\n", sigstack.ss_sp, (char *)sbrk(0) - 1);

    sa.sa_handler = sigseg_handler;    //建立SIGSEGV  的处理函数
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;    //处理函数使用可变栈
    if (sigaction(SIGSEGV, &sa, NULL) == -1) {
       perror("sigaction");
       exit(EXIT_FAILURE);
    }

    overflow_stack(1);
}

该程序输出的结果一般会有万条以上,因此可以将结果重定向到一个文本里保存。该程序主要通过递归执行一个 overflow_stack() 函数(该函数通过局部变量 a[100000] 极大耗费栈帧)了标准栈耗尽并被内核发出 SIGSEGV  信号 --- 庆幸的是,我们使用了“可替换信号栈”技术,因此避免了 SIGSEGV 信号结束进程。比如在我这,可以对比第 1 行和倒数第 2 行输出的情况:
[beyes@beyes   signal]$ ./sigaltstack
Alternate stack is at     0x9bf9008-0x9c1bfff
... ...
Caught signal 11 (Segmentation fault)
Top of handler stack near  0x9bfad1c
可以看到,信号处理函数的栈位于堆分配的内存空间中。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2019-4-22 00:55 , Processed in 0.054978 second(s), 24 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表