_Unwind_Backtrace для другого контекста в FreeRTOS - PullRequest
2 голосов
/ 22 января 2020

Здравствуйте, я пытаюсь реализовать обработку ошибок в проекте FreeRTOS. Обработчик запускается прерыванием WatchDog до сброса WatchDog. Идея состоит в том, чтобы зарегистрировать имя задачи + стек вызовов невыполненной задачи.
Мне удалось отследить стек вызовов, но в неправильном контексте контекст прерывания. Пока мне нужен контекст неудачной задачи, который хранится в pxCurrentTCB. но я не знаю, как сказать _Unwind_Backtrace использовать его вместо контекста прерывания, откуда он вызывается. Поэтому я хочу _Unwind_Backtrace не контекст, из которого он вызывается, а для другого контекста, найденного в pxCurrentTCB. Я искал и пытался понять, как работает _Unwind_Backtrace, но безуспешно, поэтому, пожалуйста, помогите.
Любая помощь будет оценена, особенно пример кода. Спасибо.

_Unwind_Reason_Code unwind_backtrace_callback(_Unwind_Context * context, void * arg)
{
    static uint8_t row = 1;
    char str_buff[BUFF_SIZE];
    uintptr_t pc = _Unwind_GetIP(context);
    if (pc && row < MAX_ROW) {
        snprintf(str_buff, sizeof(str_buff), "%d .. 0x%x", row, pc);
        printString(str_buff, 0, ROW_SIZE * row++);
    }
    return _URC_NO_REASON;
}

void WDOG1_DriverIRQHandler(void)
{
    printString(pxCurrentTCB->pcTaskName, 0, 0);

    _Unwind_Backtrace(unwind_backtrace_callback, 0);

    while(1) Wdog_Service();
}

1 Ответ

0 голосов
/ 06 мая 2020

Как оказалось, OpenMRN реализует именно то решение, которое вы ищете: https://github.com/bakerstu/openmrn/blob/master/src/freertos_drivers/common/cpu_profile.hxx

Более подробную информацию можно найти здесь: Обратный стек для ядра ARM с помощью G CC компилятор (при наличии коммутатора MSP в PSP) . Цитирую этот пост:

Это выполнимо, но требуется доступ к внутренним деталям того, как libg cc реализует функцию _Unwind_Backtrace. К счастью, код с открытым исходным кодом, но в зависимости от таких внутренних деталей хрупок в том смысле, что он может сломаться в будущих версиях armg cc без какого-либо уведомления.

Как правило, чтение из источника libg cc выполняя обратную трассировку, он создает виртуальное представление памяти регистров ядра процессора, а затем использует это представление для перемещения по стеку, имитируя выбросы исключений. Первое, что делает _Unwind_Backtrace, - это заполняет этот контекст из текущих регистров ЦП, а затем вызывает внутреннюю функцию реализации.

