How to determine if code is running in signal-handler context?

前端 未结 6 1231
一生所求
一生所求 2021-02-03 11:11

I just found out that someone is calling - from a signal handler - a definitely not async-signal-safe function that I wrote.

So, now I\'m curious: how to circumvent this

相关标签:
6条回答
  • 2021-02-03 11:12

    You could work out something using sigaltstack. Set up an alternative signal stack, get the stack pointer in some async-safe way, if within the alternative stack go on, otherwise abort().

    0 讨论(0)
  • 2021-02-03 11:12

    I guess you need to do the following. This is a complex solution, which combines the best practices not only from coding, but from software engineering as well!

    1. Persuade your boss that naming convention on signal handlers is a good thing. Propose, for example, a Hungarian notation, and tell that it was used in Microsoft with great success. So, all signal handlers will start with sighnd, like sighndInterrupt.
    2. Your function that detects signal handling context would do the following:
      1. Get the backtrace().
      2. Look if any of the functions in it begin with sighnd.... If it does, then congratulations, you're inside a signal handler!
      3. Otherwise, you're not.
    3. Try to avoid working with Jimmy in the same company. "There can be only one", you know.
    0 讨论(0)
  • 2021-02-03 11:14

    Apparently, newer Linux/x86 (probably since some 2.6.x kernel) calls signal handlers from the vdso. You could use this fact to inflict the following horrible hack upon the unsuspecting world:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>
    #include <signal.h>
    
    #include <unistd.h>
    
    uintmax_t vdso_start = 0;
    uintmax_t vdso_end = 0;             /* actually, next byte */
    
    int check_stack_for_vdso(uint32_t *esp, size_t len)
    {
        size_t i;
    
        for (i = 0; i < len; i++, esp++)
                if (*esp >= vdso_start && *esp < vdso_end)
                        return 1;
    
        return 0;
    }
    
    void handler(int signo)
    {
        uint32_t *esp;
    
        __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
        /* XXX only for demonstration, don't call printf from a signal handler */
        printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
    }
    
    void parse_maps()
    {
        FILE *maps;
        char buf[256];
        char path[7];
        uintmax_t start, end, offset, inode;
        char r, w, x, p;
        unsigned major, minor;
    
        maps = fopen("/proc/self/maps", "rt");
        if (maps == NULL)
                return;
    
        while (!feof(maps) && !ferror(maps)) {
                if (fgets(buf, 256, maps) != NULL) {
                        if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s",
                                        &start, &end, &r, &w, &x, &p, &offset,
                                        &major, &minor, &inode, path) == 11) {
                                if (!strcmp(path, "[vdso]")) {
                                        vdso_start = start;
                                        vdso_end = end;
                                        break;
                                }
                        }
                }
        }
    
        fclose(maps);
    
        printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end);
    }
    
    int main()
    {
        struct sigaction sa;
        uint32_t *esp;
    
        parse_maps();
        memset(&sa, 0, sizeof(struct sigaction));
        sa.sa_handler = handler;
        sa.sa_flags = SA_RESTART;
    
        if (sigaction(SIGUSR1, &sa, NULL) < 0) {
                perror("sigaction");
                exit(1);
        }
    
        __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
        printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
    
        kill(getpid(), SIGUSR1);
    
        __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
        printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
    
        return 0;
    }
    

    SCNR.

    0 讨论(0)
  • 2021-02-03 11:19

    for code optimized at -O2 or better (istr) have found need to add -fno-omit-frame-pointer

    else gcc will optimize out the stack context information

    0 讨论(0)
  • 2021-02-03 11:25

    There are two proper ways to deal with this:

    • Have your co-workers stop doing the wrong thing. Good luck pulling this off with the boss, though...

    • Make your function re-entrant and async-safe. If necessary, provide a function with a different signature (e.g. using the widely-used *_r naming convention) with the additional arguments that are necessary for state preservation.

    As for the non-proper way to do this, on Linux with GNU libc you can use backtrace() and friends to go through the caller list of your function. It's not easy to get right, safe or portable, but it might do for a while:

    /*
     * *** Warning ***
     *
     * Black, fragile and unportable magic ahead
     *
     * Do not use this, lest the daemons of hell be unleashed upon you
     */
    int in_signal_handler_context() {
            int i, n;
            void *bt[1000];
            char **bts = NULL;
    
            n = backtrace(bt, 1000);
            bts = backtrace_symbols(bt, n);
    
            for (i = 0; i < n; ++i)
                    printf("%i - %s\n", i, bts[i]);
    
            /* Have a look at the caller chain */
            for (i = 0; i < n; ++i) {
                    /* Far more checks are needed here to avoid misfires */
                    if (strstr(bts[i], "(__libc_start_main+") != NULL)
                            return 0;
                    if (strstr(bts[i], "libc.so.6(+") != NULL)
                            return 1;
            }
    
            return 0;
    }
    
    
    void unsafe() {
            if (in_signal_handler_context())
                    printf("John, you know you are an idiot, right?\n");
    }
    

    In my opinion, it might just be better to quit rather than be forced to write code like this.

    0 讨论(0)
  • 2021-02-03 11:35

    If we can assume your application doesn't manually block signals using sigprocmask() or pthread_sigmask(), then this is pretty simple: get your current thread ID (tid). Open /proc/tid/status and get the values for SigBlk and SigCgt. AND those two values. If the result of that AND is non-zero, then that thread is currently running from inside a signal handler. I've tested this myself and it works.

    0 讨论(0)
提交回复
热议问题