Skip to content

[lldb] Provide lr value in faulting frame on arm64 #138805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions lldb/source/Target/RegisterContextUnwind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ void RegisterContextUnwind::InitializeZerothFrame() {
active_row =
m_full_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset);
row_register_kind = m_full_unwind_plan_sp->GetRegisterKind();
PropagateTrapHandlerFlagFromUnwindPlan(m_full_unwind_plan_sp);
if (active_row && log) {
StreamString active_row_strm;
active_row->Dump(active_row_strm, m_full_unwind_plan_sp.get(), &m_thread,
Expand Down Expand Up @@ -1375,6 +1376,7 @@ RegisterContextUnwind::SavedLocationForRegister(
}
}

// Check if the active_row has a register location listed.
if (regnum.IsValid() && active_row &&
active_row->GetRegisterInfo(regnum.GetAsKind(unwindplan_registerkind),
unwindplan_regloc)) {
Expand All @@ -1388,11 +1390,10 @@ RegisterContextUnwind::SavedLocationForRegister(
// This is frame 0 and we're retrieving the PC and it's saved in a Return
// Address register and it hasn't been saved anywhere yet -- that is,
// it's still live in the actual register. Handle this specially.

if (!have_unwindplan_regloc && return_address_reg.IsValid() &&
IsFrameZero()) {
if (return_address_reg.GetAsKind(eRegisterKindLLDB) !=
LLDB_INVALID_REGNUM) {
return_address_reg.GetAsKind(eRegisterKindLLDB) !=
LLDB_INVALID_REGNUM) {
if (IsFrameZero()) {
lldb_private::UnwindLLDB::ConcreteRegisterLocation new_regloc;
new_regloc.type = UnwindLLDB::ConcreteRegisterLocation::
eRegisterInLiveRegisterContext;
Expand All @@ -1406,6 +1407,17 @@ RegisterContextUnwind::SavedLocationForRegister(
return_address_reg.GetAsKind(eRegisterKindLLDB),
return_address_reg.GetAsKind(eRegisterKindLLDB));
return UnwindLLDB::RegisterSearchResult::eRegisterFound;
} else if (BehavesLikeZerothFrame()) {
// This function was interrupted asynchronously -- it faulted,
// an async interrupt, a timer fired, a debugger expression etc.
// The caller's pc is in the Return Address register, but the
// UnwindPlan for this function may have no location rule for
// the RA reg.
// This means that the caller's return address is in the RA reg
// when the function was interrupted--descend down one stack frame
// to retrieve it from the trap handler's saved context.
unwindplan_regloc.SetSame();
have_unwindplan_regloc = true;
}
}

Expand Down Expand Up @@ -1922,6 +1934,7 @@ void RegisterContextUnwind::PropagateTrapHandlerFlagFromUnwindPlan(
}

m_frame_type = eTrapHandlerFrame;
UnwindLogMsg("This frame is marked as a trap handler via its UnwindPlan");

if (m_current_offset_backed_up_one != m_current_offset) {
// We backed up the pc by 1 to compute the symbol context, but
Expand Down
13 changes: 13 additions & 0 deletions lldb/test/API/functionalities/unwind/frameless-faulted/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
C_SOURCES := main.c

interrupt-and-trap-funcs.o: interrupt-and-trap-funcs.c
$(CC) $(CFLAGS) -E -o interrupt-and-trap-funcs.s $(SRCDIR)/interrupt-and-trap-funcs.c
$(CC) $(CFLAGS) -c -o interrupt-and-trap-funcs.o interrupt-and-trap-funcs.s

include Makefile.rules

a.out: interrupt-and-trap-funcs.o

# Needs to come after include
OBJECTS += interrupt-and-trap-funcs.o

Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Test that lldb backtraces a frameless function that faults correctly."""

import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import shutil
import os


class TestUnwindFramelessFaulted(TestBase):
NO_DEBUG_INFO_TESTCASE = True

@skipIf(
oslist=no_match([lldbplatformutil.getDarwinOSTriples(), "linux"]),
archs=no_match(["aarch64", "arm64", "arm64e"]),
)
def test_frameless_faulted_unwind(self):
self.build()

(target, process, thread, bp) = lldbutil.run_to_name_breakpoint(
self, "main", only_one_thread=False
)

# The test program will have a backtrace like this at its deepest:
#
# * frame #0: 0x0000000102adc468 a.out`break_to_debugger + 4
# frame #1: 0x0000000102adc458 a.out`trap + 16
# frame #2: 0x0000000102adc440 a.out`to_be_interrupted + 20
# frame #3: 0x0000000102adc418 a.out`main at main.c:4:7
# frame #4: 0x0000000193b7eb4c dyld`start + 6000

correct_frames = ["break_to_debugger", "trap", "to_be_interrupted", "main"]

# Keep track of when main has branch & linked, instruction step until we're
# back in main()
main_has_bl_ed = False

# Instruction step through the binary until we are in a function not
# listed in correct_frames.
frame = thread.GetFrameAtIndex(0)
step_count = 0
max_step_count = 200
while (
process.GetState() == lldb.eStateStopped
and frame.name in correct_frames
and step_count < max_step_count
):
starting_index = 0
if self.TraceOn():
self.runCmd("bt")

# Find which index into correct_frames the current stack frame is
for idx, name in enumerate(correct_frames):
if frame.name == name:
starting_index = idx

# Test that all frames after the current frame listed in
# correct_frames appears in the backtrace.
frame_idx = 0
for expected_frame in correct_frames[starting_index:]:
self.assertEqual(thread.GetFrameAtIndex(frame_idx).name, expected_frame)
frame_idx = frame_idx + 1

# When we're at our deepest level, test that register passing of
# x0 and x20 follow the by-hand UnwindPlan rules.
# In this test program, we can get x0 in the middle of the stack
# and we CAN'T get x20. The opposites of the normal AArch64 SysV
# ABI.
if frame.name == "break_to_debugger":
tbi_frame = thread.GetFrameAtIndex(2)
self.assertEqual(tbi_frame.name, "to_be_interrupted")
# The original argument to to_be_interrupted(), 10
# Normally can't get x0 mid-stack, but UnwindPlans have
# special rules to make this possible.
x0_reg = tbi_frame.register["x0"]
self.assertTrue(x0_reg.IsValid())
self.assertEqual(x0_reg.GetValueAsUnsigned(), 10)
# The incremented return value from to_be_interrupted(), 11
x24_reg = tbi_frame.register["x24"]
self.assertTrue(x24_reg.IsValid())
self.assertEqual(x24_reg.GetValueAsUnsigned(), 11)
# x20 can normally be fetched mid-stack, but the UnwindPlan
# has a rule saying it can't be fetched.
x20_reg = tbi_frame.register["x20"]
self.assertTrue(x20_reg.error.fail)

trap_frame = thread.GetFrameAtIndex(1)
self.assertEqual(trap_frame.name, "trap")
# Confirm that we can fetch x0 from trap() which
# is normally not possible w/ SysV AbI, but special
# UnwindPlans in use.
x0_reg = trap_frame.register["x0"]
self.assertTrue(x0_reg.IsValid())
self.assertEqual(x0_reg.GetValueAsUnsigned(), 10)
x1_reg = trap_frame.register["x1"]
self.assertTrue(x1_reg.error.fail)

main_frame = thread.GetFrameAtIndex(3)
self.assertEqual(main_frame.name, "main")
# x20 can normally be fetched mid-stack, but the UnwindPlan
# has a rule saying it can't be fetched.
x20_reg = main_frame.register["x20"]
self.assertTrue(x20_reg.error.fail)
# x21 can be fetched mid-stack.
x21_reg = main_frame.register["x21"]
self.assertTrue(x21_reg.error.success)

# manually move past the BRK instruction in
# break_to_debugger(). lldb-server doesn't
# advance past the builtin_debugtrap() BRK
# instruction.
if (
thread.GetStopReason() == lldb.eStopReasonException
and frame.name == "break_to_debugger"
):
frame.SetPC(frame.GetPC() + 4)

if self.TraceOn():
print("StepInstruction")
thread.StepInstruction(False)
frame = thread.GetFrameAtIndex(0)
step_count = step_count + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// This is assembly code that needs to be run
// through the preprocessor, for simplicity of
// preprocessing it's named .c to start with.
//
// clang-format off


#define DW_CFA_register 0x9
#define ehframe_x0 0
#define ehframe_x20 20
#define ehframe_x22 22
#define ehframe_x23 23
#define ehframe_pc 32

#if defined(__APPLE__)
#define TO_BE_INTERRUPTED _to_be_interrupted
#define TRAP _trap
#define BREAK_TO_DEBUGGER _break_to_debugger
#else
#define TO_BE_INTERRUPTED to_be_interrupted
#define TRAP trap
#define BREAK_TO_DEBUGGER break_to_debugger
#endif

.text
//--------------------------------------
// to_be_interrupted() a frameless function that does a non-ABI
// function call to trap(), simulating an async signal/interrup/exception/fault.
// Before it branches to trap(), put the return address in x23.
// trap() knows to branch back to $x23 when it has finished.
//--------------------------------------
.globl TO_BE_INTERRUPTED
TO_BE_INTERRUPTED:
.cfi_startproc

// This is a garbage entry to ensure that eh_frame is emitted.
// If there's no eh_frame, lldb can use the assembly emulation scan,
// which always includes a rule for $lr, and we won't replicate the
// bug we're testing for.
.cfi_escape DW_CFA_register, ehframe_x22, ehframe_x23
mov x24, x0
add x24, x24, #1

#if defined(__APPLE__)
adrp x23, L_.return@PAGE // put return address in x23
add x23, x23, L_.return@PAGEOFF
#else
adrp x23, .L.return
add x23, x23, :lo12:.L.return
#endif

b TRAP // branch to trap handler, fake async interrupt

#if defined(__APPLE__)
L_.return:
#else
.L.return:
#endif
mov x0, x24
ret
.cfi_endproc



//--------------------------------------
// trap() trap handler function, sets up stack frame
// with special unwind rule for the pc value of the
// "interrupted" stack frame (it's in x23), then calls
// break_to_debugger().
//--------------------------------------
.globl TRAP
TRAP:
.cfi_startproc
.cfi_signal_frame

// The pc value when we were interrupted is in x23
.cfi_escape DW_CFA_register, ehframe_pc, ehframe_x23

// For fun, mark x0 as unmodified so the caller can
// retrieve the value if it wants.
.cfi_same_value ehframe_x0

// Mark x20 as undefined. This is a callee-preserved
// (non-volatile) register by the SysV AArch64 ABI, but
// it'll be fun to see lldb not passing a value past this
// point on the stack.
.cfi_undefined ehframe_x20

// standard prologue save of fp & lr so we can call
// break_to_debugger()
sub sp, sp, #32
stp x29, x30, [sp, #16]
add x29, sp, #16
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16

bl BREAK_TO_DEBUGGER

ldp x29, x30, [sp, #16]
.cfi_same_value x29
.cfi_same_value x30
.cfi_def_cfa sp, 32
add sp, sp, #32
.cfi_same_value sp
.cfi_def_cfa sp, 0

// jump back to $x23 to resume execution of to_be_interrupted
br x23
.cfi_endproc

//--------------------------------------
// break_to_debugger() executes a BRK instruction
//--------------------------------------
.globl BREAK_TO_DEBUGGER
BREAK_TO_DEBUGGER:
.cfi_startproc

// For fun, mark x0 as unmodified so the caller can
// retrieve the value if it wants.
.cfi_same_value ehframe_x0

brk #0xf000 // __builtin_debugtrap aarch64 instruction

ret
.cfi_endproc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
int to_be_interrupted(int);

int main() {
int c = 10;
c = to_be_interrupted(c);
return c;
}
Loading