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
This commit is contained in:
Pavel Labath 2019-09-27 12:10:06 +00:00
parent 59876948c3
commit a8b284eeec
10 changed files with 176 additions and 12 deletions

View File

@ -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 <mutex>
@ -245,6 +245,13 @@ public:
return nullptr;
}
/// Return the number of stack bytes taken up by the parameters to this
/// function.
virtual llvm::Expected<lldb::addr_t> GetParameterStackSize(Symbol &symbol) {
return llvm::createStringError(make_error_code(llvm::errc::not_supported),
"Operation not supported.");
}
virtual void Dump(Stream &s);
protected:

View File

@ -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

View File

@ -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 + =

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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;
///

View File

@ -374,6 +374,20 @@ void SymbolFileBreakpad::AddSymbols(Symtab &symtab) {
symtab.CalculateSymbolSizes();
}
llvm::Expected<lldb::addr_t>
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<std::pair<llvm::StringRef, llvm::StringRef>>
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<postfix::SymbolNode>(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<uint8_t> saved = SaveAsDWARF(*it->second);
row_sp->GetCFAValue().SetIsDWARFExpression(saved.data(), saved.size());
}
llvm::ArrayRef<uint8_t> 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

View File

@ -135,6 +135,8 @@ public:
void AddSymbols(Symtab &symtab) override;
llvm::Expected<lldb::addr_t> GetParameterStackSize(Symbol &symbol) override;
lldb::UnwindPlanSP
GetUnwindPlan(const Address &address,
const RegisterInfoResolver &resolver) override;

View File

@ -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;
}
}