c - 堆栈溢出信号发生后如何清除堆栈

标签 c multithreading posix pthreads

在 pthread 中,到达堆栈中的黄色区域后,信号处理程序通过返回来停止递归函数

但是,我们只能继续使用黄色区域的额外区域,

如何清除线程栈中黄色区域前的垃圾?


(复制自“答案”):

#include <pthread.h>

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>
#include <unistd.h>
#include <assert.h>
#include <sys/resource.h>


#define ALT_STACK_SIZE (64*1024)
#define YELLOW_ZONE_PAGES (1)

typedef struct {
    size_t  stack_size;
    char*   stack_pointer;
    char*   red_zone_boundary;
    char*   yellow_zone_boundary;

    sigjmp_buf return_point;
    size_t red_zone_size;
} ThreadInfo;

static pthread_key_t thread_info_key;
static struct sigaction newAct, oldAct;
bool gofromyellow = false;
int call_times = 0;

static void main_routine(){
    // make it overflow
    if(gofromyellow == true)
    {
        printf("return from yellow zone, called %d times\n", call_times);
        return;
    }
    else
    {
        call_times = call_times + 1;
        main_routine();
        gofromyellow = true;
    }
}
// red zone management
static void stackoverflow_routine(){
    fprintf(stderr, "stack overflow error.\n");
    fflush(stderr);
}
// yellow zone management
static void yellow_zone_hook(){
    fprintf(stderr, "exceed yellow zone.\n");
    fflush(stderr);
}

static int get_stack_info(void** stackaddr, size_t* stacksize){
    int ret = -1;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    if(pthread_getattr_np(pthread_self(), &attr) == 0){
        ret = pthread_attr_getstack(&attr, stackaddr, stacksize);
    }
    pthread_attr_destroy(&attr);
    return ret;
}

static int is_in_stack(const ThreadInfo* tinfo, char* pointer){
    return (tinfo->stack_pointer <= pointer) && (pointer < tinfo->stack_pointer + tinfo->stack_size);
}

static int is_in_red_zone(const ThreadInfo* tinfo, char* pointer){
    if(tinfo->red_zone_boundary){
        return (tinfo->stack_pointer <= pointer) && (pointer < tinfo->red_zone_boundary);
    }
}
static int is_in_yellow_zone(const ThreadInfo* tinfo, char* pointer){
    if(tinfo->yellow_zone_boundary){
        return (tinfo->red_zone_boundary <= pointer) && (pointer < tinfo->yellow_zone_boundary);
    }
}

static void set_yellow_zone(ThreadInfo* tinfo){
    int pagesize = sysconf(_SC_PAGE_SIZE);
    assert(pagesize > 0);
    tinfo->yellow_zone_boundary = tinfo->red_zone_boundary + pagesize * YELLOW_ZONE_PAGES;
    mprotect(tinfo->red_zone_boundary, pagesize * YELLOW_ZONE_PAGES, PROT_NONE);
}

static void reset_yellow_zone(ThreadInfo* tinfo){
    size_t pagesize = tinfo->yellow_zone_boundary - tinfo->red_zone_boundary;
    if(mmap(tinfo->red_zone_boundary, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0) == 0){
        perror("mmap failed"), exit(1);
    }
    mprotect(tinfo->red_zone_boundary, pagesize, PROT_READ | PROT_WRITE);
    tinfo->yellow_zone_boundary = 0;
}

static void signal_handler(int sig, siginfo_t* sig_info, void* sig_data){
    if(sig == SIGSEGV){
        ThreadInfo* tinfo = (ThreadInfo*) pthread_getspecific(thread_info_key);
        char* fault_address = (char*) sig_info->si_addr;

        if(is_in_stack(tinfo, fault_address)){
            if(is_in_red_zone(tinfo, fault_address)){
                siglongjmp(tinfo->return_point, 1);
            }else if(is_in_yellow_zone(tinfo, fault_address)){
                reset_yellow_zone(tinfo);
                yellow_zone_hook();
                gofromyellow = true;
                return;
            } else {
                //inside stack not related overflow SEGV happen
            }
        }
    }
}

