Android NDK: возвращаемся - PullRequest
       2

Android NDK: возвращаемся

42 голосов
/ 14 ноября 2011

Я разрабатываю нативное приложение, которое работает с Android через NDK.Мне нужно вызвать функцию backtrace(), когда происходит сбой.Проблема в том, что для NDK нет <execinfo.h>.

Есть ли другой способ получить этот обратный след?

Ответы [ 7 ]

36 голосов
/ 04 марта 2015

Android не имеет backtrace(), но unwind.h здесь для обслуживания. Символизация возможна через dladdr().

Следующий код - моя простая реализация backtrace (без разборки):

#include <iostream>
#include <iomanip>

#include <unwind.h>
#include <dlfcn.h>

namespace {

struct BacktraceState
{
    void** current;
    void** end;
};

static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
{
    BacktraceState* state = static_cast<BacktraceState*>(arg);
    uintptr_t pc = _Unwind_GetIP(context);
    if (pc) {
        if (state->current == state->end) {
            return _URC_END_OF_STACK;
        } else {
            *state->current++ = reinterpret_cast<void*>(pc);
        }
    }
    return _URC_NO_REASON;
}

}

size_t captureBacktrace(void** buffer, size_t max)
{
    BacktraceState state = {buffer, buffer + max};
    _Unwind_Backtrace(unwindCallback, &state);

    return state.current - buffer;
}

void dumpBacktrace(std::ostream& os, void** buffer, size_t count)
{
    for (size_t idx = 0; idx < count; ++idx) {
        const void* addr = buffer[idx];
        const char* symbol = "";

        Dl_info info;
        if (dladdr(addr, &info) && info.dli_sname) {
            symbol = info.dli_sname;
        }

        os << "  #" << std::setw(2) << idx << ": " << addr << "  " << symbol << "\n";
    }
}

Может использоваться для возврата в LogCat как

#include <sstream>
#include <android/log.h>

void backtraceToLogcat()
{
    const size_t max = 30;
    void* buffer[max];
    std::ostringstream oss;

    dumpBacktrace(oss, buffer, captureBacktrace(buffer, max));

    __android_log_print(ANDROID_LOG_INFO, "app_name", "%s", oss.str().c_str());
}
20 голосов
/ 23 февраля 2016

Вот некоторый рабочий и полный код, который реализует dump_stack (), начиная с ответа Евгения Шаповалова и выполняя поиск символов и имен C ++ прямо на устройстве. Это решение:

  • работает с NDK r10e (вам не нужно полное дерево исходников Android AOSP)
  • НЕ требует никаких дополнительных сторонних библиотек (без libunwind, libbacktrace, corkscrew, CallStack)
  • НЕ зависит от каких-либо разделяемых библиотек, установленных на устройстве (например, corkscrew, получивший поддержку в Android 5)
  • НЕ вынуждает вас сопоставлять адреса с символами на вашем компьютере разработчика; все имена символов отображаются на устройстве Android в вашем коде

Используются следующие средства, встроенные в NDK:

  • <unwind.h> заголовок, который находится в наборе инструментов / директорий NDK (НЕ libunwind)
  • dladdr()
  • __cxxabiv1::__cxa_demangle() из <cxxabi.h> (см. Примечание STLport ниже)

До сих пор я проверял это только на устройстве Android 5.1 на основе руки и вызывал его только из основной программы (не из обработчика сигнала). Я использовал ndk-build по умолчанию, который выбирает gcc для платформы arm.

Пожалуйста, прокомментируйте, если вы можете сделать эту работу

  • на других ОС Android
  • из обработчика SIGSEGV при сбое (моей целью было просто напечатать трассировку стека при ошибке подтверждения)
  • с использованием наборов инструментов clang вместо gcc

Обратите внимание, что R10e NDK имеет код <unwind.h> для многих архитектур в наборах инструментов gcc и clang, поэтому поддержка выглядит широкой.

Поддержка разграничения имен символов C ++ зависит от функции __cxxabiv1::__cxa_demangle(), которая поступает из C ++ STL, включенного в NDK. Это должно работать как есть, если вы выполняете сборку Android с GNU STL (APP_STL := gnustl_static или gnustl_shared в Application.mk; см. на этой странице для получения дополнительной информации). Если вы в настоящее время вообще не используете STL, просто добавьте APP_STL := gnustl_static или gnustl_shared к Application.mk. Если вы используете STLport, вы должны наслаждаться особым видом веселья (подробнее ниже).