Создание этого контекста вручную из сложенной структуры исключений достаточно, чтобы подделать обратную трассировку, идущую из режима обработчика вверх через стек вызовов в большинстве случаев. Вот пример кода (из https://github.com/bakerstu/openmrn/blob/62683863e8621cef35e94c9dcfe5abcaf996d7a2/src/freertos_drivers/common/cpu_profile.hxx#L162):

/// This struct definition mimics the internal structures of libgcc in
/// arm-none-eabi binary. It's not portable and might break in the future.
struct core_regs
{
    unsigned r[16];
};

/// This struct definition mimics the internal structures of libgcc in
/// arm-none-eabi binary. It's not portable and might break in the future.
typedef struct
{
    unsigned demand_save_flags;
    struct core_regs core;
} phase2_vrs;

/// We store what we know about the external context at interrupt entry in this
/// structure.
phase2_vrs main_context;
/// Saved value of the lr register at the exception entry.
unsigned saved_lr;

/// Takes registers from the core state and the saved exception context and
/// fills in the structure necessary for the LIBGCC unwinder.
void fill_phase2_vrs(volatile unsigned *fault_args)
{
    main_context.demand_save_flags = 0;
    main_context.core.r[0] = fault_args[0];
    main_context.core.r[1] = fault_args[1];
    main_context.core.r[2] = fault_args[2];
    main_context.core.r[3] = fault_args[3];
    main_context.core.r[12] = fault_args[4];
    // We add +2 here because first thing libgcc does with the lr value is
    // subtract two, presuming that lr points to after a branch
    // instruction. However, exception entry's saved PC can point to the first
    // instruction of a function and we don't want to have the backtrace end up
    // showing the previous function.
    main_context.core.r[14] = fault_args[6] + 2;
    main_context.core.r[15] = fault_args[6];
    saved_lr = fault_args[5];
    main_context.core.r[13] = (unsigned)(fault_args + 8); // stack pointer
}
extern "C"
{
    _Unwind_Reason_Code __gnu_Unwind_Backtrace(
        _Unwind_Trace_Fn trace, void *trace_argument, phase2_vrs *entry_vrs);
}

/// Static variable for trace_func.
void *last_ip;

/// Callback from the unwind backtrace function.
_Unwind_Reason_Code trace_func(struct _Unwind_Context *context, void *arg)
{
    void *ip;
    ip = (void *)_Unwind_GetIP(context);
    if (strace_len == 0)
    {
        // stacktrace[strace_len++] = ip;
        // By taking the beginning of the function for the immediate interrupt
        // we will attempt to coalesce more traces.
        // ip = (void *)_Unwind_GetRegionStart(context);
    }
    else if (last_ip == ip)
    {
        if (strace_len == 1 && saved_lr != _Unwind_GetGR(context, 14))
        {
            _Unwind_SetGR(context, 14, saved_lr);
            allocator.singleLenHack++;
            return _URC_NO_REASON;
        }
        return _URC_END_OF_STACK;
    }
    if (strace_len >= MAX_STRACE - 1)
    {
        ++allocator.limitReached;
        return _URC_END_OF_STACK;
    }
    // stacktrace[strace_len++] = ip;
    last_ip = ip;
    ip = (void *)_Unwind_GetRegionStart(context);
    stacktrace[strace_len++] = ip;
    return _URC_NO_REASON;
}

/// Called from the interrupt handler to take a CPU trace for the current
/// exception.
void take_cpu_trace()
{
    memset(stacktrace, 0, sizeof(stacktrace));
    strace_len = 0;
    last_ip = nullptr;
    phase2_vrs first_context = main_context;
    __gnu_Unwind_Backtrace(&trace_func, 0, &first_context);
    // This is a workaround for the case when the function in which we had the
    // exception trigger does not have a stack saved LR. In this case the
    // backtrace will fail after the first step. We manually append the second
    // step to have at least some idea of what's going on.
    if (strace_len == 1)
    {
        main_context.core.r[14] = saved_lr;
        main_context.core.r[15] = saved_lr;
        __gnu_Unwind_Backtrace(&trace_func, 0, &main_context);
    }
    unsigned h = hash_trace(strace_len, (unsigned *)stacktrace);
    struct trace *t = find_current_trace(h);
    if (!t)
    {
        t = add_new_trace(h);
    }
    if (t)
    {
        t->total_size += 1;
    }
}

/// Change this value to runtime disable and enable the CPU profile gathering
/// code.
bool enable_profiling = 0;

/// Helper function to declare the CPU usage tick interrupt.
/// @param irq_handler_name is the name of the interrupt to declare, for example
/// timer4a_interrupt_handler.
/// @param CLEAR_IRQ_FLAG is a c++ statement or statements in { ... } that will
/// be executed before returning from the interrupt to clear the timer IRQ flag.
#define DEFINE_CPU_PROFILE_INTERRUPT_HANDLER(irq_handler_name, CLEAR_IRQ_FLAG) \
    extern "C"                                                                 \
    {                                                                          \
        void __attribute__((__noinline__)) load_monitor_interrupt_handler(     \
            volatile unsigned *exception_args, unsigned exception_return_code) \
        {                                                                      \
            if (enable_profiling)                                              \
            {                                                                  \
                fill_phase2_vrs(exception_args);                               \
                take_cpu_trace();                                              \
            }                                                                  \
            cpuload_tick(exception_return_code & 4 ? 0 : 255);                 \
            CLEAR_IRQ_FLAG;                                                    \
        }                                                                      \
        void __attribute__((__naked__)) irq_handler_name(void)                 \
        {                                                                      \
            __asm volatile("mov  r0, %0 \n"                                    \
                           "str  r4, [r0, 4*4] \n"                             \
                           "str  r5, [r0, 5*4] \n"                             \
                           "str  r6, [r0, 6*4] \n"                             \
                           "str  r7, [r0, 7*4] \n"                             \
                           "str  r8, [r0, 8*4] \n"                             \
                           "str  r9, [r0, 9*4] \n"                             \
                           "str  r10, [r0, 10*4] \n"                           \
                           "str  r11, [r0, 11*4] \n"                           \
                           "str  r12, [r0, 12*4] \n"                           \
                           "str  r13, [r0, 13*4] \n"                           \
                           "str  r14, [r0, 14*4] \n"                           \
                           :                                                   \
                           : "r"(main_context.core.r)                          \
                           : "r0");                                            \
            __asm volatile(" tst   lr, #4               \n"                    \
                           " ite   eq                   \n"                    \
                           " mrseq r0, msp              \n"                    \
                           " mrsne r0, psp              \n"                    \
                           " mov r1, lr \n"                                    \
                           " ldr r2,  =load_monitor_interrupt_handler  \n"     \
                           " bx  r2  \n"                                       \
                           :                                                   \
                           :                                                   \
                           : "r0", "r1", "r2");                                \
        }                                                                      \
    }

Этот код предназначен для получения профиля ЦП с использованием прерывания по таймеру, но с обратной трассировкой разматывание можно использовать из любого обработчика, включая обработчики ошибок. Прочитайте код снизу вверх:

  • Важно, чтобы функция IRQ была определена с атрибутом __naked__, в противном случае заголовок записи функции G CC будет манипулировать состоянием CPU непредсказуемым образом, например, изменяя указатель стека.
  • Первым делом мы сохраняем все остальные регистры ядра, которые не входят в структуру записи исключения. Мы должны сделать это из сборки в самом начале, потому что они, как правило, будут модифицированы более поздним C кодом, когда они будут использоваться как временные регистры.
  • Затем мы восстанавливаем указатель стека до прерывания; код будет работать независимо от того, был ли процессор раньше в режиме обработчика или в режиме потока. Этот указатель является структурой записи исключения. Этот код не обрабатывает стеки, которые не выровнены по 4 байта, но я никогда не видел, чтобы armg cc делал это в любом случае.
  • Остальная часть кода находится на C / C ++, мы заполняем внутреннюю структуру, которую мы взял из libg cc, затем вызвал внутреннюю реализацию процесса раскрутки. Мы должны внести некоторые коррективы, чтобы обойти определенные допущения libg cc, которые не сохраняются при вводе исключения.
  • Существует одна конкретная c ситуация, когда разматывание не работает, а если исключение произошло в конечной функции, которая не сохраняет LR в стек при входе. Этого никогда не происходит, когда вы пытаетесь выполнить возврат из режима процесса, потому что вызываемая функция отслеживания гарантирует, что вызывающая функция не является листом. Я пытался применить некоторые обходные пути, настраивая регистр LR во время самого процесса обратного отслеживания, но я не уверен, что он работает каждый раз. Меня интересуют предложения о том, как сделать это лучше.
...