From 622a582fa684085811bd34d58494792446b7c98b Mon Sep 17 00:00:00 2001 From: Ivan Penkov Date: Thu, 24 Feb 2022 20:49:43 +0000 Subject: [PATCH] Support for leaf functions which don't touch any callee-saved registers for Windows x64 stacks. According to https://reviews.llvm.org/D2474, LLVM does't generate unwind info for leaf function which doesn't touch any callee-saved registers. According to MSDN, leaf functions can be unwound simply by simulating a return. Change-Id: Ic0503e2aca90b0ba5799133ea8439f1b5f2eefda Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/3489332 Reviewed-by: Mark Mentovai Reviewed-by: Joshua Peraza --- src/google_breakpad/processor/stack_frame.h | 11 ++++-- src/processor/stackwalker_amd64.cc | 41 ++++++++++++++++++++- src/processor/stackwalker_amd64.h | 5 +++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/google_breakpad/processor/stack_frame.h b/src/google_breakpad/processor/stack_frame.h index 18e95fbc..7d5682a7 100644 --- a/src/google_breakpad/processor/stack_frame.h +++ b/src/google_breakpad/processor/stack_frame.h @@ -50,9 +50,12 @@ struct StackFrame { FRAME_TRUST_CFI_SCAN, // Found while scanning stack using call frame info FRAME_TRUST_FP, // Derived from frame pointer FRAME_TRUST_CFI, // Derived from call frame info - FRAME_TRUST_PREWALKED, // Explicitly provided by some external stack walker. + // Explicitly provided by some external stack walker. + FRAME_TRUST_PREWALKED, FRAME_TRUST_CONTEXT, // Given as instruction pointer in a context - FRAME_TRUST_INLINE // Found by inline records in symbol files. + FRAME_TRUST_INLINE, // Found by inline records in symbol files. + // Derived from leaf function by simulating a return. + FRAME_TRUST_LEAF, }; StackFrame() @@ -85,7 +88,9 @@ struct StackFrame { return "stack scanning"; case StackFrame::FRAME_TRUST_INLINE: return "inline record"; - default: + case StackFrame::FRAME_TRUST_LEAF: + return "simulating a return from leaf function"; + default: return "unknown"; } } diff --git a/src/processor/stackwalker_amd64.cc b/src/processor/stackwalker_amd64.cc index 6e2f86b4..0fc22a4f 100644 --- a/src/processor/stackwalker_amd64.cc +++ b/src/processor/stackwalker_amd64.cc @@ -221,6 +221,35 @@ StackFrameAMD64* StackwalkerAMD64::GetCallerByFramePointerRecovery( return NULL; } +StackFrameAMD64* StackwalkerAMD64::GetCallerBySimulatingReturn( + const vector& frames) { + assert(frames.size() == 1); + StackFrameAMD64* last_frame = static_cast(frames.back()); + uint64_t last_rsp = last_frame->context.rsp; + uint64_t caller_rip_address, caller_rip; + int searchwords = 1; + if (!ScanForReturnAddress(last_rsp, &caller_rip_address, &caller_rip, + searchwords)) { + // No plausible return address at the top of the stack. Unable to simulate + // a return. + return NULL; + } + + // Create a new stack frame (ownership will be transferred to the caller) + // and fill it in. + StackFrameAMD64* frame = new StackFrameAMD64(); + + frame->trust = StackFrame::FRAME_TRUST_LEAF; + frame->context = last_frame->context; + frame->context.rip = caller_rip; + // The caller's %rsp is directly underneath the return address pushed by + // the call. + frame->context.rsp = caller_rip_address + 8; + frame->context_validity = last_frame->context_validity; + + return frame; +} + StackFrameAMD64* StackwalkerAMD64::GetCallerByStackScan( const vector& frames) { StackFrameAMD64* last_frame = static_cast(frames.back()); @@ -282,12 +311,22 @@ StackFrame* StackwalkerAMD64::GetCallerFrame(const CallStack* stack, StackFrameAMD64* last_frame = static_cast(frames.back()); scoped_ptr new_frame; - // If we have DWARF CFI information, use it. + // If we have CFI information, use it. scoped_ptr cfi_frame_info( frame_symbolizer_->FindCFIFrameInfo(last_frame)); if (cfi_frame_info.get()) new_frame.reset(GetCallerByCFIFrameInfo(frames, cfi_frame_info.get())); + // If CFI was not available and this is a Windows x64 stack, check whether + // this is a leaf function which doesn't touch any callee-saved registers. + // According to https://reviews.llvm.org/D24748, LLVM doesn't generate unwind + // info for such functions. According to MSDN, leaf functions can be unwound + // simply by simulating a return. + if (!new_frame.get() && stack->frames()->size() == 1 && + system_info_->os_short == "windows") { + new_frame.reset(GetCallerBySimulatingReturn(frames)); + } + // If CFI was not available or failed, try using frame pointer recovery. // Never try to use frame pointer unwinding on Windows x64 stack. MSVC never // generates code that works with frame pointer chasing, and LLVM does the diff --git a/src/processor/stackwalker_amd64.h b/src/processor/stackwalker_amd64.h index 5e1af6f1..78401038 100644 --- a/src/processor/stackwalker_amd64.h +++ b/src/processor/stackwalker_amd64.h @@ -90,6 +90,11 @@ class StackwalkerAMD64 : public Stackwalker { // of the returned frame. Return NULL on failure. StackFrameAMD64* GetCallerByStackScan(const vector& frames); + // Trying to simulate a return. The caller takes ownership of the returned + // frame. Return NULL on failure. + StackFrameAMD64* GetCallerBySimulatingReturn( + const vector& frames); + // Stores the CPU context corresponding to the innermost stack frame to // be returned by GetContextFrame. const MDRawContextAMD64* context_;