ВАЖНО: , чтобы этот код работал, вы не должны использовать опцию компилятора -fvisibility=hidden gcc (по крайней мере, в ваших отладочных сборках). Эта опция обычно используется, чтобы скрыть символы от посторонних глаз в релизных сборках.

Многие отмечают, что скрипт ndk-build удаляет символы из вашего NDK .so, копируя его в каталог libs / вашего проекта. Это правда (использование nm на двух копиях .so дает очень разные результаты) ОДНАКО этот удивительный слой разметки не препятствует работе приведенного ниже кода. Каким-то образом даже после удаления остаются символы (если вы помните, что не нужно компилировать с -fvisibility=hidden). Они появляются с nm -D.

В других сообщениях на эту тему обсуждались другие параметры компилятора, такие как -funwind-tables. Я не нашел, что мне нужно было установить такую ​​опцию. Сработали опции ndk-build по умолчанию.

Чтобы использовать этот код, замените _my_log() на вашу любимую функцию ведения журнала или строки.

Пользователи STLport видят специальные примечания ниже.

#include <unwind.h>
#include <dlfcn.h>
#include <cxxabi.h>

struct android_backtrace_state
{
    void **current;
    void **end;
};

_Unwind_Reason_Code android_unwind_callback(struct _Unwind_Context* context, 
                                            void* arg)
{
    android_backtrace_state* state = (android_backtrace_state *)arg;
    uintptr_t pc = _Unwind_GetIP(context);
    if (pc) 
    {
        if (state->current == state->end) 
        {
            return _URC_END_OF_STACK;
        } 
        else 
        {
            *state->current++ = reinterpret_cast<void*>(pc);
        }
    }
    return _URC_NO_REASON;
}

void dump_stack(void)
{
    _my_log("android stack dump");

    const int max = 100;
    void* buffer[max];

    android_backtrace_state state;
    state.current = buffer;
    state.end = buffer + max;

    _Unwind_Backtrace(android_unwind_callback, &state);

    int count = (int)(state.current - buffer);

    for (int idx = 0; idx < count; idx++) 
    {
        const void* addr = buffer[idx];
        const char* symbol = "";

        Dl_info info;
        if (dladdr(addr, &info) && info.dli_sname) 
        {
            symbol = info.dli_sname;
        }
        int status = 0; 
        char *demangled = __cxxabiv1::__cxa_demangle(symbol, 0, 0, &status); 

        _my_log("%03d: 0x%p %s",
                idx,
                addr,
                (NULL != demangled && 0 == status) ?
                demangled : symbol);

        if (NULL != demangled)
            free(demangled);        
    }

    _my_log("android stack dump done");
}

Что если вы используете STLport STL вместо GNU STL?

Отстой, чтобы быть вами (и мной). Есть две проблемы:

  • Первая проблема заключается в том, что в STLport отсутствует вызов __cxxabiv1::__cxa_demangle() из <cxxabi.h>. Вам нужно будет загрузить два исходных файла cp-demangle.c и cp-demangle.h из этого хранилища и поместить их в подкаталог demangle/ вашего источника, а затем сделать это вместо #include <cxxabi.h>:

    #define IN_LIBGCC2 1 // means we want to define __cxxabiv1::__cxa_demangle
    namespace __cxxabiv1
    {
    extern "C"
    {
    #include "demangle/cp-demangle.c"
    }
    }
    
  • Вторая проблема более неприятна. Оказывается, в NDK есть не один, не два, а ТРИ различных несовместимых типа <unwind.h>. И вы уже догадались, что <unwind.h> в STLport (на самом деле он находится в библиотеке gabi ++, которая подходит для поездки, когда вы выбираете STLport) несовместим. Тот факт, что STLport / gabi ++ включает в себя, предшествует тому, что включает в себя набор инструментов (смотрите параметры -I вашего вывода ndk-build), означает, что STLport не позволяет вам использовать настоящий <unwind.h>. Я не мог найти лучшего решения, чем зайти и взломать имена файлов в моем установленном NDK:

    • sources/cxx-stl/gabi++/include/unwind.h до sources/cxx-stl/gabi++/include/unwind.h.NOT
    • sources/cxx-stl/gabi++/include/unwind-arm.h до sources/cxx-stl/gabi++/include/unwind-arm.h.NOT
    • sources/cxx-stl/gabi++/include/unwind-itanium.h до sources/cxx-stl/gabi++/include/unwind-itanium.h.NOT

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

