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,96 @@ 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); + + // The sigcontext may also contain floating point and SVE registers. + // However this would require a dynamic unwind plan so they are not included + // here. + + unwind_plan_sp = std::make_shared(eRegisterKindDWARF); + 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); + + 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/functionalities/signal/handle-abrt/TestHandleAbort.py b/lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py copy from lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py copy to lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py --- a/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py +++ b/lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py @@ -1,6 +1,5 @@ -"""Test that we can unwind out of a SIGABRT handler""" - - +"""Test that we can unwind out of a signal handler. + Which for AArch64 Linux requires a specific unwind plan.""" import lldb @@ -9,33 +8,29 @@ from lldbsuite.test import lldbutil -class HandleAbortTestCase(TestBase): +class UnwindSignalTestCase(TestBase): mydir = TestBase.compute_mydir(__file__) 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. - Stopped at a breakpoint in the handler, verify that the backtrace - includes the function that called abort().""" + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + def test_unwind_signal(self): + """Inferior calls sigill() and handles the resultant SIGILL. + Stopped at a breakpoint in the handler, check that we can unwind + back to sigill() and get the expected register contents there.""" self.build() exe = self.getBuildArtifact("a.out") - # Create a target by the debugger. target = self.dbg.CreateTarget(exe) self.assertTrue(target, VALID_TARGET) - # launch 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("SIGABRT") + signo = process.GetUnixSignals().GetSignalNumberFromName("SIGILL") thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonSignal) self.assertTrue( @@ -45,9 +40,9 @@ thread.GetStopReasonDataCount() >= 1, "There should be data in the event.") self.assertEqual(thread.GetStopReasonDataAtIndex(0), - signo, "The stop signal should be SIGABRT") + signo, "The stop signal should be SIGILL") - # Continue to breakpoint in abort handler + # Continue to breakpoint in sigill handler bkpt = target.FindBreakpointByID( lldbutil.run_break_set_by_source_regexp(self, "Set a breakpoint here")) threads = lldbutil.continue_to_breakpoint(process, bkpt) @@ -58,13 +53,26 @@ frame = thread.GetFrameAtIndex(0) self.assertEqual(frame.GetDisplayFunctionName(), "handler", "Unexpected break?") - # Expect that unwinding should find 'abort_caller' - foundFoo = False - for frame in thread: - if frame.GetDisplayFunctionName() == "abort_caller": - foundFoo = True - - self.assertTrue(foundFoo, "Unwinding did not find func that called abort") + # Expect that unwinding should find 'sigill' + found_caller = False + for frame in thread.get_thread_frames(): + if frame.GetDisplayFunctionName() == "sigill": + # We should have ascending values in the x registers + regs = frame.GetRegisters().GetValueAtIndex(0) + err = lldb.SBError() + + for i in range(31): + name = 'x{}'.format(i) + value = regs.GetChildMemberWithName(name).GetValueAsUnsigned(err) + self.assertTrue(err.Success(), "Failed to get register {}: {}".format( + name, err)) + self.assertEqual(value, i, "Unexpected value for register {}".format( + name)) + + found_caller = True + break + + self.assertTrue(found_caller, "Unwinding did not find func that caused the SIGILL") # Continue until we exit. process.Continue() 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,64 @@ +#include +#include +#include + +void handler(int sig) { + // The kernel only changes a few registers so set them all to something other + // than the values in sigill() so that we can't fall back to real registers + // and still pass the test. +#define SETREG(N) "mov x" N ", #" N "+1\n\t" + asm volatile( + /* clang-format off */ + /* x0 is used for a parameter */ + 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") // fp/x29 needed for unwiding + SETREG("30") // 31 is xzr/sp + /* clang-format on */ + :: + : /* skipped 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", + /* skipped fp/x29 */ "x30"); + 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 volatile( + /* 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 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; +}