Как определить, выполняется ли код в контексте обработчика сигналов? - PullRequest
11 голосов
/ 28 января 2011

Я только что узнал, что кто-то вызывает - из обработчика сигнала - определенно не безопасную асинхронную функцию, которую я написал.

Итак, теперь мне любопытно: как обойти эту ситуацию, чтобы она не повторилась? Я хотел бы иметь возможность легко определить, выполняется ли мой код в контексте обработчика сигнала (язык C, но разве решение не применимо к любому языку?):

int myfunc( void ) {
    if( in_signal_handler_context() ) { return(-1) }
    // rest of function goes here
    return( 0 );
}

Это под Linux. Надеюсь, это не простой ответ, иначе я буду чувствовать себя идиотом.

Ответы [ 6 ]

7 голосов
/ 29 января 2011

Очевидно, что более новая версия Linux / x86 (возможно, с ядром 2.6.x) вызывает обработчики сигналов из vdso. Вы можете использовать этот факт, чтобы нанести следующий ничего не подозревающий мир ничего не подозревающему:

#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.

3 голосов
/ 26 апреля 2017

Если мы можем предположить, что ваше приложение не блокирует сигналы вручную, используя sigprocmask() или pthread_sigmask(), то это довольно просто: получите ваш текущий идентификатор потока (tid).Откройте /proc/tid/status и получите значения для SigBlk и SigCgt.AND эти два значения.Если результат этого AND не равен нулю, то этот поток в настоящее время работает из обработчика сигнала.Я сам проверил это, и оно работает.

0 голосов
/ 11 января 2012

для кода, оптимизированного при -O2 или лучше (istr), было обнаружено необходимость добавить -fno-omit-frame-pointer

иначе gcc оптимизирует контекстную информацию стека

0 голосов
/ 29 января 2011

Полагаю, вам нужно сделать следующее. Это комплексное решение, в котором сочетаются лучшие практики не только кодирования, но и разработки программного обеспечения!

  1. Убедите вашего босса, что соглашение об именах для обработчиков сигналов - это хорошо. Например, предложите венгерскую запись и скажите, что она использовалась в Microsoft с большим успехом Таким образом, все обработчики сигналов будут начинаться с sighnd, например sighndInterrupt.
  2. Ваша функция, которая определяет контекст обработки сигнала, будет делать следующее:
    1. Получите backtrace().
    2. Посмотрите, начинаются ли какие-либо функции в нем с sighnd.... Если да, то поздравляю, вы внутри обработчика сигналов!
    3. В противном случае вы не.
  3. Старайтесь не работать с Джимми в той же компании. «Может быть только один», вы знаете.
0 голосов
/ 29 января 2011

Вы можете решить что-нибудь, используя sigaltstack . Установите альтернативный стек сигналов, получите указатель стека некоторым асинхронным способом, если в пределах альтернативного стека продолжите, иначе abort ().

0 голосов
/ 29 января 2011

Есть два правильных способа справиться с этим:

  • Пусть ваши коллеги перестанут поступать неправильно.Удачи, справляясь с боссом, хотя ...

  • Сделайте вашу функцию повторной и асинхронной.При необходимости предоставьте функцию с другой подписью (например, используя широко используемое *_r соглашение об именах) с дополнительными аргументами, необходимыми для сохранения состояния.

Что касается неправильный способ сделать это, в Linux с GNU libc вы можете использовать backtrace() и друзей, чтобы просмотреть список вызывающих абонентов вашей функции.Это не легко получить правильно, безопасно или переносимо, но это может занять некоторое время:

/*
 * *** 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");
}

На мой взгляд, может быть лучше бросить вместо того, чтобы быть вынужденным писать код, такой как this .

...