Наслаждайтесь!

18 голосов
/ 28 ноября 2011

backtrace() - это нестандартное расширение Glibc, и даже тогда оно несколько шатко для ARM (я думаю, вам нужно собрать все с -funwind-tables, а затем иметь несколько новый Glibc?)

Насколько я знаю, эта функция не включена в используемую Android библиотеку Bionic C.

Вы можете попробовать перетащить исходный код Glibc backtrace в ваш проект, а затем восстановить интересные вещи с помощью таблицы раскрутки., но это звучит как тяжелая работа для меня.

Если у вас есть отладочная информация, вы можете попробовать запустить GDB со скриптом, который присоединяется к вашему процессу, и таким образом печатает обратную трассировку, но я понятия не имею, если GDBработает на Android (хотя Android - это в основном Linux, так что с большим количеством идентификаторов, детали установки могут быть проблематичными?) Вы можете пойти дальше, выгрузив ядро ​​каким-либо образом (поддерживает ли это Bionic?) и проанализировав его после факта.

6 голосов
/ 23 февраля 2016

Вот сумасшедший однострочный метод для получения фантастически подробной трассировки стека, которая включает в себя как C / C ++ (нативный), так и Java: злоупотребление JNI

env->FindClass(NULL);

Пока ваше приложение скомпилировано, отлажено или иным образом использует Android CheckJNI, этот ошибочный вызов вызовет встроенную в Android программу проверки JNI, которая выдаст великолепную трассировку стека на консоли (из источника «art»). Эта трассировка стека выполняется внутри Android libart.so с использованием всех новейших технологий и наворотов, которые нелегко доступны для таких пользователей NDK, как мы.

Вы можете включить CheckJNI даже для отладочных приложений. Подробнее см. в этом FAQ по Google .

Я не знаю, работает ли этот трюк с обработчиком SIGSEGV (из SIGSEGV вы можете получить трассировку стека с неправильным стеком, или, возможно, art вообще не будет запускаться), но его стоит попробовать.

Если вам нужно решение, которое делает трассировку стека доступной в вашем коде (например, чтобы вы могли отправить ее по сети или зарегистрировать ее), см. Мой другой ответ на этот же вопрос.

5 голосов
/ 30 сентября 2013

Вы можете использовать CallStack:

#include <utils/CallStack.h>

void log_backtrace()
{
    CallStack cs;
    cs.update(2);
    cs.dump();
}

Результаты нужно будет деформировать на c++filt или что-то подобное:

D/CallStack( 2277): #08  0x0x40b09ac8: <_ZN7android15TimedEventQueue11threadEntryEv>+0x0x40b09961
D/CallStack( 2277): #09  0x0x40b09b0c: <_ZN7android15TimedEventQueue13ThreadWrapperEPv>+0x0x40b09af9

you @ work> $ c ++ filter _ZN7android15TimedEventQueue11threadEntryEv _ZN7android15TimedEventQueue13ThreadWrapperEPv

    android::TimedEventQueue::threadEntry()
    android::TimedEventQueue::ThreadWrapper(void*)
1 голос
/ 14 ноября 2011

Если вам просто нужно несколько (например, 2 - 5) самых верхних фреймов вызова, и если ваш GCC достаточно недавний, вы можете рассмотреть возможность использования некоторых встроенных адресов возврата или адресов фреймов.

(но я мало что знаю об Android, поэтому могу ошибаться)

0 голосов
/ 25 апреля 2018

Вот как вы записываете обратную трассировку на 32-битном ARM, используя libunwind, который поставляется с современными Android NDK (такими как NDK r16b).


// Android NDK r16b contains "libunwind.a" for armeabi-v7a ABI.
// This library is even silently linked in by the ndk-build,
// so we don't have to add it manually in "Android.mk".
// We can use this library, but we need matching headers,
// namely "libunwind.h" and "__libunwind_config.h".
// For NDK r16b, the headers can be fetched here:
// https://android.googlesource.com/platform/external/libunwind_llvm/+/ndk-r16/include/
#include "libunwind.h"

