Unix/Linux编程:在备选栈中处理信号

本文讲述了如何在进程栈溢出时,通过设置备选信号栈和SIGSEGV处理器函数,确保信号处理的正常执行。介绍了sigaltstack()函数的用法和相关参数,以及如何在Linux/x86-32系统中合理配置和管理备选信号栈。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在调用进程处理器函数时,内核通常会在进程栈中为其创建一栈。不过,如果进程对栈的扩展突破了对栈大小的限制时,这种做法就不太可行了。比如,栈的增长过大,以至于会触及到一片映射内存或者向上增长的堆,又或者栈的大小已经直逼RLIMIT_STACK资源限制,这些都会造成这种情况的发生。

当进程对栈的扩展试图突破其上限时,内核将为该进程产生 SIGSEGV信号号。不过,因为栈空间已然耗尽,内核也就无法为进程已经安装的SIGSEGV 处理器函数创建栈帧。结果是,处理器函数得不到调用,而进程也就终止了。

如果希望在这种情况下确保对 SIGSEGV 信号处理器函数的调用,就需要做如下工作

  • 分配一块被称为“备选信号栈”的内存区域,作为信号处理函数的栈帧
  • 调用sigaltstack() ,告知内核该备选信号栈的存在
  • 在创建信号处理函数时指定SA_ONSTACK 标志,也就是通知内核在备选栈上为处理器函数创建栈帧。

利用系统调用 sigaltstack(),既可以创建一个备选信号栈,也可以将已创建备选信号栈的相关信息返回。

NAME
       sigaltstack - set and/or get signal stack context

SYNOPSIS
       #include <signal.h>

       int sigaltstack(const stack_t *ss, stack_t *oss);

DESCRIPTION
	  ss 所指向的数据结构描述了新备选信号栈的位置及属性
	  
	  oss 指向的结构则用于返回上一备选信号栈的相关信息(如果存在)

	  两个参数之一均可为 NULL.例如,将参数 ss设为 NULL 可以发现现有备选信号栈,并且不用将其改变。

	  不为 NULL时,这些参数所指向的数据结构类型如下

		typedef struct {
               void  *ss_sp;     /* Base address of stack */
               int    ss_flags;  /* Flags */
               size_t ss_size;   /* Number of bytes in stack */
           } stack_t;

	  字段 ss_sp 和 ss_size 分别指定了备选信号栈的位置和大小.在实际使用信号栈时,内核会将 ss_sp
	  值自动对齐为与硬件架构相适宜的地址边界

	  ss_flags 可以包含如下值之一

		SS_ONSTACK 
		
			如果在获取已创建备选信号栈的当前信息时该标志已然置位,就表明进程正在备选信号栈上执行。当
			进程已经在备选信号栈上运行时,试图调用 sigaltstack()来创建一个新的备选信号栈将会产生一
			个错误(EPERM)。

		SS_DISABLE 

			在 old_sigstack 中返回,表示当前不存在已创建的备选信号栈。如果在 sigstack 中指定,则
			会禁用当前已创建的备选信号栈
		
		

备选信号栈通常既可以静态分配,也可以在堆上动态分配。SUSv3 规定将常量 SIGSTKSZ作为划分备选栈大小的典型值,而将 MINSSIGSTKSZ 作为调用信号处理器函数所需的最小值。在 Linux/x86-32 系统上,分别将这两个值定义为 8192 和 2048。

内核不会重新划分备选栈的大小。如果栈溢出了分配给它的空间,就会产生混乱。这通常不会是一个问题,因为一般情况下会利用备选栈来处理标准栈溢出的特殊情况,尝尝只在这个栈上分配为数不多的几帧。SIGSEGV 处理器函数的工作不是在执行清理动作后终止进程,就是使用非本地跳转解开标准栈。

看个例子:在创建一个新的备选信号栈以及 SIGSEGV的信号处理器函数之后,程序将调用一个无限递归函数,这会导致栈溢出,同时系统会向进程发送 SIGSEGV 信号。

#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <zconf.h>
#define _GNU_SOURCE             /* Get strsignal() declaration from <string.h> */
#include <string.h>
#include <signal.h>

static void sigsegvHandler(int sig)
{
    int x;

    /* UNSAFE: 该处理程序使用非异步信号安全功能
       (printf(), strsignal(), fflush(); ) */

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

    _exit(EXIT_FAILURE);                /* Can't return after SIGSEGV */
}
/* The following stops 'gcc -Wall' complaining that "control reaches
   end of non-void function" because we don't follow the call to
   overflowStack() stack in main() with a call to exit(). */

#ifdef __GNUC__
static void
overflowStack(int callNum) __attribute__ ((__noreturn__));
#endif
static void             /* A recursive function that overflows the stack */
overflowStack(int callNum)
{
    char a[100000];                     /* Make this stack frame large */

    printf("Call %4d - top of stack near %10p\n", callNum, &a[0]);
    overflowStack(callNum+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 = sigsegvHandler;     /* Establish handler for SIGSEGV */
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;           /* Handler uses alternate stack */
    if (sigaction(SIGSEGV, &sa, NULL) == -1){
        perror("sigaction");
        exit(EXIT_FAILURE);
    }


    overflowStack(1);
}

运行该程序的结果如下
在这里插入图片描述
在这一 shell 会话中,命令 ulimit 负责移除 shell 之前可能设置的任何 RLIMIT_STACK 资源限制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值