arm64: recover register X30(LR) when fallback to frame pointer unwinder

Stackwalk can't recover caller's register X30($LR) value by STACK CFI info.
This will lead unwinding stop immediately when fallback to frame pointer unwinder.

This PR will use try to use second last frame to recover register X30($LR) by frame pointer.
And we will give up correction if STACK CFI info doesn't agree with frame pointer info.

Bug: https://bugs.chromium.org/p/google-breakpad/issues/detail?id=808
Change-Id: I50649e3398e268b02ff297e83db21d05705c2a2d
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/1992641
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
Sim Sun 2020-01-14 21:48:37 -08:00 committed by Joshua Peraza
parent 4e15f7d230
commit 13c1568702
2 changed files with 46 additions and 0 deletions

View file

@ -208,6 +208,9 @@ StackFrameARM64* StackwalkerARM64::GetCallerByStackScan(
StackFrameARM64* StackwalkerARM64::GetCallerByFramePointer( StackFrameARM64* StackwalkerARM64::GetCallerByFramePointer(
const vector<StackFrame*> &frames) { const vector<StackFrame*> &frames) {
StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back()); StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
if (!(last_frame->context_validity & StackFrameARM64::CONTEXT_VALID_LR)) {
CorrectRegLRByFramePointer(frames, last_frame);
}
uint64_t last_fp = last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP]; uint64_t last_fp = last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP];
@ -248,6 +251,42 @@ StackFrameARM64* StackwalkerARM64::GetCallerByFramePointer(
return frame; return frame;
} }
void StackwalkerARM64::CorrectRegLRByFramePointer(
const vector<StackFrame*>& frames,
StackFrameARM64* last_frame) {
// Need at least two frames to correct and
// register $FP should always be greater than register $SP.
if (frames.size() < 2 || !last_frame ||
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP] <=
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_SP])
return;
StackFrameARM64* last_last_frame =
static_cast<StackFrameARM64*>(*(frames.end() - 2));
uint64_t last_last_fp =
last_last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP];
uint64_t last_fp = 0;
if (last_last_fp && !memory_->GetMemoryAtAddress(last_last_fp, &last_fp)) {
BPLOG(ERROR) << "Unable to read last_fp from last_last_fp: 0x"
<< std::hex << last_last_fp;
return;
}
// Give up if STACK CFI doesn't agree with frame pointer.
if (last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP] != last_fp)
return;
uint64_t last_lr = 0;
if (last_last_fp && !memory_->GetMemoryAtAddress(last_last_fp + 8, &last_lr)) {
BPLOG(ERROR) << "Unable to read last_lr from (last_last_fp + 8): 0x"
<< std::hex << (last_last_fp + 8);
return;
}
last_lr = PtrauthStrip(last_lr);
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_LR] = last_lr;
}
StackFrame* StackwalkerARM64::GetCallerFrame(const CallStack* stack, StackFrame* StackwalkerARM64::GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed) { bool stack_scan_allowed) {
if (!memory_ || !stack) { if (!memory_ || !stack) {

View file

@ -90,6 +90,13 @@ class StackwalkerARM64 : public Stackwalker {
// of the returned frame. Return NULL on failure. // of the returned frame. Return NULL on failure.
StackFrameARM64* GetCallerByStackScan(const vector<StackFrame*> &frames); StackFrameARM64* GetCallerByStackScan(const vector<StackFrame*> &frames);
// GetCallerByFramePointer() depends on the previous frame having recovered
// x30($LR) which may not have been done when using CFI.
// This function recovers $LR in the previous frame by using the frame-pointer
// two frames back to read it from the stack.
void CorrectRegLRByFramePointer(const vector<StackFrame*>& frames,
StackFrameARM64* last_frame);
// Stores the CPU context corresponding to the youngest stack frame, to // Stores the CPU context corresponding to the youngest stack frame, to
// be returned by GetContextFrame. // be returned by GetContextFrame.
const MDRawContextARM64* context_; const MDRawContextARM64* context_;