struct BacktraceState {
    const ucontext_t*   signal_ucontext;
    size_t              address_count = 0;
    static const size_t address_count_max = 30;
    uintptr_t           addresses[address_count_max] = {};

    BacktraceState(const ucontext_t* ucontext) : signal_ucontext(ucontext) {}

    bool AddAddress(uintptr_t ip) {
        // No more space in the storage. Fail.
        if (address_count >= address_count_max)
            return false;

        // Add the address to the storage.
        addresses[address_count++] = ip;
        return true;
    }
};

void CaptureBacktraceUsingLibUnwind(BacktraceState* state) {
    assert(state);

    // Initialize unw_context and unw_cursor.
    unw_context_t unw_context = {};
    unw_getcontext(&unw_context);
    unw_cursor_t  unw_cursor = {};
    unw_init_local(&unw_cursor, &unw_context);

    // Get more contexts.
    const ucontext_t* signal_ucontext = state->signal_ucontext;
    assert(signal_ucontext);
    const sigcontext* signal_mcontext = &(signal_ucontext->uc_mcontext);
    assert(signal_mcontext);

    // Set registers.
    unw_set_reg(&unw_cursor, UNW_ARM_R0,  signal_mcontext->arm_r0);
    unw_set_reg(&unw_cursor, UNW_ARM_R1,  signal_mcontext->arm_r1);
    unw_set_reg(&unw_cursor, UNW_ARM_R2,  signal_mcontext->arm_r2);
    unw_set_reg(&unw_cursor, UNW_ARM_R3,  signal_mcontext->arm_r3);
    unw_set_reg(&unw_cursor, UNW_ARM_R4,  signal_mcontext->arm_r4);
    unw_set_reg(&unw_cursor, UNW_ARM_R5,  signal_mcontext->arm_r5);
    unw_set_reg(&unw_cursor, UNW_ARM_R6,  signal_mcontext->arm_r6);
    unw_set_reg(&unw_cursor, UNW_ARM_R7,  signal_mcontext->arm_r7);
    unw_set_reg(&unw_cursor, UNW_ARM_R8,  signal_mcontext->arm_r8);
    unw_set_reg(&unw_cursor, UNW_ARM_R9,  signal_mcontext->arm_r9);
    unw_set_reg(&unw_cursor, UNW_ARM_R10, signal_mcontext->arm_r10);
    unw_set_reg(&unw_cursor, UNW_ARM_R11, signal_mcontext->arm_fp);
    unw_set_reg(&unw_cursor, UNW_ARM_R12, signal_mcontext->arm_ip);
    unw_set_reg(&unw_cursor, UNW_ARM_R13, signal_mcontext->arm_sp);
    unw_set_reg(&unw_cursor, UNW_ARM_R14, signal_mcontext->arm_lr);
    unw_set_reg(&unw_cursor, UNW_ARM_R15, signal_mcontext->arm_pc);

    unw_set_reg(&unw_cursor, UNW_REG_IP, signal_mcontext->arm_pc);
    unw_set_reg(&unw_cursor, UNW_REG_SP, signal_mcontext->arm_sp);

    // unw_step() does not return the first IP,
    // the address of the instruction which caused the crash.
    // Thus let's add this address manually.
    state->AddAddress(signal_mcontext->arm_pc);

    // Unwind frames one by one, going up the frame stack.
    while (unw_step(&unw_cursor) > 0) {
        unw_word_t ip = 0;
        unw_get_reg(&unw_cursor, UNW_REG_IP, &ip);

        bool ok = state->AddAddress(ip);
        if (!ok)
            break;
    }
}

void SigActionHandler(int sig, siginfo_t* info, void* ucontext) {
    const ucontext_t* signal_ucontext = (const ucontext_t*)ucontext;
    assert(signal_ucontext);

    BacktraceState backtrace_state(signal_ucontext);
    CaptureBacktraceUsingLibUnwind(&backtrace_state);

    exit(0);
}

Вот пример приложения для тестирования обратного отслеживания с 3 реализованными методами обратного отслеживания, включая метод, показанный выше.

https://github.com/alexeikh/android-ndk-backtrace-test

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...