From 4668e1a8939d34992fe4e8b49e60e646246adb69 Mon Sep 17 00:00:00 2001 From: Bryan Moffatt Date: Tue, 23 Aug 2022 00:40:32 -0700 Subject: [PATCH] update backward.h to v1.6 upstream release (#154) --- ci/codebuild/amazonlinux-2.yml | 2 +- src/backward.h | 513 ++++++++++++++++++++-------- tests/CMakeLists.txt | 1 + tests/resources/lambda_function.cpp | 8 + tests/runtime_tests.cpp | 27 +- 5 files changed, 416 insertions(+), 135 deletions(-) diff --git a/ci/codebuild/amazonlinux-2.yml b/ci/codebuild/amazonlinux-2.yml index 71c0b59..7a5441c 100644 --- a/ci/codebuild/amazonlinux-2.yml +++ b/ci/codebuild/amazonlinux-2.yml @@ -5,7 +5,7 @@ phases: - yum install -y zip build: commands: - - cmake3 -S . -B build -DENABLE_TESTS=ON -DTEST_RESOURCE_PREFIX=al2arm -GNinja + - cmake3 -S . -B build -DBUILD_SHARED_LIBS=ON -DENABLE_TESTS=ON -DTEST_RESOURCE_PREFIX=al2arm -GNinja - cd build - ninja-build - ninja-build aws-lambda-package-lambda-test-fun diff --git a/src/backward.h b/src/backward.h index 29beb81..c421378 100644 --- a/src/backward.h +++ b/src/backward.h @@ -1,4 +1,3 @@ -// clang-format off /* * backward.hpp * Copyright 2013 Google Inc. All Rights Reserved. @@ -36,6 +35,9 @@ # define BACKWARD_CXX11 # define BACKWARD_ATLEAST_CXX11 # define BACKWARD_ATLEAST_CXX98 +# if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define BACKWARD_ATLEAST_CXX17 +# endif # else # define BACKWARD_CXX98 # define BACKWARD_ATLEAST_CXX98 @@ -50,6 +52,9 @@ // #define BACKWARD_SYSTEM_DARWIN // - specialization for Mac OS X 10.5 and later. // +// #define BACKWARD_SYSTEM_WINDOWS +// - specialization for Windows (Clang 9 and MSVC2017) +// // #define BACKWARD_SYSTEM_UNKNOWN // - placebo implementation, does nothing. // @@ -85,6 +90,8 @@ #include #include #include +#include +#include #if defined(BACKWARD_SYSTEM_LINUX) @@ -98,6 +105,11 @@ // exception. // - normally libgcc is already linked to your program by default. // +// #define BACKWARD_HAS_LIBUNWIND 1 +// - libunwind provides, in some cases, a more accurate stacktrace as it knows +// to decode signal handler frames and lets us edit the context registers when +// unwinding, allowing stack traces over bad function references. +// // #define BACKWARD_HAS_BACKTRACE == 1 // - backtrace seems to be a little bit more portable than libunwind, but on // linux, it uses unwind anyway, but abstract away a tiny information that is @@ -110,10 +122,13 @@ // Note that only one of the define should be set to 1 at a time. // # if BACKWARD_HAS_UNWIND == 1 +# elif BACKWARD_HAS_LIBUNWIND == 1 # elif BACKWARD_HAS_BACKTRACE == 1 # else # undef BACKWARD_HAS_UNWIND # define BACKWARD_HAS_UNWIND 1 +# undef BACKWARD_HAS_LIBUNWIND +# define BACKWARD_HAS_LIBUNWIND 0 # undef BACKWARD_HAS_BACKTRACE # define BACKWARD_HAS_BACKTRACE 0 # endif @@ -126,9 +141,10 @@ // - object filename // - function name // - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) -// - variables name and values (if not optimized out) +// - line and column numbers +// - source code snippet (assuming the file is accessible) +// - variable names (if not optimized out) +// - variable values (not supported by backward-cpp) // - You need to link with the lib "dw": // - apt-get install libdw-dev // - g++/clang++ -ldw ... @@ -138,8 +154,8 @@ // - object filename // - function name // - source filename -// - line numbers -// - source code snippet (assuming the file is accessible) +// - line numbers +// - source code snippet (assuming the file is accessible) // - You need to link with the lib "bfd": // - apt-get install binutils-dev // - g++/clang++ -lbfd ... @@ -151,7 +167,8 @@ // - source filename // - line and column numbers // - source code snippet (assuming the file is accessible) -// - variables name and values (if not optimized out) +// - variable names (if not optimized out) +// - variable values (not supported by backward-cpp) // - You need to link with the lib "dwarf": // - apt-get install libdwarf-dev // - g++/clang++ -ldwarf ... @@ -240,13 +257,10 @@ # endif # endif -# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -# include -# endif - # if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) // then we shall rely on backtrace # include +# include # endif #endif // defined(BACKWARD_SYSTEM_LINUX) @@ -258,10 +272,16 @@ // #define BACKWARD_HAS_UNWIND 1 // - unwind comes from libgcc, but I saw an equivalent inside clang itself. // - with unwind, the stacktrace is as accurate as it can possibly be, since -// this is used by the C++ runtime in gcc/clang for stack unwinding on +// this is used by the C++ runtine in gcc/clang for stack unwinding on // exception. // - normally libgcc is already linked to your program by default. // +// #define BACKWARD_HAS_LIBUNWIND 1 +// - libunwind comes from clang, which implements an API compatible version. +// - libunwind provides, in some cases, a more accurate stacktrace as it knows +// to decode signal handler frames and lets us edit the context registers when +// unwinding, allowing stack traces over bad function references. +// // #define BACKWARD_HAS_BACKTRACE == 1 // - backtrace is available by default, though it does not produce as much // information as another library might. @@ -273,11 +293,14 @@ // # if BACKWARD_HAS_UNWIND == 1 # elif BACKWARD_HAS_BACKTRACE == 1 +# elif BACKWARD_HAS_LIBUNWIND == 1 # else # undef BACKWARD_HAS_UNWIND # define BACKWARD_HAS_UNWIND 1 # undef BACKWARD_HAS_BACKTRACE # define BACKWARD_HAS_BACKTRACE 0 +# undef BACKWARD_HAS_LIBUNWIND +# define BACKWARD_HAS_LIBUNWIND 0 # endif // On Darwin, backward can extract detailed information about a stack trace @@ -296,7 +319,6 @@ # undef BACKWARD_HAS_BACKTRACE_SYMBOL # define BACKWARD_HAS_BACKTRACE_SYMBOL 1 # endif - # include # include # include @@ -315,14 +337,16 @@ # include # include -# include +# include typedef SSIZE_T ssize_t; -# define NOMINMAX -# include +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include # include -# include +# include # include # ifndef __clang__ @@ -330,8 +354,10 @@ typedef SSIZE_T ssize_t; # define NOINLINE __declspec(noinline) # endif -# pragma comment(lib, "psapi.lib") -# pragma comment(lib, "dbghelp.lib") +# ifdef _MSC_VER +# pragma comment(lib, "psapi.lib") +# pragma comment(lib, "dbghelp.lib") +# endif // Comment / packing is from stackoverflow: // https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 @@ -376,6 +402,11 @@ extern "C" uintptr_t _Unwind_GetIPInfo(_Unwind_Context*, int*); #endif // BACKWARD_HAS_UNWIND == 1 +#if BACKWARD_HAS_LIBUNWIND == 1 +# define UNW_LOCAL_ONLY +# include +#endif // BACKWARD_HAS_LIBUNWIND == 1 + #ifdef BACKWARD_ATLEAST_CXX11 # include # include // for std::swap @@ -567,7 +598,7 @@ class handle { void update(T new_val) { _val = new_val; - _empty = static_cast(new_val); + _empty = !static_cast(new_val); } operator const dummy*() const @@ -728,14 +759,14 @@ class StackTraceImpl { size_t size() const { return 0; } Trace operator[](size_t) const { return Trace(); } size_t load_here(size_t = 0) { return 0; } - size_t load_from(void*, size_t = 0) { return 0; } + size_t load_from(void*, size_t = 0, void* = nullptr, void* = nullptr) { return 0; } size_t thread_id() const { return 0; } void skip_n_firsts(size_t) {} }; class StackTraceImplBase { public: - StackTraceImplBase() : _thread_id(0), _skip(0) {} + StackTraceImplBase() : _thread_id(0), _skip(0), _context(nullptr), _error_addr(nullptr) {} size_t thread_id() const { return _thread_id; } @@ -764,16 +795,24 @@ class StackTraceImplBase { #endif } + void set_context(void* context) { _context = context; } + void* context() const { return _context; } + + void set_error_addr(void* error_addr) { _error_addr = error_addr; } + void* error_addr() const { return _error_addr; } + size_t skip_n_firsts() const { return _skip; } private: size_t _thread_id; size_t _skip; + void* _context; + void* _error_addr; }; class StackTraceImplHolder : public StackTraceImplBase { public: - size_t size() const { return _stacktrace.size() ? _stacktrace.size() - skip_n_firsts() : 0; } + size_t size() const { return (_stacktrace.size() >= skip_n_firsts()) ? _stacktrace.size() - skip_n_firsts() : 0; } Trace operator[](size_t idx) const { if (idx >= size()) { @@ -861,9 +900,11 @@ template <> class StackTraceImpl : public StackTraceImplHolder { public: NOINLINE - size_t load_here(size_t depth = 32) + size_t load_here(size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { load_thread_info(); + set_context(context); + set_error_addr(error_addr); if (depth == 0) { return 0; } @@ -873,9 +914,9 @@ class StackTraceImpl : public StackTraceImplHolder { skip_n_firsts(0); return size(); } - size_t load_from(void* addr, size_t depth = 32) + size_t load_from(void* addr, size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { - load_here(depth + 8); + load_here(depth + 8, context, error_addr); for (size_t i = 0; i < _stacktrace.size(); ++i) { if (_stacktrace[i] == addr) { @@ -897,14 +938,182 @@ class StackTraceImpl : public StackTraceImplHolder { }; }; +#elif BACKWARD_HAS_LIBUNWIND == 1 + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + __attribute__((noinline)) size_t load_here(size_t depth = 32, void* _context = nullptr, void* _error_addr = nullptr) + { + set_context(_context); + set_error_addr(_error_addr); + load_thread_info(); + if (depth == 0) { + return 0; + } + _stacktrace.resize(depth + 1); + + int result = 0; + + unw_context_t ctx; + size_t index = 0; + + // Add the tail call. If the Instruction Pointer is the crash address it + // means we got a bad function pointer dereference, so we "unwind" the + // bad pointer manually by using the return address pointed to by the + // Stack Pointer as the Instruction Pointer and letting libunwind do + // the rest + + if (context()) { + ucontext_t* uctx = reinterpret_cast(context()); +# ifdef REG_RIP // x86_64 + if (uctx->uc_mcontext.gregs[REG_RIP] == reinterpret_cast(error_addr())) { + uctx->uc_mcontext.gregs[REG_RIP] = *reinterpret_cast(uctx->uc_mcontext.gregs[REG_RSP]); + } + _stacktrace[index] = reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); + ++index; + ctx = *reinterpret_cast(uctx); +# elif defined(REG_EIP) // x86_32 + if (uctx->uc_mcontext.gregs[REG_EIP] == reinterpret_cast(error_addr())) { + uctx->uc_mcontext.gregs[REG_EIP] = *reinterpret_cast(uctx->uc_mcontext.gregs[REG_ESP]); + } + _stacktrace[index] = reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); + ++index; + ctx = *reinterpret_cast(uctx); +# elif defined(__arm__) + // libunwind uses its own context type for ARM unwinding. + // Copy the registers from the signal handler's context so we can + // unwind + unw_getcontext(&ctx); + ctx.regs[UNW_ARM_R0] = uctx->uc_mcontext.arm_r0; + ctx.regs[UNW_ARM_R1] = uctx->uc_mcontext.arm_r1; + ctx.regs[UNW_ARM_R2] = uctx->uc_mcontext.arm_r2; + ctx.regs[UNW_ARM_R3] = uctx->uc_mcontext.arm_r3; + ctx.regs[UNW_ARM_R4] = uctx->uc_mcontext.arm_r4; + ctx.regs[UNW_ARM_R5] = uctx->uc_mcontext.arm_r5; + ctx.regs[UNW_ARM_R6] = uctx->uc_mcontext.arm_r6; + ctx.regs[UNW_ARM_R7] = uctx->uc_mcontext.arm_r7; + ctx.regs[UNW_ARM_R8] = uctx->uc_mcontext.arm_r8; + ctx.regs[UNW_ARM_R9] = uctx->uc_mcontext.arm_r9; + ctx.regs[UNW_ARM_R10] = uctx->uc_mcontext.arm_r10; + ctx.regs[UNW_ARM_R11] = uctx->uc_mcontext.arm_fp; + ctx.regs[UNW_ARM_R12] = uctx->uc_mcontext.arm_ip; + ctx.regs[UNW_ARM_R13] = uctx->uc_mcontext.arm_sp; + ctx.regs[UNW_ARM_R14] = uctx->uc_mcontext.arm_lr; + ctx.regs[UNW_ARM_R15] = uctx->uc_mcontext.arm_pc; + + // If we have crashed in the PC use the LR instead, as this was + // a bad function dereference + if (reinterpret_cast(error_addr()) == uctx->uc_mcontext.arm_pc) { + ctx.regs[UNW_ARM_R15] = uctx->uc_mcontext.arm_lr - sizeof(unsigned long); + } + _stacktrace[index] = reinterpret_cast(ctx.regs[UNW_ARM_R15]); + ++index; +# elif defined(__APPLE__) && defined(__x86_64__) + unw_getcontext(&ctx); + // OS X's implementation of libunwind uses its own context object + // so we need to convert the passed context to libunwind's format + // (information about the data layout taken from unw_getcontext.s + // in Apple's libunwind source + ctx.data[0] = uctx->uc_mcontext->__ss.__rax; + ctx.data[1] = uctx->uc_mcontext->__ss.__rbx; + ctx.data[2] = uctx->uc_mcontext->__ss.__rcx; + ctx.data[3] = uctx->uc_mcontext->__ss.__rdx; + ctx.data[4] = uctx->uc_mcontext->__ss.__rdi; + ctx.data[5] = uctx->uc_mcontext->__ss.__rsi; + ctx.data[6] = uctx->uc_mcontext->__ss.__rbp; + ctx.data[7] = uctx->uc_mcontext->__ss.__rsp; + ctx.data[8] = uctx->uc_mcontext->__ss.__r8; + ctx.data[9] = uctx->uc_mcontext->__ss.__r9; + ctx.data[10] = uctx->uc_mcontext->__ss.__r10; + ctx.data[11] = uctx->uc_mcontext->__ss.__r11; + ctx.data[12] = uctx->uc_mcontext->__ss.__r12; + ctx.data[13] = uctx->uc_mcontext->__ss.__r13; + ctx.data[14] = uctx->uc_mcontext->__ss.__r14; + ctx.data[15] = uctx->uc_mcontext->__ss.__r15; + ctx.data[16] = uctx->uc_mcontext->__ss.__rip; + + // If the IP is the same as the crash address we have a bad function + // dereference The caller's address is pointed to by %rsp, so we + // dereference that value and set it to be the next frame's IP. + if (uctx->uc_mcontext->__ss.__rip == reinterpret_cast<__uint64_t>(error_addr())) { + ctx.data[16] = *reinterpret_cast<__uint64_t*>(uctx->uc_mcontext->__ss.__rsp); + } + _stacktrace[index] = reinterpret_cast(ctx.data[16]); + ++index; +# elif defined(__APPLE__) + unw_getcontext(&ctx) + // TODO: Convert the ucontext_t to libunwind's unw_context_t like + // we do in 64 bits + if (ctx.uc_mcontext->__ss.__eip == reinterpret_cast(error_addr())) + { + ctx.uc_mcontext->__ss.__eip = ctx.uc_mcontext->__ss.__esp; + } + _stacktrace[index] = reinterpret_cast(ctx.uc_mcontext->__ss.__eip); + ++index; +# endif + } + + unw_cursor_t cursor; + if (context()) { +# if defined(UNW_INIT_SIGNAL_FRAME) + result = unw_init_local2(&cursor, &ctx, UNW_INIT_SIGNAL_FRAME); +# else + result = unw_init_local(&cursor, &ctx); +# endif + } + else { + unw_getcontext(&ctx); + ; + result = unw_init_local(&cursor, &ctx); + } + + if (result != 0) + return 1; + + unw_word_t ip = 0; + + while (index <= depth && unw_step(&cursor) > 0) { + result = unw_get_reg(&cursor, UNW_REG_IP, &ip); + if (result == 0) { + _stacktrace[index] = reinterpret_cast(--ip); + ++index; + } + } + --index; + + _stacktrace.resize(index + 1); + skip_n_firsts(0); + return size(); + } + + size_t load_from(void* addr, size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) + { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + _stacktrace[i] = (void*)((uintptr_t)_stacktrace[i]); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } +}; + #elif defined(BACKWARD_HAS_BACKTRACE) template <> class StackTraceImpl : public StackTraceImplHolder { public: NOINLINE - size_t load_here(size_t depth = 32) + size_t load_here(size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { + set_context(context); + set_error_addr(error_addr); load_thread_info(); if (depth == 0) { return 0; @@ -916,9 +1125,9 @@ class StackTraceImpl : public StackTraceImplHolder { return size(); } - size_t load_from(void* addr, size_t depth = 32) + size_t load_from(void* addr, size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { - load_here(depth + 8); + load_here(depth + 8, context, error_addr); for (size_t i = 0; i < _stacktrace.size(); ++i) { if (_stacktrace[i] == addr) { @@ -945,9 +1154,10 @@ class StackTraceImpl : public StackTraceImplHolder { void set_thread_handle(HANDLE handle) { thd_ = handle; } NOINLINE - size_t load_here(size_t depth = 32) + size_t load_here(size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { - + set_context(static_cast(context)); + set_error_addr(error_addr); CONTEXT localCtx; // used when no context is provided if (depth == 0) { @@ -1009,9 +1219,9 @@ class StackTraceImpl : public StackTraceImplHolder { return size(); } - size_t load_from(void* addr, size_t depth = 32) + size_t load_from(void* addr, size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { - load_here(depth + 8); + load_here(depth + 8, context, error_addr); for (size_t i = 0; i < _stacktrace.size(); ++i) { if (_stacktrace[i] == addr) { @@ -1037,24 +1247,24 @@ class StackTrace : public StackTraceImpl { /*************** TRACE RESOLVER ***************/ -template -class TraceResolverImpl; +class TraceResolverImplBase { +public: + virtual ~TraceResolverImplBase() {} -#ifdef BACKWARD_SYSTEM_UNKNOWN + virtual void load_addresses(void* const* addresses, int address_count) + { + (void)addresses; + (void)address_count; + } -template <> -class TraceResolverImpl { -public: template - void load_stacktrace(ST&) + void load_stacktrace(ST& st) { + load_addresses(st.begin(), (int)st.size()); } - ResolvedTrace resolve(ResolvedTrace t) { return t; } -}; -#endif + virtual ResolvedTrace resolve(ResolvedTrace t) { return t; } -class TraceResolverImplBase { protected: std::string demangle(const char* funcname) { return _demangler.demangle(funcname); } @@ -1062,6 +1272,17 @@ class TraceResolverImplBase { details::demangler _demangler; }; +template +class TraceResolverImpl; + +#ifdef BACKWARD_SYSTEM_UNKNOWN + +template <> +class TraceResolverImpl : public TraceResolverImplBase { +}; + +#endif + #ifdef BACKWARD_SYSTEM_LINUX class TraceResolverLinuxBase : public TraceResolverImplBase { @@ -1078,6 +1299,12 @@ class TraceResolverLinuxBase : public TraceResolverImplBase { // variable; In that case, we actually open /proc/self/exe, which // is always the actual executable (even if it was deleted/replaced!) // but display the path that /proc/self/exe links to. + // However, this right away reduces probability of successful symbol + // resolution, because libbfd may try to find *.debug files in the + // same dir, in case symbols are stripped. As a result, it may try + // to find a file /proc/self/.debug, which obviously does + // not exist. /proc/self/exe is a last resort. First load attempt + // should go for the original executable file path. symbol_info.dli_fname = "/proc/self/exe"; return exec_path_; } @@ -1129,17 +1356,15 @@ class TraceResolverLinuxImpl; template <> class TraceResolverLinuxImpl : public TraceResolverLinuxBase { public: - template - void load_stacktrace(ST& st) + void load_addresses(void* const* addresses, int address_count) override { - using namespace details; - if (st.size() == 0) { + if (address_count == 0) { return; } - _symbols.reset(backtrace_symbols(st.begin(), (int)st.size())); + _symbols.reset(backtrace_symbols(addresses, address_count)); } - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { char* filename = _symbols[trace.idx]; char* funcname = filename; @@ -1177,12 +1402,7 @@ class TraceResolverLinuxImpl : public TraceResolverL public: TraceResolverLinuxImpl() : _bfd_loaded(false) {} - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { Dl_info symbol_info; @@ -1213,9 +1433,45 @@ class TraceResolverLinuxImpl : public TraceResolverL } trace.object_filename = resolve_exec_path(symbol_info); - bfd_fileobject& fobj = load_object_with_bfd(symbol_info.dli_fname); - if (!fobj.handle) { - return trace; // sad, we couldn't load the object :( + bfd_fileobject* fobj; + // Before rushing to resolution need to ensure the executable + // file still can be used. For that compare inode numbers of + // what is stored by the executable's file path, and in the + // dli_fname, which not necessarily equals to the executable. + // It can be a shared library, or /proc/self/exe, and in the + // latter case has drawbacks. See the exec path resolution for + // details. In short - the dli object should be used only as + // the last resort. + // If inode numbers are equal, it is known dli_fname and the + // executable file are the same. This is guaranteed by Linux, + // because if the executable file is changed/deleted, it will + // be done in a new inode. The old file will be preserved in + // /proc/self/exe, and may even have inode 0. The latter can + // happen if the inode was actually reused, and the file was + // kept only in the main memory. + // + struct stat obj_stat; + struct stat dli_stat; + if (stat(trace.object_filename.c_str(), &obj_stat) == 0 && stat(symbol_info.dli_fname, &dli_stat) == 0 && + obj_stat.st_ino == dli_stat.st_ino) { + // The executable file, and the shared object containing the + // address are the same file. Safe to use the original path. + // this is preferable. Libbfd will search for stripped debug + // symbols in the same directory. + fobj = load_object_with_bfd(trace.object_filename); + } + else { + // The original object file was *deleted*! The only hope is + // that the debug symbols are either inside the shared + // object file, or are in the same directory, and this is + // not /proc/self/exe. + fobj = nullptr; + } + if (fobj == nullptr || !fobj->handle) { + fobj = load_object_with_bfd(symbol_info.dli_fname); + if (!fobj->handle) { + return trace; + } } find_sym_result* details_selected; // to be filled. @@ -1346,7 +1602,7 @@ class TraceResolverLinuxImpl : public TraceResolverL typedef details::hashtable::type fobj_bfd_map_t; fobj_bfd_map_t _fobj_bfd_map; - bfd_fileobject& load_object_with_bfd(const std::string& filename_object) + bfd_fileobject* load_object_with_bfd(const std::string& filename_object) { using namespace details; @@ -1358,11 +1614,11 @@ class TraceResolverLinuxImpl : public TraceResolverL fobj_bfd_map_t::iterator it = _fobj_bfd_map.find(filename_object); if (it != _fobj_bfd_map.end()) { - return it->second; + return &it->second; } // this new object is empty for now. - bfd_fileobject& r = _fobj_bfd_map[filename_object]; + bfd_fileobject* r = &_fobj_bfd_map[filename_object]; // we do the work temporary in this one; bfd_handle_t bfd_handle; @@ -1407,9 +1663,9 @@ class TraceResolverLinuxImpl : public TraceResolverL return r; // damned, that's a stripped file that you got there! } - r.handle = move(bfd_handle); - r.symtab = move(symtab); - r.dynamic_symtab = move(dynamic_symtab); + r->handle = move(bfd_handle); + r->symtab = move(symtab); + r->dynamic_symtab = move(dynamic_symtab); return r; } @@ -1428,15 +1684,15 @@ class TraceResolverLinuxImpl : public TraceResolverL find_sym_result result; }; - find_sym_result find_symbol_details(bfd_fileobject& fobj, void* addr, void* base_addr) + find_sym_result find_symbol_details(bfd_fileobject* fobj, void* addr, void* base_addr) { find_sym_context context; context.self = this; - context.fobj = &fobj; + context.fobj = fobj; context.addr = addr; context.base_addr = base_addr; context.result.found = false; - bfd_map_over_sections(fobj.handle.get(), &find_in_section_trampoline, static_cast(&context)); + bfd_map_over_sections(fobj->handle.get(), &find_in_section_trampoline, static_cast(&context)); return context.result; } @@ -1446,7 +1702,7 @@ class TraceResolverLinuxImpl : public TraceResolverL context->self->find_in_section( reinterpret_cast(context->addr), reinterpret_cast(context->base_addr), - *context->fobj, + context->fobj, section, context->result); } @@ -1454,7 +1710,7 @@ class TraceResolverLinuxImpl : public TraceResolverL void find_in_section( bfd_vma addr, bfd_vma base_addr, - bfd_fileobject& fobj, + bfd_fileobject* fobj, asection* section, find_sym_result& result) { @@ -1462,14 +1718,14 @@ class TraceResolverLinuxImpl : public TraceResolverL return; # ifdef bfd_get_section_flags - if ((bfd_get_section_flags(fobj.handle.get(), section) & SEC_ALLOC) == 0) + if ((bfd_get_section_flags(fobj->handle.get(), section) & SEC_ALLOC) == 0) # else if ((bfd_section_flags(section) & SEC_ALLOC) == 0) # endif return; // a debug section is never loaded automatically. # ifdef bfd_get_section_vma - bfd_vma sec_addr = bfd_get_section_vma(fobj.handle.get(), section); + bfd_vma sec_addr = bfd_get_section_vma(fobj->handle.get(), section); # else bfd_vma sec_addr = bfd_section_vma(section); # endif @@ -1491,22 +1747,22 @@ class TraceResolverLinuxImpl : public TraceResolverL # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" # endif - if (!result.found && fobj.symtab) { + if (!result.found && fobj->symtab) { result.found = bfd_find_nearest_line( - fobj.handle.get(), + fobj->handle.get(), section, - fobj.symtab.get(), + fobj->symtab.get(), addr - sec_addr, &result.filename, &result.funcname, &result.line); } - if (!result.found && fobj.dynamic_symtab) { + if (!result.found && fobj->dynamic_symtab) { result.found = bfd_find_nearest_line( - fobj.handle.get(), + fobj->handle.get(), section, - fobj.dynamic_symtab.get(), + fobj->dynamic_symtab.get(), addr - sec_addr, &result.filename, &result.funcname, @@ -1517,14 +1773,14 @@ class TraceResolverLinuxImpl : public TraceResolverL # endif } - ResolvedTrace::source_locs_t backtrace_inliners(bfd_fileobject& fobj, find_sym_result previous_result) + ResolvedTrace::source_locs_t backtrace_inliners(bfd_fileobject* fobj, find_sym_result previous_result) { // This function can be called ONLY after a SUCCESSFUL call to // find_symbol_details. The state is global to the bfd_handle. ResolvedTrace::source_locs_t results; while (previous_result.found) { find_sym_result result; - result.found = bfd_find_inliner_info(fobj.handle.get(), &result.filename, &result.funcname, &result.line); + result.found = bfd_find_inliner_info(fobj->handle.get(), &result.filename, &result.funcname, &result.line); if (result.found) /* and not ( cstrings_eq(previous_result.filename, @@ -1565,12 +1821,7 @@ class TraceResolverLinuxImpl : public TraceResolverLi public: TraceResolverLinuxImpl() : _dwfl_handle_initialized(false) {} - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { using namespace details; @@ -1861,9 +2112,9 @@ class TraceResolverLinuxImpl : public TraceResolverLi static const char* die_call_file(Dwarf_Die* die) { Dwarf_Attribute attr_mem; - Dwarf_Sword file_idx = 0; + Dwarf_Word file_idx = 0; - dwarf_formsdata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx); + dwarf_formudata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx); if (file_idx == 0) { return 0; @@ -1894,12 +2145,7 @@ class TraceResolverLinuxImpl : public TraceResolve public: TraceResolverLinuxImpl() : _dwarf_loaded(false) {} - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { // trace.addr is a virtual address in memory pointing to some code. // Let's try to find from which loaded object it comes from. @@ -2901,7 +3147,7 @@ class TraceResolverLinuxImpl : public TraceResolve trace.object_function = demangler.demangle(linkage); dwarf_dealloc(dwarf, linkage, DW_DLA_STRING); } - dwarf_dealloc(dwarf, name, DW_DLA_ATTR); + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); } break; @@ -3112,12 +3358,12 @@ class TraceResolverLinuxImpl : public TraceResolve { Dwarf_Attribute attr_mem; Dwarf_Error error = DW_DLE_NE; - Dwarf_Signed file_index; + Dwarf_Unsigned file_index; std::string file; if (dwarf_attr(die, DW_AT_call_file, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formsdata(attr_mem, &file_index, &error) != DW_DLV_OK) { + if (dwarf_formudata(attr_mem, &file_index, &error) != DW_DLV_OK) { file_index = 0; } dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); @@ -3129,8 +3375,9 @@ class TraceResolverLinuxImpl : public TraceResolve char** srcfiles = 0; Dwarf_Signed file_count = 0; if (dwarf_srcfiles(cu_die, &srcfiles, &file_count, &error) == DW_DLV_OK) { - if (file_index <= file_count) + if (file_count > 0 && file_index <= static_cast(file_count)) { file = std::string(srcfiles[file_index - 1]); + } // Deallocate all strings! for (int i = 0; i < file_count; ++i) { @@ -3259,17 +3506,15 @@ class TraceResolverDarwinImpl; template <> class TraceResolverDarwinImpl : public TraceResolverImplBase { public: - template - void load_stacktrace(ST& st) + void load_addresses(void* const* addresses, int address_count) override { - using namespace details; - if (st.size() == 0) { + if (address_count == 0) { return; } - _symbols.reset(backtrace_symbols(st.begin(), (int)st.size())); + _symbols.reset(backtrace_symbols(addresses, address_count)); } - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { // parse: // + @@ -3368,9 +3613,9 @@ class get_mod_info { ret.base_address = mi.lpBaseOfDll; ret.load_size = mi.SizeOfImage; - GetModuleFileNameEx(process, module, temp, sizeof(temp)); + GetModuleFileNameExA(process, module, temp, sizeof(temp)); ret.image_name = temp; - GetModuleBaseName(process, module, temp, sizeof(temp)); + GetModuleBaseNameA(process, module, temp, sizeof(temp)); ret.module_name = temp; std::vector img(ret.image_name.begin(), ret.image_name.end()); std::vector mod(ret.module_name.begin(), ret.module_name.end()); @@ -3380,7 +3625,7 @@ class get_mod_info { }; template <> -class TraceResolverImpl { +class TraceResolverImpl : public TraceResolverImplBase { public: TraceResolverImpl() { @@ -3404,11 +3649,6 @@ class TraceResolverImpl { image_type = h->FileHeader.Machine; } - template - void load_stacktrace(ST&) - { - } - static const int max_sym_len = 255; struct symbol_t { SYMBOL_INFO sym; @@ -3417,31 +3657,32 @@ class TraceResolverImpl { DWORD64 displacement; - ResolvedTrace resolve(ResolvedTrace t) + ResolvedTrace resolve(ResolvedTrace t) override { HANDLE process = GetCurrentProcess(); char name[256]; - memset(&sym, 0, sizeof sym); + memset(&sym, 0, sizeof(sym)); sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO); sym.sym.MaxNameLen = max_sym_len; if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) { // TODO: error handling everywhere - LPTSTR lpMsgBuf; + char* lpMsgBuf; DWORD dw = GetLastError(); - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&lpMsgBuf, - 0, - NULL); - - printf(lpMsgBuf); + if (FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char*)&lpMsgBuf, + 0, + NULL)) { + std::fprintf(stderr, "%s\n", lpMsgBuf); + LocalFree(lpMsgBuf); + } // abort(); } @@ -3691,7 +3932,7 @@ class cfile_streambuf : public std::streambuf { int_type underflow() override { return traits_type::eof(); } int_type overflow(int_type ch) override { - if (traits_type::not_eof(ch) && fwrite(&ch, sizeof ch, 1, sink) == 1) { + if (traits_type::not_eof(ch) && fputc(ch, sink) != EOF) { return ch; } return traits_type::eof(); @@ -4029,11 +4270,17 @@ class SignalHandling { # elif defined(__arm__) error_addr = reinterpret_cast(uctx->uc_mcontext.arm_pc); # elif defined(__aarch64__) +# if defined(__APPLE__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__pc); +# else error_addr = reinterpret_cast(uctx->uc_mcontext.pc); +# endif # elif defined(__mips__) error_addr = reinterpret_cast(reinterpret_cast(&uctx->uc_mcontext)->sc_pc); # elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) error_addr = reinterpret_cast(uctx->uc_mcontext.regs->nip); +# elif defined(__riscv) + error_addr = reinterpret_cast(uctx->uc_mcontext.__gregs[REG_PC]); # elif defined(__s390x__) error_addr = reinterpret_cast(uctx->uc_mcontext.psw.addr); # elif defined(__APPLE__) && defined(__x86_64__) @@ -4044,10 +4291,10 @@ class SignalHandling { # warning ":/ sorry, ain't know no nothing none not of your architecture!" # endif if (error_addr) { - st.load_from(error_addr, 32); + st.load_from(error_addr, 32, reinterpret_cast(uctx), info->si_addr); } else { - st.load_here(32); + st.load_here(32, reinterpret_cast(uctx), info->si_addr); } Printer printer; @@ -4117,8 +4364,10 @@ class SignalHandling { signal(SIGABRT, signal_handler); _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - set_terminate(&terminator); - set_unexpected(&terminator); + std::set_terminate(&terminator); +# ifndef BACKWARD_ATLEAST_CXX17 + std::set_unexpected(&terminator); +# endif _set_purecall_handler(&terminator); _set_invalid_parameter_handler(&invalid_parameter_handler); } @@ -4266,9 +4515,8 @@ class SignalHandling { StackTrace st; st.set_machine_type(printer.resolver().machine_type()); - st.set_context(ctx()); st.set_thread_handle(thread_handle()); - st.load_here(32 + skip_frames); + st.load_here(32 + skip_frames, ctx()); st.skip_n_firsts(skip_frames); printer.address = true; @@ -4292,4 +4540,3 @@ class SignalHandling { } // namespace backward #endif /* H_GUARD */ -// clang-format on diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ddec1a8..06bf89a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,5 @@ project(aws-lambda-runtime-tests LANGUAGES CXX) + find_package(AWSSDK COMPONENTS lambda iam) add_executable(${PROJECT_NAME} diff --git a/tests/resources/lambda_function.cpp b/tests/resources/lambda_function.cpp index 43f3dac..7cd62e5 100644 --- a/tests/resources/lambda_function.cpp +++ b/tests/resources/lambda_function.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -26,12 +27,19 @@ invocation_response binary_response(invocation_request const& /*request*/) return invocation_response::success(png, "image/png"); } +invocation_response crash_backtrace(invocation_request const& /*request*/) +{ + throw std::runtime_error("barf"); + return invocation_response::failure("unreachable", "unreachable"); +} + int main(int argc, char* argv[]) { std::unordered_map> handlers; handlers.emplace("echo_success", echo_success); handlers.emplace("echo_failure", echo_failure); handlers.emplace("binary_response", binary_response); + handlers.emplace("crash_backtrace", crash_backtrace); if (argc < 2) { aws::logging::log_error("lambda_fun", "Missing handler argument. Exiting."); diff --git a/tests/runtime_tests.cpp b/tests/runtime_tests.cpp index 9d0ee72..a65a267 100644 --- a/tests/runtime_tests.cpp +++ b/tests/runtime_tests.cpp @@ -11,13 +11,16 @@ #include #include #include -#include #include +#include #include "gtest/gtest.h" +#include #include +#include #include #include #include +#include extern std::string aws_prefix; @@ -63,6 +66,7 @@ struct LambdaRuntimeTest : public ::testing::Test { delete_function(build_resource_name("echo_success"), false /*assert*/); delete_function(build_resource_name("echo_failure"), false /*assert*/); delete_function(build_resource_name("binary_response"), false /*assert*/); + delete_function(build_resource_name("crash_backtrace"), false /*assert*/); } Aws::String get_role_arn(Aws::String const& role_name) @@ -217,4 +221,25 @@ TEST_F(LambdaRuntimeTest, binary_response) EXPECT_EQ(expected_length, invoke_outcome.GetResult().GetPayload().tellp()); delete_function(funcname); } + +TEST_F(LambdaRuntimeTest, crash) +{ + Aws::String const funcname = build_resource_name("crash_backtrace"); + create_function(funcname, "crash_backtrace" /*handler_name*/); + Model::InvokeRequest invoke_request; + invoke_request.SetFunctionName(funcname); + invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); + invoke_request.SetLogType(Model::LogType::Tail); + + Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); + EXPECT_TRUE(invoke_outcome.IsSuccess()); + EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); + EXPECT_STREQ("Unhandled", invoke_outcome.GetResult().GetFunctionError().c_str()); + Aws::Utils::Base64::Base64 base64; + auto decoded = base64.Decode(invoke_outcome.GetResult().GetLogResult()); + std::string tail_logs(reinterpret_cast(decoded.GetUnderlyingData()), decoded.GetLength()); + EXPECT_NE(tail_logs.find("Stack trace (most recent call last):"), std::string::npos); + delete_function(funcname); +} + } // namespace