Несколько вызовов обработчика sigtrap не работают

У меня есть небольшое приложение, написанное на C для Linux на ARM, которое настраивает обработчик сигнала SIGTRAP и должно перехватывать инструкцию bkpt (через код TRAP_HWBKPT) и эффективно пропускать ее.

Обработчик просто уведомляет, откуда была поднята точка останова, и использует встроенный эпилог для перенаправления потока кода на 4 байта (из-за архитектуры ARM RISC) после адреса точки останова (который является следующей инструкцией). Я знаю, что должно быть какое-то сохранение реестра, но сейчас это не проблема.

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

Кто-нибудь знает, почему это происходит и как это исправить, чтобы я мог достичь цели вызова обработчика для каждой инструкции bkpt и продолжения выполнения в обычном режиме?

$ gcc -o break break.c
$ ./break
START
ENTER [0]!
SIGTRAP at 0x856c
EXIT [0]!
ENTER [1]!
Trace/breakpoint trap

Это код, о котором идет речь:

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifndef TRAP_BRANCH
    #define TRAP_BRANCH 3
#endif

#ifndef TRAP_HWBKPT
    #define TRAP_HWBKPT 4
#endif

static void sigtrap_handler(int sig, siginfo_t* siginfo, void* ptr)
{
    switch(siginfo->si_code)
    {
        case TRAP_HWBKPT:
        {
            printf("SIGTRAP at %p\n", (void*)siginfo->si_addr);

            asm volatile("sub sp, r11, #4\n\t"
                         "pop {r11}\n\t"
                         "sub sp, #4\n\t"
                         "mov pc, %0"
                         :
                         : "r" (siginfo->si_addr + 4) // Proceed to instruction directly after breakpoint
                        );
        }break;

        // misc. SIGTRAP codes (fallthrough)
        case TRAP_BRKPT:
        case TRAP_TRACE:
        case TRAP_BRANCH:
        default:
        {
            exit(-1);
        }break;
    }
}

void dummy_routine(int n)
{
    printf("ENTER [%d]!\n", n);

    // Breakpoint
    asm volatile("bkpt");

    printf("EXIT [%d]!\n", n);
    return;
}

int main (int argc, char *argv[])
{
    int i;
    struct sigaction act;

    // Set up sigtrap handler
    memset (&act, 0, sizeof(act));
    act.sa_sigaction = sigtrap_handler;
    act.sa_flags = SA_SIGINFO;

    if (sigaction(SIGTRAP, &act, 0)) {
        perror("Error: sigaction");
        return 1;
    }

    printf("START\n");

    // Trigger routine containing breakpoint multiple times
    for(i = 0; i < 2; i++)
    {
        dummy_routine(i);
    }

    printf("END\n");

    return 0;
}

person ayylmao    schedule 21.09.2015    source источник
comment
Подождите, так вы берете сигнал, искажаете поток управления, перескакивая прямо к своему коду в контексте обработчика сигнала, и пытаетесь рекурсивно обработать больше сигналов без возврата? Я не претендую на звание эксперта по сигналам, но это выглядит неправильно...   -  person Notlikethat    schedule 21.09.2015
comment
@Notlikethat: это настолько плохо, что даже неопределенное поведение при вызове printf() в обработчике сигналов выглядит прирученным по сравнению с ним.   -  person EOF    schedule 22.09.2015
comment
sigsetjmp() и siglongjmp() с большей вероятностью преуспеют в чем-то близком к тому, что вы пытаетесь сделать.   -  person Andrew Henle    schedule 22.09.2015
comment
Вопросы и ответы затрагивают все, кроме фактического изменения сохраненного контекста.   -  person Notlikethat    schedule 22.09.2015
comment
@notlikethat: спасибо, я нашел то, что искал. Вы можете указать: struct sigcontext* context = &(((ucontext_t*)ptr)->uc_mcontext); uintptr_t fault_address = context->arm_pc; Чтобы получить точный адрес поднятия сигнала (таким образом GDB проверяет состояние регистра). И вы можете установить context->arm_pc += 4 для достижения того, что я хочу, не вызывая ошибочного поведения из-за неправильного возврата из обработчика сигнала.   -  person ayylmao    schedule 22.09.2015
comment
Ядро по-прежнему будет считать, что вы обрабатываете сигнал, и вы получите «сигнал в сигнале», когда вы примете исходный подход (скажем, несколько sigbus и т. д.), поэтому ядро ​​​​считает, что ваш процесс вышел из-под контроля. ucontext_t — это правильный метод изменения потока при возврате из обработчика сигнала.   -  person artless noise    schedule 22.09.2015