web-dev-qa-db-ja.com

指定されたスタックポインタ(SP)のバックトレースを取得するためにスタックを巻き戻す方法は?

私はこれをAndroid(ARMのみ)用に書いていますが、原則は一般的なLinuxでも同じだと思います。

アプリがクラッシュしたときにログに記録できるように、シグナルハンドラー内からスタックトレースをキャプチャしようとしています。これは私が_<unwind.h>_を使用して思いついたものです。
初期化:

_struct sigaction signalhandlerDescriptor;
memset(&signalhandlerDescriptor, 0, sizeof(signalhandlerDescriptor));
signalhandlerDescriptor.sa_flags = SA_SIGINFO;
signalhandlerDescriptor._u._sa_sigaction = signalHandler;
sigaction(SIGSEGV, &signalhandlerDescriptor, 0);
_

コード自体:

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

問題:unwindCallback_Unwind_GetIP(context)を呼び出してPCレジスタを取得すると、完全なトレースが取得されますシグナルハンドラスタックの場合。これは別のスタックであり、それは明らかに私が望んでいることではありません。そこで、シグナルハンドラーのucontextから取得したPCを提供しようとしましたが、奇妙な結果が得られました。スタックエントリが1つあり、それが正しいエントリです。ただし、2回ログに記録されます(アドレスも同じであるため、シンボリック名検索のバグではありません)。明らかに、それだけでは十分ではありません。スタック全体が必要です。そして、この結果は単なる偶然であるのではないかと思います(つまり、一般的には機能しないはずです。

今、私はスタックポインタも提供する必要があることを読みました。これは明らかにPCと同じようにucontextから取得できます。しかし、私はそれをどうするかわかりません。 __Unwind_Backtrace_を使用する代わりに、手動で巻き戻す必要がありますか?もしそうなら、サンプルコードを教えていただけますか? 1日の大部分を探していましたが、コピーしてプロジェクトに貼り付けることができるものがまだ見つかりませんでした。

価値があるものとして、ここに__Unwind_Backtrace_定義を含む libunwind ソースがあります。その出所を見れば何かわかると思いましたが、思ったよりずっと複雑です。

31
Violet Giraffe

シグナルハンドラのスタックトレースの代わりにSIGSEGVを引き起こしたコードのスタックトレースを取得するには、_ucontext_t_からARMレジスタを取得し、それらを巻き戻しに使用する必要があります。

しかし、_Unwind_Backtrace()で行うのは難しいです。したがって、libc ++(LLVM STL)を使用する場合は、最新のAndroid NDK(at _sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a_)にバンドルされた32ビットARM用のプリコンパイル済みlibunwindを試してください。サンプルコードを次に示します。


_// 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.
}
_

私はまた、より多くのコードとより多くの情報を含むこの私の答えを見てみることをお勧めします:

https://stackoverflow.com/a/50027799/101658

Libstdc ++(GNU STL)を使用する場合は、VasilyGalkinのソリューションを使用してください。

https://stackoverflow.com/a/30515756/101658

、これは別の投稿からのDar Hooのソリューションと同じです:

https://stackoverflow.com/a/48593413/101658

1

まず、「非同期シグナルセーフ」機能のセクションを読む必要があります。

http://man7.org/linux/man-pages/man7/signal.7.html

これが、シグナルハンドラーで安全に呼び出すことができる関数のセット全体です。あなたができる最悪のことは、内部でmalloc()/ free()を呼び出すものを呼び出すことです-または自分でそれを行います。

次に、最初にシグナルハンドラーの外部で動作させます。

第三に、これらはおそらく適切です:

AndroidでC++バックトレースを取得する方法

Android NDK:バックトレースの取得

1
Andrew Henle