как очистить стек после появления сигнала переполнения стека - PullRequest
2 голосов
/ 06 июня 2010

В 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;
}

язык C в Linux, Ubuntu

Ответы [ 2 ]

1 голос
/ 06 июня 2010

Хотя я нахожу вопрос несколько запутанным, я подозреваю, что вы хотите использовать siglongjmp для возврата, как вы в настоящее время делаете с красной зоной. Это перемотает стек обратно к точке sigsetjmp.

1 голос
/ 06 июня 2010

Обработчики сигналов - плохой способ обработки ошибок в программе. по своей природе они асинхронны, и в них очень мало что можно сделать.

Рекурсивные функции - тоже плохая идея - у вас нет гарантий, что вы не переполните стек. да, вы выделяете размер стека, да, вы обрабатываете предупреждения о переполнении, но должен быть лучший способ сделать то, что вы хотите сделать ... Все, что вам нужно, это коллега добавить некоторые локальные переменные стека в вашу рекурсивную функцию, и вы меняете количество возможных рекурсий ...

В любом случае, чтобы ответить на ваш оригинальный вопрос ...

  • В обработчике сигналов установите глобальный (или поток-локальная) переменная, указывающая условие переполнения стека

  • В рекурсивная функция, проверьте переменная, и если она установлена, используйте «С» автоматическая очистка волшебного стека ключевое слово ...

О, забыл упомянуть, что это за ключевое слово очистки стека:

return;

Вы также можете использовать его для возврата состояния ошибки в ваших рекурсивных функциях, поэтому каждый вызывающий пользователь прекратит обработку и вернет условие ошибки обратно своему вызывающему ...

...