it-swarm.com.ru

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

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

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

40
givi

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

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

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

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

17
ams

У 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());
}
33
Eugene Shapovalov

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

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

Он использует эти средства, которые встроены в NDK:

  • Заголовок <unwind.h>, который находится в наборе инструментов/директорий NDK (НЕ libunwind)
  • dladdr()
  • __cxxabiv1::__cxa_demangle() from <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, вы должны наслаждаться особым видом веселья (подробнее ниже).

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

Многие люди отмечают, что скрипт 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 to sources/cxx-stl/gabi++/include/unwind.h.NOT
    • sources/cxx-stl/gabi++/include/unwind-arm.h to sources/cxx-stl/gabi++/include/unwind-arm.h.NOT
    • sources/cxx-stl/gabi++/include/unwind-itanium.h to sources/cxx-stl/gabi++/include/unwind-itanium.h.NOT

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

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

19
Louis Semprini

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

env->FindClass(NULL);

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

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

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

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

6
Louis Semprini

Вы можете использовать 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*)
5
Vladimir Kunschikov

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

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

1
Basile Starynkevitch

Вот как вы записываете обратную трассировку на 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

0
Alexei Khlebnikov