Изменение поведения утверждений для отладки (SIGABRT -> SIGTRAP)

Я пытаюсь отладить программу на Debian, созданную с помощью gcc/g++ (DEBUG=1 и NDEBUG не определены). Я использую стороннюю библиотеку, и она также создана для отладки (DEBUG=1, NDEBUG undefined, плюс другие определения отладки). Библиотека состоит из 150 тысяч строк и полна утверждений. Это хорошо.

В моем коде есть ошибка, и сейчас я пытаюсь отладить SQL-запрос. Это вызывает срабатывание утверждения в сторонней библиотеке. Это нормально и в порядке вещей.

Однако это поведение, заданное Posix, приводит к сбою программы, когда в библиотеке срабатывает assert. Это бесполезное поведение при отладке функции "отладки и диагностики". Это должно быть одно из самых глупых решений этого комитета, и неудивительно, что многие люди редко используют его во время разработки.

Я хочу изменить поведение таким образом, чтобы при срабатывании assert он поднимал SIGTRAP, а не SIGABRT. Я немного ограничен, потому что я не писал стороннюю библиотеку (в моем коде используется MY_ASSERT, и он вызывает SIGTRAP, поэтому я могу продолжать и наблюдать за отрицательными путями кода).

Я знаю, что могу установить или изменить обработчики сигналов через gdb, но gdb уже останавливается на SIGABRT, прежде чем передать его программе и прервать. Я также попытался установить пользовательский обработчик сигнала SIGABRT, который, похоже, не помогает, поскольку прерывание все еще происходит.

Как изменить поведение assert, чтобы при отладке возникало SIGTRAP?


person jww    schedule 21.01.2014    source источник


Ответы (1)


Для Linux вы можете определить __assert_fail, которая может вызвать SIGTRAP, а не реализацию по умолчанию.

void __assert_fail(const char * assertion, const char * file, unsigned int line, const char * function) {
    fprintf(stderr, "Assert: %s failed at %s:%d in function %s", assertion, file, line, function);
    raise(SIGTRAP);
}

Спецификация говорит, что вы фактически должны завершить программу в этот момент, и поскольку функция объявлена ​​как имеющая __attribute__((noreturn)) в signal.h.

Вы можете добавить такой сомнительный код (отредактированный для использования обнаружения ptrace через fork, а не через сигналы):

#include <assert.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ptrace.h>

static int ptrace_failed;

static int
detect_ptrace(void)
{
    int status, waitrc;

    pid_t child = fork();
    if (child == -1) return -1;
    if (child == 0) {
        if (ptrace(PT_ATTACH, getppid(), 0, 0))
            exit(1);
        do {
            waitrc = waitpid(getppid(), &status, 0);
        } while (waitrc == -1 && errno == EINTR);
        ptrace(PT_DETACH, getppid(), (caddr_t)1, SIGCONT);
        exit(0);
    }
    do {
        waitrc = waitpid(child, &status, 0);
    } while (waitrc == -1 && errno == EINTR);
    return WEXITSTATUS(status);
}

__attribute__((constructor))
static void
detect_debugger(void)
{
    ptrace_failed = detect_ptrace();
}

void __assert_fail(const char * assertion, const char * file, unsigned int line, const char * function) {
    fprintf(stderr, "Assert: %s failed at %s:%d in function %s\n", assertion, file, line, function);
    if (ptrace_failed)
        raise(SIGTRAP);
    else
        abort();
}

Который запускает SIGTRAP при запуске под gdb, но в противном случае запускает SIGABRT. Я протестировал его, связанный с LD_PRELOAD, и, похоже, он работает так, как ожидалось.

person Petesh    schedule 21.01.2014
comment
Спасибо, Патеш, скоро попробую. Я думаю, что мой другой выбор - инъекция против LD_PRELOAD трюков, что кажется мне немного экстремальным. О чем, черт возьми, думал Posix ..... - person jww; 21.01.2014
comment
К сожалению, это не сработало, когда я тестировал реализацию __assert_fail в исходном файле main. __assert_fail никогда не звонили. - person jww; 21.01.2014
comment
Платформа? Архитектура? Варианты компиляции? функция __assert_fail является глобально видимой (если нет, то она просто не будет работать)? Я протестировал его с помощью простого: int main(int argc, char **argv) { assert(argc != 1); } в качестве основного метода, и он прекрасно работал в системе i386/x64. - person Petesh; 21.01.2014
comment
Обратите внимание, что если вы утверждаете с постоянным выражением времени компиляции, это изменит поведение (это не сработает, вы получите SEGV при возврате из обработчика утверждения) - person Petesh; 21.01.2014