Как определить, выполняется ли код в контексте обработчика сигналов?

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

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

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

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


person smcdow    schedule 28.01.2011    source источник


Ответы (6)


Судя по всему, более новый 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;
}

ГКНР.

person ninjalj    schedule 28.01.2011
comment
Я не знал, что обработчики сигналов вызываются из vdso. Не могли бы вы указать ссылку? Во всяком случае, мне нравится этот лайфхак. Много. Было бы достаточно просто свернуть это в непрозрачную библиотеку. Хитрость заключается в том, чтобы убедиться, что parse_maps() вызывается до любых обработчиков сигналов. - person smcdow; 29.01.2011
comment
Лучшая ссылка, которую я могу найти, это lxr.free-electrons.com/source/arch/x86/kernel/ - person ninjalj; 29.01.2011
comment
Но комментарий в строке 320 выглядит интересно: lxr.free-electrons.com/source/arch/x86/kernel/ - person ninjalj; 29.01.2011
comment
Мы работаем на стандартных дистрибутивах RHEL-5 -- Linux-2.6.18, так что, возможно, я смогу использовать это. Я напишу тестовый пример на следующей неделе (что на самом деле означает, что я скопирую и вставлю ваш код, чтобы посмотреть, что он делает :-). Спасибо. - person smcdow; 29.01.2011

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

person David Yeager    schedule 25.04.2017
comment
Вам понадобится идентификатор процесса (PID), а не идентификатор потока. И это потребует вызова небезопасных для асинхронных сигналов функций, исключая написание собственных. - person mgarey; 09.03.2018
comment
@mgarey хорошее замечание, убедитесь, что вы делаете это только с использованием безопасных функций асинхронного сигнала, таких как системные вызовы open () и read (), что определенно выполнимо. Однако, если программа многопоточная, вы ДОЛЖНЫ использовать tid, который будет отличаться от pid. Нахождение внутри обработчика сигнала является состоянием, специфичным для потока. - person David Yeager; 07.05.2018

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

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

  • Сделайте вашу функцию реентерабельной и асинхронно-безопасной. При необходимости предоставьте функцию с другой сигнатурой (например, с использованием широко используемого соглашения об именах *_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");
}

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

person thkala    schedule 28.01.2011
comment
Я только что попробовал backtrace(), и это просто не работает: __libc_start_main находится в трассировке как в контексте обработки сигнала, так и вне его. - person P Shved; 29.01.2011
comment
Как я уже говорил, это не легко сделать правильно. Вы должны найти разницу в обратной трассировке между двумя случаями и использовать ее. Например, для моего теста я предположил, что ни одна функция libc не будет находиться в обратной трассе до достижения main(), если только это не код обработки сигнала. Как выглядит ваша обратная трассировка в каждом случае? - person thkala; 29.01.2011
comment
Два комментария: (1) Я боялся, что это может быть что-то вроде этого. (2) Не хочу быть сопливой, но printf(3) не является функцией, безопасной для асинхронных сигналов. Вам придется использовать запись (2). -- Список функций, безопасных для асинхронных сигналов (по крайней мере, список, на который я обычно ссылаюсь) можно найти здесь: pubs.opengroup.org/onlinepubs/009695399/functions/ - person smcdow; 29.01.2011
comment
Вот почему я вообще избегал попыток дать прямой ответ. То, о чем вы просите, требует взлома, как предложил @thkala. Следовательно, вероятно, лучше найти дипломатическое решение. - person Judge Maygarden; 29.01.2011
comment
@smcdow: я хорошо знаю, что printf() не является безопасным для асинхронного режима, но и функция, которая его использует (т.е. ваша функция) :-) - person thkala; 29.01.2011
comment
Забыл прочитать ваш комментарий над кодом. Весь смысл in_signal_handler_context() заключался в том, чтобы сделать функцию реентерабельной :-). - person smcdow; 29.01.2011
comment
@smcdow: я думал, что это было для того, чтобы вы могли сказать Джону, что он идиот, и заставить его изменить свой код :-) - person thkala; 29.01.2011
comment
@thkala: На самом деле мне эта идея нравится больше. :-) - person smcdow; 29.01.2011
comment
разрешены ли вызовы backtrace и backtrace_symbols внутри обработчика сигналов, поскольку они не безопасны для async_siganl? - person sandeep; 05.07.2011
comment
разрешены ли вызовы backtrace и backtrace_symbols внутри обработчика сигналов, поскольку они не безопасны для async_siganl? в одной из моих программ, где я использовал обратную трассировку, когда когда-либо были вызовы malloc/free/printf/etc.. в стеке вызовов потока, и мой обработчик сигналов выполнял обратную трассировку этого потока частиц, программа использовалась для сбоя. когда я отлаживаю его с помощью gdb, я наблюдал ссылку на регистр RBP после того, как обработчик сигнала был сломан.. (т. е. регистр текущего кадра RBP указывает на предыдущий кадр RBP.. таким образом, мы можем перемещаться по стеку). - person sandeep; 05.07.2011

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

person Tobu    schedule 28.01.2011
comment
Я думал о чем-то подобном, но не думаю, что могу гарантировать, что у меня будет настроен альтернативный стек до того, как моя функция будет вызвана из обработчика сигнала. Я также должен повторить, что моя функция НИКОГДА не должна вызываться из обработчика сигнала, и документация говорит об этом. - person smcdow; 29.01.2011
comment
Есть более простой способ. sigaltstack требуется для возврата ошибки, если вы уже работаете в альтернативном стеке и пытаетесь внести в него изменения, поэтому вы можете просто попробовать вызвать его и посмотреть, не сработает ли вызов. - person R.. GitHub STOP HELPING ICE; 22.03.2011

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

  1. Убедите своего начальника, что соглашение об именовании обработчиков сигналов — это хорошо. Предложите, например, венгерскую нотацию и сообщите, что она использовалась в Майкрософт с большим успехом. Таким образом, все обработчики сигналов будут начинаться с sighnd, например sighndInterrupt.
  2. Your function that detects signal handling context would do the following:
    1. Get the backtrace().
    2. Посмотрите, не начинаются ли в нем какие-либо функции с sighnd.... Если да, то поздравляю, вы внутри обработчика сигнала!
    3. В противном случае, вы не.
  3. Старайтесь не работать с Джимми в одной компании. "Может быть только один", вы знаете.
person P Shved    schedule 28.01.2011
comment
У нас будет уборщик, который просматривает базу кода и сообщает обо всех без исключения функциях, вызываемых обработчиками сигналов. Я уже ржу. - person smcdow; 29.01.2011
comment
@smcdow, кстати, вы можете использовать для этого инструмент статического анализа. Но опять же, вам придется аннотировать каждый обработчик сигнала, что делает предлагаемое решение не более сложным. :-) - person P Shved; 29.01.2011
comment
некоторое время назад был инструмент Valgrind для поиска проблем с обработчиками сигналов, он назывался crocus. - person ninjalj; 29.01.2011
comment
@ninjalj, Valgrind - это инструмент динамического анализа, а не статический. Во всяком случае, я только что придумал способ сделать это статически, но здесь это совсем не по теме. - person P Shved; 29.01.2011

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

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

person chaosless    schedule 10.01.2012