From 6c57bc19a58a9fae96b0ce1dc7e969a65f2d37b8 Mon Sep 17 00:00:00 2001 From: "rsesek@chromium.org" Date: Fri, 18 Jul 2014 00:27:49 +0000 Subject: Add frame pointer recovery to the AMD64 Stackwalker. BUG=https://code.google.com/p/chromium/issues/detail?id=393594 R=mark@chromium.org Review URL: https://breakpad.appspot.com/10664002 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1350 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/processor/stackwalker_amd64.cc | 54 +++++++++++++++++++++++++++-- src/processor/stackwalker_amd64.h | 8 +++++ src/processor/stackwalker_amd64_unittest.cc | 2 +- 3 files changed, 61 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/processor/stackwalker_amd64.cc b/src/processor/stackwalker_amd64.cc index b2ffdb89..f252a33b 100644 --- a/src/processor/stackwalker_amd64.cc +++ b/src/processor/stackwalker_amd64.cc @@ -147,6 +147,52 @@ StackFrameAMD64* StackwalkerAMD64::GetCallerByCFIFrameInfo( return frame.release(); } +StackFrameAMD64* StackwalkerAMD64::GetCallerByFramePointerRecovery( + const vector& frames) { + StackFrameAMD64* last_frame = static_cast(frames.back()); + uint64_t last_rsp = last_frame->context.rsp; + uint64_t last_rbp = last_frame->context.rbp; + + // Assume the presence of a frame pointer. This is not mandated by the + // AMD64 ABI, c.f. section 3.2.2 footnote 7, though it is typical for + // compilers to still preserve the frame pointer and not treat %rbp as a + // general purpose register. + // + // With this assumption, the CALL instruction pushes the return address + // onto the stack and sets %rip to the procedure to enter. The procedure + // then establishes the stack frame with a prologue that PUSHes the current + // %rbp onto the stack, MOVes the current %rsp to %rbp, and then allocates + // space for any local variables. Using this procedure linking information, + // it is possible to locate frame information for the callee: + // + // %caller_rsp = *(%callee_rbp + 16) + // %caller_rip = *(%callee_rbp + 8) + // %caller_rbp = *(%callee_rbp) + + uint64_t caller_rip, caller_rbp; + if (memory_->GetMemoryAtAddress(last_rbp + 8, &caller_rip) && + memory_->GetMemoryAtAddress(last_rbp, &caller_rbp)) { + uint64_t caller_rsp = last_rbp + 16; + + // Simple sanity check that the stack is growing downwards as expected. + if (caller_rbp < last_rbp || caller_rsp < last_rsp) + return NULL; + + StackFrameAMD64* frame = new StackFrameAMD64(); + frame->trust = StackFrame::FRAME_TRUST_FP; + frame->context = last_frame->context; + frame->context.rip = caller_rip; + frame->context.rsp = caller_rsp; + frame->context.rbp = caller_rbp; + frame->context_validity = StackFrameAMD64::CONTEXT_VALID_RIP | + StackFrameAMD64::CONTEXT_VALID_RSP | + StackFrameAMD64::CONTEXT_VALID_RBP; + return frame; + } + + return NULL; +} + StackFrameAMD64* StackwalkerAMD64::GetCallerByStackScan( const vector &frames) { StackFrameAMD64* last_frame = static_cast(frames.back()); @@ -214,8 +260,12 @@ StackFrame* StackwalkerAMD64::GetCallerFrame(const CallStack* stack, if (cfi_frame_info.get()) new_frame.reset(GetCallerByCFIFrameInfo(frames, cfi_frame_info.get())); - // If CFI failed, or there wasn't CFI available, fall back - // to stack scanning. + // If CFI was not available or failed, try using frame pointer recovery. + if (!new_frame.get()) { + new_frame.reset(GetCallerByFramePointerRecovery(frames)); + } + + // If all else fails, fall back to stack scanning. if (stack_scan_allowed && !new_frame.get()) { new_frame.reset(GetCallerByStackScan(frames)); } diff --git a/src/processor/stackwalker_amd64.h b/src/processor/stackwalker_amd64.h index acdd2c2f..8f3dbd52 100644 --- a/src/processor/stackwalker_amd64.h +++ b/src/processor/stackwalker_amd64.h @@ -78,6 +78,14 @@ class StackwalkerAMD64 : public Stackwalker { StackFrameAMD64* GetCallerByCFIFrameInfo(const vector &frames, CFIFrameInfo* cfi_frame_info); + // Assumes a traditional frame layout where the frame pointer has not been + // omitted. The expectation is that caller's %rbp is pushed to the stack + // after the return address of the callee, and that the callee's %rsp can + // be used to find the pushed %rbp. + // Caller owns the returned frame object. Returns NULL on failure. + StackFrameAMD64* GetCallerByFramePointerRecovery( + const vector& frames); + // Scan the stack for plausible return addresses. The caller takes ownership // of the returned frame. Return NULL on failure. StackFrameAMD64* GetCallerByStackScan(const vector &frames); diff --git a/src/processor/stackwalker_amd64_unittest.cc b/src/processor/stackwalker_amd64_unittest.cc index 31489106..a54198bf 100644 --- a/src/processor/stackwalker_amd64_unittest.cc +++ b/src/processor/stackwalker_amd64_unittest.cc @@ -491,7 +491,7 @@ TEST_F(GetCallerFrame, CallerPushedRBP) { EXPECT_EQ(0x40000000c0000100ULL, frame0->function_base); StackFrameAMD64 *frame1 = static_cast(frames->at(1)); - EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame1->trust); + EXPECT_EQ(StackFrame::FRAME_TRUST_FP, frame1->trust); ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP | StackFrameAMD64::CONTEXT_VALID_RSP | StackFrameAMD64::CONTEXT_VALID_RBP), -- cgit v1.2.1