Comment décompresser la pile pour obtenir backtrace pour le pointeur de pile spécifié (SP)?
j'écris ceci pour Android (ARM seulement), mais je crois que le principe est le même pour Linux générique aussi.
j'essaye de capturer la trace de la pile depuis le gestionnaire de signal, pour que je puisse la Logger quand mon application crash. C'est ce que j'ai inventé avec <unwind.h>
.
Initialisation:
struct sigaction signalhandlerDescriptor;
memset(&signalhandlerDescriptor, 0, sizeof(signalhandlerDescriptor));
signalhandlerDescriptor.sa_flags = SA_SIGINFO;
signalhandlerDescriptor._u._sa_sigaction = signalHandler;
sigaction(SIGSEGV, &signalhandlerDescriptor, 0);
le code lui-même:
struct BacktraceState
{
void** current;
void** end;
void* pc;
};
inline _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
{
BacktraceState* state = static_cast<BacktraceState*>(arg);
state->pc = (void*)_Unwind_GetIP(context);
if (state->pc)
{
if (state->current == state->end)
return _URC_END_OF_STACK;
else
*state->current++ = reinterpret_cast<void*>(state->pc);
}
return _URC_NO_REASON;
}
inline size_t captureBacktrace(void** addrs, size_t max, unsigned long pc)
{
BacktraceState state = {addrs, addrs + max, (void*)pc};
_Unwind_Backtrace(unwindCallback, &state);
personality_routine();
return state.current - addrs;
}
inline void dumpBacktrace(std::ostream& os, void** addrs, size_t count)
{
for (size_t idx = 0; idx < count; ++idx) {
const void* addr = addrs[idx];
const char* symbol = "";
Dl_info info;
if (dladdr(addr, &info) && info.dli_sname) {
symbol = info.dli_sname;
}
int status = -3;
char * demangledName = abi::__cxa_demangle(symbol, 0, 0, &status);
os << "#" << idx << ": " << addr << " " << (status == 0 ? demangledName : symbol) << "n";
free(demangledName);
}
}
void signalHandler(int sig, siginfo_t *siginfo, void *uctx)
{
ucontext * context = (ucontext*)uctx;
unsigned long PC = context->uc_mcontext.arm_pc;
unsigned long SP = context->uc_mcontext.arm_sp;
Logger() << __PRETTY_FUNCTION__ << "Fatal signal:" << sig;
const size_t maxNumAddresses = 50;
void* addresses[maxNumAddresses];
std::ostringstream oss;
const size_t actualNumAddresses = captureBacktrace(addresses, maxNumAddresses, PC);
dumpBacktrace(oss, addresses, actualNumAddresses);
Logger() << oss.str();
exit(EXIT_FAILURE);
}
problème: si j'obtiens le registre PC par en appelant _Unwind_GetIP(context)
dans unwindCallback
, j'obtiens la trace complète pour la pile du gestionnaire de signal . Qui est une pile séparée, et ce n'est évidemment pas ce que je veux. J'ai donc essayé de fournir le PC pris du ucontext
dans le gestionnaire de signal, et j'ai obtenu un résultat bizarre: j'obtiens une entrée de pile, c'est la bonne entrée - la fonction qui a causé le signal en premier lieu. Mais il est enregistré deux fois (même l'adresse est la même, donc ce n'est pas une recherche de nom symbolique). Évidemment, ce n'est pas suffisant - j'ai besoin de toute la pile. Et je me demande si ce résultat n'est pas simplement accidentel (I. E. ça ne devrait pas marcher en général.
maintenant, j'ai lu que je dois aussi fournir le pointeur de pile , que je peux apparemment obtenir de ucontext
, même que PC. Mais je ne sais pas quoi faire avec elle. Dois-je me détendre manuellement au lieu d'utiliser _Unwind_Backtrace
? Si c'est le cas, Pouvez-vous me donner un exemple de code? J'ai été la recherche de la meilleure partie de la journée, et ne pouvait pas trouver tout ce que je pouvais copier et coller dans mon projet.
pour ce que ça vaut, voici la source libunwind qui contient la définition _Unwind_Backtrace
. J'ai pensé que je pourrais trouver quelque chose si je vois sa source, mais c'est beaucoup plus compliqué que je ne le pensais.
2 réponses
tout d'abord, vous devez lire la section" sécurité du signal async "fonctions:
http://man7.org/linux/man-pages/man7/signal.7.html
c'est l'ensemble des fonctions qui peuvent être activées sans danger par un gestionnaire de signal. La pire chose à faire est d'appeler tout ce qui s'appelle malloc()/free() sous le capot - ou de le faire vous-même.
Deuxièmement, faites-le travailler à l'extérieur d'un manipulateur de signal d'abord.
Troisièmement, ceux-ci sont probablement apropos:
pour obtenir stacktrace de code qui a causé SIGSEGV au lieu de stacktrace du gestionnaire de signal, vous devez obtenir des ARM registers de ucontext_t
et les utiliser pour la déverrouillage.
Mais il est difficile de faire avec _Unwind_Backtrace()
. Ainsi, si vous utilisez libc++ (LLVM STL), mieux essayer précompilé libunwind
pour bras 32-bit, livré avec NDKs Android moderne (à sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a
). Voici un exemple de code.
// This method can only be used on 32-bit ARM with libc++ (LLVM STL).
// 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/
#if _LIBCPP_VERSION && __has_include("libunwind.h")
#include "libunwind.h"
#endif
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;
// Reset the Thumb bit, if it is set.
const uintptr_t thumb_bit = 1;
ip &= ~thumb_bit;
// Ignore null addresses.
if (ip == 0)
return true;
// Finally 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.
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);
// Do something with the backtrace - print, save to file, etc.
}
je vous conseille également de jeter un oeil à ma réponse qui contient plus de code et plus d'info:
https://stackoverflow.com/a/50027799/1016580
si vous utilisez libstdc++ (GNU STL), utilisez la solution de Vasily Galkin:
https://stackoverflow.com/a/30515756/1016580
, qui est la même que la solution de Dar Hoo d'un autre post: