From a8b284eeec5fbdc4371f6e4286cb66acc4bbeea3 Mon Sep 17 00:00:00 2001 From: Pavel Labath Date: Fri, 27 Sep 2019 12:10:06 +0000 Subject: [PATCH] Unwind: Add a stack scanning mechanism to support win32 unwinding Summary: Windows unwinding is weird. The unwind rules do not (always) describe the precise layout of the stack, but rather expect the debugger to scan the stack for something which looks like a plausible return address, and the unwind based on that. The reason this works somewhat reliably is because the the unwinder also has access to the frame sizes of the functions on the stack. This allows it (in most cases) to skip function pointers in local variables or function arguments, which could otherwise be mistaken for return addresses. Implementing this kind of unwind mechanism in lldb was a bit challenging because we expect to be able to statically describe (in the UnwindPlan) structure, the layout of the stack for any given instruction. Giving a precise desription of this is not possible, because it requires correlating information from two functions -- the pushed arguments to a function are considered a part of the callers stack frame, and their size needs to be considered when unwinding the caller, but they are only present in the unwind entry of the callee. The callee may end up being in a completely different module, or it may not even be possible to determine it statically (indirect calls). This patch implements this functionality by introducing a couple of new APIs: SymbolFile::GetParameterStackSize - return the amount of stack space taken up by parameters of this function. SymbolFile::GetOwnFrameSize - the size of this function's frame. This excludes the parameters, but includes stuff like local variables and spilled registers. These functions are then used by the unwinder to compute the estimated location of the return address. This address is not always exact, because the stack may contain some additional values -- for instance, if we're getting ready to call a function then the stack will also contain partially set up arguments, but we will not know their size because we haven't called the function yet. For this reason the unwinder will crawl up the stack from the return address position, and look for something that looks like a possible return address. Currently, we assume that something is a valid return address if it ends up pointing to an executable section. All of this logic kicks in when the UnwindPlan sets the value of CFA as "isHeuristicallyDetected", which is also the final new API here. Right now, only SymbolFileBreakpad implements these APIs, but in the future SymbolFilePDB will use them too. Differential Revision: https://reviews.llvm.org/D66638 llvm-svn: 373072 --- lldb/include/lldb/Symbol/SymbolFile.h | 9 +++- lldb/include/lldb/Symbol/UnwindPlan.h | 21 +++++++-- .../Breakpad/Inputs/unwind-via-raSearch.syms | 15 ++++++ .../Breakpad/Inputs/unwind-via-stack-win.yaml | 2 + .../SymbolFile/Breakpad/unwind-via-raSearch.test | 43 +++++++++++++++++ .../Process/Utility/RegisterContextLLDB.cpp | 55 ++++++++++++++++++++++ .../Plugins/Process/Utility/RegisterContextLLDB.h | 2 + .../SymbolFile/Breakpad/SymbolFileBreakpad.cpp | 31 ++++++++++-- .../SymbolFile/Breakpad/SymbolFileBreakpad.h | 2 + lldb/source/Symbol/UnwindPlan.cpp | 8 +++- 10 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 lldb/lit/SymbolFile/Breakpad/Inputs/unwind-via-raSearch.syms create mode 100644 lldb/lit/SymbolFile/Breakpad/unwind-via-raSearch.test diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h index d92b456..99c57bd 100644 --- a/lldb/include/lldb/Symbol/SymbolFile.h +++ b/lldb/include/lldb/Symbol/SymbolFile.h @@ -19,8 +19,8 @@ #include "lldb/Symbol/TypeList.h" #include "lldb/Symbol/TypeSystem.h" #include "lldb/lldb-private.h" - #include "llvm/ADT/DenseSet.h" +#include "llvm/Support/Errc.h" #include @@ -245,6 +245,13 @@ public: return nullptr; } + /// Return the number of stack bytes taken up by the parameters to this + /// function. + virtual llvm::Expected GetParameterStackSize(Symbol &symbol) { + return llvm::createStringError(make_error_code(llvm::errc::not_supported), + "Operation not supported."); + } + virtual void Dump(Stream &s); protected: diff --git a/lldb/include/lldb/Symbol/UnwindPlan.h b/lldb/include/lldb/Symbol/UnwindPlan.h index fc3e3cc..fa41ba5 100644 --- a/lldb/include/lldb/Symbol/UnwindPlan.h +++ b/lldb/include/lldb/Symbol/UnwindPlan.h @@ -201,7 +201,8 @@ public: unspecified, // not specified isRegisterPlusOffset, // FA = register + offset isRegisterDereferenced, // FA = [reg] - isDWARFExpression // FA = eval(dwarf_expr) + isDWARFExpression, // FA = eval(dwarf_expr) + isRaSearch, // FA = SP + offset + ??? }; FAValue() : m_type(unspecified), m_value() {} @@ -214,6 +215,11 @@ public: bool IsUnspecified() const { return m_type == unspecified; } + void SetRaSearch(int32_t offset) { + m_type = isRaSearch; + m_value.ra_search_offset = offset; + } + bool IsRegisterPlusOffset() const { return m_type == isRegisterPlusOffset; } @@ -250,9 +256,14 @@ public: ValueType GetValueType() const { return m_type; } int32_t GetOffset() const { - if (m_type == isRegisterPlusOffset) - return m_value.reg.offset; - return 0; + switch (m_type) { + case isRegisterPlusOffset: + return m_value.reg.offset; + case isRaSearch: + return m_value.ra_search_offset; + default: + return 0; + } } void IncOffset(int32_t delta) { @@ -304,6 +315,8 @@ public: const uint8_t *opcodes; uint16_t length; } expr; + // For m_type == isRaSearch + int32_t ra_search_offset; } m_value; }; // class FAValue diff --git a/lldb/lit/SymbolFile/Breakpad/Inputs/unwind-via-raSearch.syms b/lldb/lit/SymbolFile/Breakpad/Inputs/unwind-via-raSearch.syms new file mode 100644 index 0000000..329a280 --- /dev/null +++ b/lldb/lit/SymbolFile/Breakpad/Inputs/unwind-via-raSearch.syms @@ -0,0 +1,15 @@ +MODULE windows x86 897DD83EA8C8411897F3A925EE4BF7411 unwind-via-stack-win.pdb +INFO CODE_ID 5D499B5C5000 unwind-via-stack-win.exe +PUBLIC 0 0 dummy +PUBLIC 10 0 call_many +PUBLIC 80 0 main +PUBLIC 90 0 many_pointer_args +PUBLIC 100 0 complex_rasearch +PUBLIC 110 0 esp_rasearch +PUBLIC 120 0 nonzero_frame_size +STACK WIN 4 10 6d 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + = +STACK WIN 4 80 8 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + = +STACK WIN 4 90 5 0 0 50 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + = +STACK WIN 4 100 4 0 0 0 0 0 0 1 $T0 .raSearch 80 + = $eip $T0 ^ = $esp $T0 4 + = +STACK WIN 4 110 4 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp .raSearch 4 + = +STACK WIN 4 120 4 0 0 0 4 8 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + = diff --git a/lldb/lit/SymbolFile/Breakpad/Inputs/unwind-via-stack-win.yaml b/lldb/lit/SymbolFile/Breakpad/Inputs/unwind-via-stack-win.yaml index e788575..399cf2b 100644 --- a/lldb/lit/SymbolFile/Breakpad/Inputs/unwind-via-stack-win.yaml +++ b/lldb/lit/SymbolFile/Breakpad/Inputs/unwind-via-stack-win.yaml @@ -23,6 +23,8 @@ Streams: Memory Ranges: - Start of Memory Range: 0x0000000000CFFE78 Content: 0000000079100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0000100B0085100B0094842777 + - Type: MemoryInfoList + Content: 1000000030000000020000000000000000100B00000000000000000000000000000000000000000000400000000000000010000010000000000000010000000000002677000000000000000000000000000000000000000000000E000000000000100000100000000000000100000000 - Type: SystemInfo Processor Arch: X86 Platform ID: Win32NT diff --git a/lldb/lit/SymbolFile/Breakpad/unwind-via-raSearch.test b/lldb/lit/SymbolFile/Breakpad/unwind-via-raSearch.test new file mode 100644 index 0000000..ea259a9 --- /dev/null +++ b/lldb/lit/SymbolFile/Breakpad/unwind-via-raSearch.test @@ -0,0 +1,43 @@ +# RUN: yaml2obj %S/Inputs/unwind-via-stack-win.yaml > %t +# RUN: %lldb -c %t \ +# RUN: -o "target symbols add %S/Inputs/unwind-via-raSearch.syms" \ +# RUN: -s %s -b | FileCheck %s + +# First check that unwind plan generation works correctly. +# This function has a "typical" unwind rule. +image show-unwind -n call_many +# CHECK-LABEL: image show-unwind -n call_many +# CHECK: UNWIND PLANS for unwind-via-stack-win.exe`call_many +# CHECK: Symbol file UnwindPlan: +# CHECK: This UnwindPlan originally sourced from breakpad STACK WIN +# CHECK: This UnwindPlan is sourced from the compiler: yes. +# CHECK: This UnwindPlan is valid at all instruction locations: no. +# CHECK: Address range of this UnwindPlan: [unwind-via-stack-win.exe..module_image + 16-0x0000007d) +# CHECK: row[0]: 0: CFA=RaSearch@SP+0 => esp=DW_OP_pick 0x00, DW_OP_consts +4, DW_OP_plus eip=DW_OP_pick 0x00, DW_OP_deref + +image show-unwind -n nonzero_frame_size +# CHECK-LABEL: image show-unwind -n nonzero_frame_size +# CHECK: UNWIND PLANS for unwind-via-stack-win.exe`nonzero_frame_size +# CHECK: Symbol file UnwindPlan: +# CHECK: row[0]: 0: CFA=RaSearch@SP+12 => esp=DW_OP_pick 0x00, DW_OP_consts +4, DW_OP_plus eip=DW_OP_pick 0x00, DW_OP_deref + +# Then, some invalid rules. +image show-unwind -n complex_rasearch +# CHECK-LABEL: image show-unwind -n complex_rasearch +# CHECK: UNWIND PLANS for unwind-via-stack-win.exe`complex_rasearch +# CHECK-NOT: Symbol file + +image show-unwind -n esp_rasearch +# CHECK-LABEL: image show-unwind -n esp_rasearch +# CHECK: UNWIND PLANS for unwind-via-stack-win.exe`esp_rasearch +# CHECK-NOT: Symbol file + +# And finally, check that backtracing works as a whole by unwinding a simple +# stack. +thread backtrace +# CHECK-LABEL: thread backtrace +# CHECK: frame #0: 0x000b1092 unwind-via-stack-win.exe`many_pointer_args +# CHECK: frame #1: 0x000b1079 unwind-via-stack-win.exe`call_many + 105 +# CHECK: frame #2: 0x000b1085 unwind-via-stack-win.exe`main + 5 +# CHECK: frame #3: 0x77278494 kernel32.dll +# CHECK-NOT: frame diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.cpp b/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.cpp index 0da687a..50795c0 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.cpp @@ -18,6 +18,7 @@ #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolFile.h" #include "lldb/Target/ABI.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/ExecutionContext.h" @@ -1852,12 +1853,66 @@ bool RegisterContextLLDB::ReadFrameAddress( error.AsCString()); break; } + case UnwindPlan::Row::FAValue::isRaSearch: { + Process &process = *m_thread.GetProcess(); + lldb::addr_t return_address_hint = GetReturnAddressHint(fa.GetOffset()); + if (return_address_hint == LLDB_INVALID_ADDRESS) + return false; + const unsigned max_iterations = 256; + for (unsigned i = 0; i < max_iterations; ++i) { + Status st; + lldb::addr_t candidate_addr = + return_address_hint + i * process.GetAddressByteSize(); + lldb::addr_t candidate = + process.ReadPointerFromMemory(candidate_addr, st); + if (st.Fail()) { + UnwindLogMsg("Cannot read memory at 0x%" PRIx64 ": %s", candidate_addr, + st.AsCString()); + return false; + } + Address addr; + uint32_t permissions; + if (process.GetLoadAddressPermissions(candidate, permissions) && + permissions & lldb::ePermissionsExecutable) { + address = candidate_addr; + UnwindLogMsg("Heuristically found CFA: 0x%" PRIx64, address); + return true; + } + } + UnwindLogMsg("No suitable CFA found"); + break; + } default: return false; } return false; } +lldb::addr_t RegisterContextLLDB::GetReturnAddressHint(int32_t plan_offset) { + addr_t hint; + if (!ReadGPRValue(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_SP, hint)) + return LLDB_INVALID_ADDRESS; + if (!m_sym_ctx.module_sp || !m_sym_ctx.symbol) + return LLDB_INVALID_ADDRESS; + + hint += plan_offset; + + if (auto next = GetNextFrame()) { + if (!next->m_sym_ctx.module_sp || !next->m_sym_ctx.symbol) + return LLDB_INVALID_ADDRESS; + if (auto expected_size = + next->m_sym_ctx.module_sp->GetSymbolFile()->GetParameterStackSize( + *next->m_sym_ctx.symbol)) + hint += *expected_size; + else { + UnwindLogMsgVerbose("Could not retrieve parameter size: %s", + llvm::toString(expected_size.takeError()).c_str()); + return LLDB_INVALID_ADDRESS; + } + } + return hint; +} + // Retrieve a general purpose register value for THIS frame, as saved by the // NEXT frame, i.e. the frame that // this frame called. e.g. diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.h b/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.h index 1ca6639..114ac35 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.h +++ b/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.h @@ -201,6 +201,8 @@ private: bool IsUnwindPlanValidForCurrentPC(lldb::UnwindPlanSP unwind_plan_sp, int &valid_pc_offset); + lldb::addr_t GetReturnAddressHint(int32_t plan_offset); + lldb_private::Thread &m_thread; /// diff --git a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp index d946ae8..b15091c 100644 --- a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp +++ b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp @@ -374,6 +374,20 @@ void SymbolFileBreakpad::AddSymbols(Symtab &symtab) { symtab.CalculateSymbolSizes(); } +llvm::Expected +SymbolFileBreakpad::GetParameterStackSize(Symbol &symbol) { + ParseUnwindData(); + if (auto *entry = m_unwind_data->win.FindEntryThatContains( + symbol.GetAddress().GetFileAddress())) { + auto record = StackWinRecord::parse( + *LineIterator(*m_objfile_sp, Record::StackWin, entry->data)); + assert(record.hasValue()); + return record->ParameterSize; + } + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Parameter size unknown."); +} + static llvm::Optional> GetRule(llvm::StringRef &unwind_rules) { // Unwind rules are of the form @@ -585,12 +599,19 @@ SymbolFileBreakpad::ParseWinUnwindPlan(const Bookmark &bookmark, // We assume the first value will be the CFA. It is usually called T0, but // clang will use T1, if it needs to realign the stack. - if (!postfix::ResolveSymbols(it->second, symbol_resolver)) { - LLDB_LOG(log, "Resolving symbols in `{0}` failed.", record->ProgramString); - return nullptr; + auto *symbol = llvm::dyn_cast(it->second); + if (symbol && symbol->GetName() == ".raSearch") { + row_sp->GetCFAValue().SetRaSearch(record->LocalSize + + record->SavedRegisterSize); + } else { + if (!postfix::ResolveSymbols(it->second, symbol_resolver)) { + LLDB_LOG(log, "Resolving symbols in `{0}` failed.", + record->ProgramString); + return nullptr; + } + llvm::ArrayRef saved = SaveAsDWARF(*it->second); + row_sp->GetCFAValue().SetIsDWARFExpression(saved.data(), saved.size()); } - llvm::ArrayRef saved = SaveAsDWARF(*it->second); - row_sp->GetCFAValue().SetIsDWARFExpression(saved.data(), saved.size()); // Replace the node value with InitialValueNode, so that subsequent // expressions refer to the CFA value instead of recomputing the whole diff --git a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h index dc30909..d7fe897 100644 --- a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h +++ b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h @@ -135,6 +135,8 @@ public: void AddSymbols(Symtab &symtab) override; + llvm::Expected GetParameterStackSize(Symbol &symbol) override; + lldb::UnwindPlanSP GetUnwindPlan(const Address &address, const RegisterInfoResolver &resolver) override; diff --git a/lldb/source/Symbol/UnwindPlan.cpp b/lldb/source/Symbol/UnwindPlan.cpp index 6af85d6..15443ce 100644 --- a/lldb/source/Symbol/UnwindPlan.cpp +++ b/lldb/source/Symbol/UnwindPlan.cpp @@ -170,7 +170,8 @@ operator==(const UnwindPlan::Row::FAValue &rhs) const { if (m_type == rhs.m_type) { switch (m_type) { case unspecified: - return true; + case isRaSearch: + return m_value.ra_search_offset == rhs.m_value.ra_search_offset; case isRegisterPlusOffset: return m_value.reg.offset == rhs.m_value.reg.offset; @@ -205,9 +206,12 @@ void UnwindPlan::Row::FAValue::Dump(Stream &s, const UnwindPlan *unwind_plan, llvm::makeArrayRef(m_value.expr.opcodes, m_value.expr.length), thread); break; - default: + case unspecified: s.PutCString("unspecified"); break; + case isRaSearch: + s.Printf("RaSearch@SP%+d", m_value.ra_search_offset); + break; } } -- 2.7.4