views:

200

answers:

2

In pthread, After reaching yellow zone in stack, signal handler stop the recursive function by making it return

however, we can only continue to use extra area in yellow zone,

how to clear the rubbish before the yellow zone in the thread stack ?


(Copied from "answers"):

#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 language in linux, ubuntu

A: 

Signal handlers are a bad way of handling errors in a program. they are by nature asynchronous and there is very little that you can do in them.

Recursive functions are also a bad idea -- you have no guarantees that you will not overflow the stack. yes, you allocate the stack size, yes you are handling overflow warnings, but there must be a better way of doing what you want to do... All it takes is a colleague to add some stack-local variables in your recursive function, and you change the number of possible recursions...

Anyway to answer your original question...

  • In the signal handler, set a global (or thread-local) variable indicating a stack overflow condition

  • In the recursive function, check the variable, and if it is set, use 'C's magic stack automatic cleanup keyword...

Oh, forgot to mention what that magic stack cleanup keyword is:

return;

You can also use it to return an error condition in your recursive functions, so each caller will quit processing and return the error condition back to its caller...

CuriousPanda
Can 'return' clear all the calling before? Or just clear one previous calling?
'In the recursive function, check the variable'... and return.This will return to the parent recursive function, which will check the variable and return to it's parent, which will check the variable... etc...
CuriousPanda
it really works, i can see it return to the original if(gofromyellow == true) { printf("return from yellow zone, called %d times\n", times); return; } else { call_times = call_times + 1; main_routine(call_times); call_times = call_times - 1; printf("return from yellow zone, %d times\n", call_times); //gofromyellow = true; }
Except recursive call to make stack overflow signal occur, any other way to make stack overflow, can "return" deal with all of them
There is nothing wrong with recursion per se. You test for your base condition and you can test for depth past any limit you choose to set.
Jim Dennis
+1  A: 

Although I find the question somewhat confusing, I suspect what you want to do is use siglongjmp to return, as you're currently doing with the red zone. This will rewind the stack back to the sigsetjmp point.

Hasturkun