Android NDK: возвращаемся
Я разрабатываю нативное приложение, которое работает с Android через NDK. Мне нужно позвонить backtrace()
функционировать, когда происходит сбой. Проблема в том, что нет <execinfo.h>
для НДК.
Есть ли другой способ получить этот обратный след?
8 ответов
backtrace()
это нестандартное расширение Glibc, и даже в этом случае оно несколько шатко в ARM (вам нужно собрать все с -funwind-tables
Я думаю, а у Глибца есть что-то новое?)
Насколько я знаю, эта функция не включена в библиотеку Bionic C, используемую Android.
Вы можете попробовать перенести исходный код Glibc backtrace в ваш проект, а затем восстановить интересные вещи с помощью таблицы раскрутки, но для меня это звучит как тяжелая работа.
Если у вас есть отладочная информация, вы можете попробовать запустить GDB со скриптом, который присоединяется к вашему процессу и печатает обратную трассировку таким образом, но я понятия не имею, работает ли GDB на Android (хотя Android в основном Linux, так что с большим количеством ид, подробности установки могут быть проблематичными?) Вы можете получить дальнейшие результаты, выполнив дамп ядра (поддерживает ли это Bionic?) и проанализируете его по факту.
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());
}
Вот некоторый рабочий и полный код, который реализует dump_stack(), начиная с ответа Евгения Шаповалова и выполняя поиск символов и имен C++ прямо на устройстве. Это решение:
- работает с NDK r10e (вам не нужно полное дерево исходников Android AOSP)
- НЕ требует никаких дополнительных сторонних библиотек (без libunwind, libbacktrace, corkscrew, CallStack)
- НЕ зависит от каких-либо разделяемых библиотек, установленных на устройстве (например, corkscrew, который получил поддержку в Android 5)
- НЕ вынуждает вас отображать адреса на символы на вашем компьютере разработчика; все имена символов отображаются на устройстве Android в вашем коде
Он использует эти средства, которые встроены в NDK:
<unwind.h>
заголовок, который находится в наборе инструментов NDK / dirs (НЕ 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" } }
Вторая проблема более неприятная. Оказывается, существует не один, не два, а три разных несовместимых типа
<unwind.h>
в НДК. И как вы уже догадались,<unwind.h>
в STLport (на самом деле он находится в библиотеке gabi++, которая подходит для поездки, когда вы выбираете STLport) несовместимо. Тот факт, что STLport/gabi++ включает в себя, предшествует тому, что включает в себя набор инструментов (см. Ваш вывод ndk-build-I
опций) означает, что 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 обычно хотят переопределить включаемые файлы набора инструментов.
Наслаждайтесь!
Вот сумасшедший однострочный метод для получения фантастически подробной трассировки стека, которая включает в себя как C/C++ (нативный), так и Java: злоупотребление JNI
env->FindClass(NULL);
Пока ваше приложение откомпилировано, отлажено или иным образом использует Android CheckJNI, этот ошибочный вызов вызовет встроенную в Android проверку JNI, которая выдаст великолепную трассировку стека на консоли (из источника журнала "art"). Эта трассировка стека выполняется внутри Android libart.so
используя все новейшие технологии и навороты, которые нелегко доступны для таких пользователей NDK, как мы.
Вы можете включить CheckJNI даже для отладочных приложений. См. Этот часто задаваемые вопросы Google для деталей.
Я не знаю, работает ли этот трюк с обработчиком SIGSEGV (из SIGSEGV вы можете получить трассировку стека неправильного стека, или, возможно, art не будет запускаться вообще), но стоит попробовать.
Если вам нужно решение, которое делает трассировку стека доступной в вашем коде (например, чтобы вы могли отправить ее по сети или зарегистрировать ее), см. Мой другой ответ на этот же вопрос.
Вы можете использовать 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*)
Вот как вы записываете обратную трассировку на 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 реализованными методами обратной трассировки, включая метод, показанный выше.
Если вы просто хотите получить несколько (например, 2 - 5) самых верхних фреймов вызова, и если ваш GCC достаточно свежий, вы можете рассмотреть возможность использования некоторых встроенных адресов возврата или адреса фрейма.
(Но я мало что знаю об Android, поэтому могу ошибаться)
Бионическийexecinfo.h
заголовок стал общедоступным начиная с уровня API 33 (Android 14), что позволяет собирать обратные трассировки во время выполнения, как в обычном Linux. Пример доступен на странице руководства дляbacktrace
: https://man7.org/linux/man-pages/man3/backtrace.3.html
Для более старых версий Android вы можете повторно использовать код по адресу: https://cs.android.com/android/platform/superproject/+/master:bionic/libc/bionic/execinfo.cpp .