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

时间:2010-06-06 08:37:19

标签: 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中的C语言,ubuntu

2 个答案:

答案 0 :(得分:1)

信号处理程序是处理程序错误的一种不好方法。它们本质上是异步的,你可以用它们做很少的事情。

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

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

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

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

哦,忘了提一下魔术堆清理关键字是什么:

return;

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

答案 1 :(得分:1)

虽然我觉得这个问题有点令人困惑,但我怀疑你想要做的是使用siglongjmp返回,因为你正在使用红色区域。这会将堆栈重新回到sigsetjmp点。