static void register_application_info(){
    pthread_key_create(&thread_info_key, NULL);

    sigemptyset(&newAct.sa_mask);
    sigaddset(&newAct.sa_mask, SIGSEGV);
    newAct.sa_sigaction = signal_handler;
    newAct.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;

    sigaction(SIGSEGV, &newAct, &oldAct);       
}

static void register_thread_info(ThreadInfo* tinfo){
    stack_t ss;

    pthread_setspecific(thread_info_key, tinfo);

    get_stack_info((void**)&tinfo->stack_pointer, &tinfo->stack_size);

    printf("stack size %d mb\n", tinfo->stack_size/1024/1024 );

    tinfo->red_zone_boundary = tinfo->stack_pointer + tinfo->red_zone_size;

    set_yellow_zone(tinfo);

    ss.ss_sp = (char*)malloc(ALT_STACK_SIZE);
    ss.ss_size = ALT_STACK_SIZE;
    ss.ss_flags = 0;
    sigaltstack(&ss, NULL);
}

static void* thread_routine(void* p){
    ThreadInfo* tinfo = (ThreadInfo*)p;

    register_thread_info(tinfo);

    if(sigsetjmp(tinfo->return_point, 1) == 0){
        main_routine();
    } else {
        stackoverflow_routine();
    }
    free(tinfo);
    printf("after tinfo, end thread\n");
    return 0;
}

int main(int argc, char** argv){
    register_application_info();

    if( argc == 2 ){
        int stacksize = atoi(argv[1]);

        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setstacksize(&attr, 1024 * 1024 * stacksize);
        {
            pthread_t pid0;
            ThreadInfo* tinfo = (ThreadInfo*)calloc(1, sizeof(ThreadInfo));

            pthread_attr_getguardsize(&attr, &tinfo->red_zone_size);
            pthread_create(&pid0, &attr, thread_routine, tinfo);
            pthread_join(pid0, NULL);
        }
    } else {
        printf("Usage: %s stacksize(mb)\n", argv[0]);
    }
    return 0;
}

linux、ubuntu中的C语言

最佳答案

信号处理程序是一种糟糕的程序错误处理方式。它们本质上是异步的,您在其中几乎无能为力。

递归函数也是一个坏主意——您无法保证不会溢出堆栈。是的,你分配了堆栈大小,是的,你正在处理溢出警告,但必须有更好的方法来做你想做的事......只需要一个同事在你的递归函数中添加一些堆栈局部变量,然后你改变了可能的递归次数......

无论如何回答你原来的问题......

  • 在信号处理程序中,设置一个全局的 (或线程本地)变量指示 堆栈溢出情况

  • 在 递归函数,检查 变量,如果已设置,请使用“C” 魔法栈自动清理 关键字...

哦,忘了提那个神奇的堆栈清理关键字是什么:

return;

您还可以使用它在递归函数中返回错误条件,这样每个调用者都会退出处理并将错误条件返回给其调用者...

关于c - 堆栈溢出信号发生后如何清除堆栈,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2983599/

相关文章:

linux - 如何从 Bash 中的并行子 shell 获取结果?

bash - 在 bash 中重命名文件的陷阱

c - 为什么我使用管道的代码挂起?

万一无法完成除法计算。商为0.0。有没有办法可以将 int 更改为 float?

c - ~ 一元运算符和按位测试给出否定结果

c++ - RabbitMQ C 库如何在 amqp_simple_wait_frame 上进行定时等待?

java - 多线程环境中的循环列表

c - 即使我为指针结构分配内存,我的 strcpy 也无法工作

c - 在 C 上重新编码 getline 函数

c - 如何从c中的POSIX消息队列中删除或清除所有消息?