diff --git a/lldb/include/lldb/Target/Platform.h b/lldb/include/lldb/Target/Platform.h --- a/lldb/include/lldb/Target/Platform.h +++ b/lldb/include/lldb/Target/Platform.h @@ -719,6 +719,24 @@ /// A list of symbol names. The list may be empty. virtual const std::vector &GetTrapHandlerSymbolNames(); + /// Try to get a specific unwind plan for a named trap handler. + /// The default is not to have specific unwind plans for trap handlers. + /// + /// \param[in] triple + /// Triple of the current target. + /// + /// \param[in] name + /// Name of the trap handler function. + /// + /// \return + /// A specific unwind plan for that trap handler, or an empty + /// shared pointer. The latter means there is no specific plan, + /// unwind as normal. + virtual lldb::UnwindPlanSP + GetTrapHandlerUnwindPlan(const llvm::Triple &triple, ConstString name) { + return {}; + } + /// Find a support executable that may not live within in the standard /// locations related to LLDB. /// diff --git a/lldb/source/Plugins/Platform/Linux/PlatformLinux.h b/lldb/source/Plugins/Platform/Linux/PlatformLinux.h --- a/lldb/source/Plugins/Platform/Linux/PlatformLinux.h +++ b/lldb/source/Plugins/Platform/Linux/PlatformLinux.h @@ -50,6 +50,9 @@ void CalculateTrapHandlerSymbolNames() override; + lldb::UnwindPlanSP GetTrapHandlerUnwindPlan(const llvm::Triple &triple, + ConstString name) override; + MmapArgList GetMmapArgumentList(const ArchSpec &arch, lldb::addr_t addr, lldb::addr_t length, unsigned prot, unsigned flags, lldb::addr_t fd, diff --git a/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp b/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp --- a/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp +++ b/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp @@ -14,9 +14,11 @@ #include #endif +#include "Utility/ARM64_DWARF_Registers.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/PluginManager.h" #include "lldb/Host/HostInfo.h" +#include "lldb/Symbol/UnwindPlan.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" #include "lldb/Utility/FileSpec.h" @@ -251,6 +253,93 @@ m_trap_handlers.push_back(ConstString("__restore_rt")); } +static lldb::UnwindPlanSP GetAArch64TrapHanlderUnwindPlan(ConstString name) { + UnwindPlanSP unwind_plan_sp; + if (name != "__kernel_rt_sigreturn") + return unwind_plan_sp; + + UnwindPlan::RowSP row = std::make_shared(); + row->SetOffset(0); + + // In the signal trampoline frame, sp points to an rt_sigframe[1], which is: + // - 128-byte siginfo struct + // - ucontext struct: + // - 8-byte long (uc_flags) + // - 8-byte pointer (uc_link) + // - 24-byte stack_t + // - 128-byte signal set + // - 8 bytes of padding because sigcontext has 16-byte alignment + // - sigcontext/mcontext_t + // [1] + // https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/signal.c + int32_t offset = 128 + 8 + 8 + 24 + 128 + 8; + // Then sigcontext[2] is: + // - 8 byte fault address + // - 31 8 byte registers + // - 8 byte sp + // - 8 byte pc + // [2] + // https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/sigcontext.h + + // Skip fault address + offset += 8; + row->GetCFAValue().SetIsRegisterPlusOffset(arm64_dwarf::sp, offset); + + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x0, 0 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x1, 1 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x2, 2 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x3, 3 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x4, 4 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x5, 5 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x6, 6 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x7, 7 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x8, 8 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x9, 9 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x10, 10 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x11, 11 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x12, 12 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x13, 13 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x14, 14 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x15, 15 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x16, 16 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x17, 17 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x18, 18 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x19, 19 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x20, 20 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x21, 21 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x22, 22 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x23, 23 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x24, 24 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x25, 25 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x26, 26 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x27, 27 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x28, 28 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::fp, 29 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x30, 30 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::sp, 31 * 8, false); + row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::pc, 32 * 8, false); + + unwind_plan_sp = std::make_shared(eRegisterKindGeneric); + unwind_plan_sp->AppendRow(row); + unwind_plan_sp->SetSourceName("AArch64 Linux sigcontext"); + unwind_plan_sp->SetSourcedFromCompiler(eLazyBoolYes); + // Because sp is the same throughout the function + unwind_plan_sp->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes); + unwind_plan_sp->SetUnwindPlanForSignalTrap(eLazyBoolYes); + unwind_plan_sp->SetRegisterKind(eRegisterKindDWARF); + + return unwind_plan_sp; +} + +lldb::UnwindPlanSP +PlatformLinux::GetTrapHandlerUnwindPlan(const llvm::Triple &triple, + ConstString name) { + if (triple.isAArch64()) + return GetAArch64TrapHanlderUnwindPlan(name); + + return {}; +} + MmapArgList PlatformLinux::GetMmapArgumentList(const ArchSpec &arch, addr_t addr, addr_t length, unsigned prot, unsigned flags, diff --git a/lldb/source/Target/RegisterContextUnwind.cpp b/lldb/source/Target/RegisterContextUnwind.cpp --- a/lldb/source/Target/RegisterContextUnwind.cpp +++ b/lldb/source/Target/RegisterContextUnwind.cpp @@ -893,13 +893,22 @@ return arch_default_unwind_plan_sp; } - // If we're in _sigtramp(), unwinding past this frame requires special - // knowledge. On Mac OS X this knowledge is properly encoded in the eh_frame - // section, so prefer that if available. On other platforms we may need to - // provide a platform-specific UnwindPlan which encodes the details of how to - // unwind out of sigtramp. if (m_frame_type == eTrapHandlerFrame && process) { m_fast_unwind_plan_sp.reset(); + + // On some platforms the unwind information for signal handlers is not + // present or correct. Give the platform plugins a chance to provide + // substitute plan. Otherwise, use eh_frame. + if (m_sym_ctx_valid) { + lldb::PlatformSP platform = process->GetTarget().GetPlatform(); + unwind_plan_sp = platform->GetTrapHandlerUnwindPlan( + process->GetTarget().GetArchitecture().GetTriple(), + GetSymbolOrFunctionName(m_sym_ctx)); + + if (unwind_plan_sp) + return unwind_plan_sp; + } + unwind_plan_sp = func_unwinders_sp->GetEHFrameUnwindPlan(process->GetTarget()); if (!unwind_plan_sp) diff --git a/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py b/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py --- a/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py +++ b/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py @@ -16,8 +16,6 @@ NO_DEBUG_INFO_TESTCASE = True @skipIfWindows # signals do not exist on Windows - # Fails on Ubuntu Focal - @skipIf(archs=["aarch64"], oslist=["linux"]) @expectedFailureNetBSD def test_inferior_handle_sigabrt(self): """Inferior calls abort() and handles the resultant SIGABRT. diff --git a/lldb/test/API/linux/aarch64/unwind_signal/Makefile b/lldb/test/API/linux/aarch64/unwind_signal/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/unwind_signal/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py b/lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py @@ -0,0 +1,115 @@ +"""Test that we can unwind out of a signal handler. + Which for AArch64 Linux requires a specific unwind plan.""" + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from collections import namedtuple + + +# Since frames internally point to ExecutionContextRef which itself points +# to something else, we can't make a deep copy of them from Python. +# Instead save what we care about for comparison purposes. +# Ignoring the frame ID because what is frame 0 in the first catch will +# be frame 2 in the handler backtrace. +class FrameInfo(namedtuple('FrameInfo', ['function_name', 'sp', 'gp_regs'])): + # So assert failures are more readable + def __repr__(self): + reg_lines = [] + for k, v in self.gp_regs.items(): + if v is None: + value = "{}".format(v) + else: + value = "0x{:x}".format(v) + + reg_lines.append("{}: {}".format(k, value)) + + return "Fn: {} SP: 0x{:x}\n{}".format(self.function_name, + self.sp, + "\n".join(reg_lines)) + + +def make_frame_info(frame): + gp_regs = frame.GetRegisters().GetValueAtIndex(0) + reg_values = {} + err = lldb.SBError() + for i in range(0, 31): + name = 'x' + str(i) + # This will fail if we have no location information + value = gp_regs.GetChildMemberWithName(name).GetValueAsUnsigned(err) + reg_values[name] = value if err.Success() else None + + return FrameInfo(frame.GetDisplayFunctionName(), frame.GetSP(), reg_values) + + +class UnwindSignalTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + def test_unwind_signal(self): + """Inferior calls sigill() and handles the resultant SIGILL. + Stopped at a breakpoint in the handler, verify that the backtrace + includes the function that called sigill() and the common frames + in the initial signal catch match those at the breakpoint.""" + self.build() + exe = self.getBuildArtifact("a.out") + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + process = target.LaunchSimple( + None, None, self.get_process_working_directory()) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertEqual(process.GetState(), lldb.eStateStopped) + signo = process.GetUnixSignals().GetSignalNumberFromName("SIGILL") + + thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonSignal) + self.assertTrue( + thread and thread.IsValid(), + "Thread should be stopped due to a signal") + self.assertTrue( + thread.GetStopReasonDataCount() >= 1, + "There should be data in the event.") + self.assertEqual(thread.GetStopReasonDataAtIndex(0), + signo, "The stop signal should be SIGILL") + + # Save the backtrace frames to compare to the handler backtrace later. + signal_frames = [make_frame_info(f) for f in thread.get_thread_frames()] + + # Continue to breakpoint in abort handler + bkpt = target.FindBreakpointByID( + lldbutil.run_break_set_by_source_regexp(self, "Set a breakpoint here")) + threads = lldbutil.continue_to_breakpoint(process, bkpt) + self.assertEqual(len(threads), 1, "Expected single thread") + thread = threads[0] + + # Expect breakpoint in 'handler' + frame = thread.GetFrameAtIndex(0) + self.assertEqual(frame.GetDisplayFunctionName(), "handler", "Unexpected break?") + + # Expect that unwinding should find 'sigill' + found_caller = False + for frame in thread.get_thread_frames(): + if frame.GetDisplayFunctionName() == "sigill": + found_caller = True + break + + self.assertTrue(found_caller, "Unwinding did not find func that caused the SIGILL") + + # The signal handler backtrace has extra frames at the start, remove those + handler_frames = thread.get_thread_frames()[-len(signal_frames):] + handler_frames = [make_frame_info(f) for f in handler_frames] + + # Check that frames present in both backtraces have the same content + self.assertEqual(signal_frames, handler_frames, "Common backtrace frames do not match") + + # Continue until we exit. + process.Continue() + self.assertEqual(process.GetState(), lldb.eStateExited) + self.assertEqual(process.GetExitStatus(), 0) diff --git a/lldb/test/API/linux/aarch64/unwind_signal/main.c b/lldb/test/API/linux/aarch64/unwind_signal/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/unwind_signal/main.c @@ -0,0 +1,41 @@ +#include +#include +#include + +void handler(int sig) { + printf("Set a breakpoint here.\n"); + exit(0); +} + +static void sigill() { + // Set all general registers to known values to check + // that the signal unwind plan sets their locations correctly. +#define SETREG(N) "mov x" N ", #" N "\n\t" + asm( + /* clang-format off */ + SETREG("0") SETREG("1") SETREG("2") SETREG("3") + SETREG("4") SETREG("5") SETREG("6") SETREG("7") + SETREG("8") SETREG("9") SETREG("10") SETREG("11") + SETREG("12") SETREG("13") SETREG("14") SETREG("15") + SETREG("16") SETREG("17") SETREG("18") SETREG("19") + SETREG("20") SETREG("21") SETREG("22") SETREG("23") + SETREG("24") SETREG("25") SETREG("26") SETREG("27") + SETREG("28") SETREG("29") SETREG("30") /* 31 is xzr/sp */ + /* clang-format on */ + ".inst 0x00000000\n\t" // udf #0 (old binutils don't support do udf) + :: + : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", + "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19", + "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", + "x29", "x30"); +} + +int main() { + if (signal(SIGILL, handler) == SIG_ERR) { + perror("signal"); + return 1; + } + + sigill(); + return 2; +}