Подтвердить что ты не робот

Android NDK: получение обратной линии

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

Есть ли другой способ получить обратную трассировку?

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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());
}

Ответ 3

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

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

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

  • <unwind.h>, который находится в NDK toolchain/dirs (NOT libunwind)
  • dladdr()
  • __cxxabiv1::__cxa_demangle() от <cxxabi.h> (см. примечание STLport ниже)

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

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

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

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

Поддержка преобразования имени символа на С++ зависит от функции __cxxabiv1::__cxa_demangle(), которая исходит из С++ 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 script разделяет символы из вашего 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"
    }
    }
    
  • Вторая проблема более противная. Оказывается, не один, не два, а три разных, несовместимых типа <unwind.h> в NDK. И вы догадались, что <unwind.h> в STLport (на самом деле это в библиотеке gabi ++, которая подходит для поездки при выборе STLport) несовместима. Тот факт, что STLport/gabi ++ включает в себя до появления инструментальной привязки (см. Параметры вывода ndk-build -I), означает, что 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 вообще хотят переопределить файлы с инструментальной привязкой.

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

Ответ 4

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

env->FindClass(NULL);

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

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

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

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

Ответ 5

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

#include <utils/CallStack.h>

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

Результаты будут нуждаться в de-mangling c++filt или что-то подобное:

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

вы @work > $С++ filter _ZN7android15TimedEventQueue11threadEntryEv _ZN7android15TimedEventQueue13ThreadWrapperEPv

    android::TimedEventQueue::threadEntry()
    android::TimedEventQueue::ThreadWrapper(void*)

Ответ 6

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

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