| """ | |
| Test that we can backtrace through an OUTLINED_FUNCTION which is called in a non-ABI way. | |
| """ | |
| import lldb | |
| import json | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| class TestBacktraceThroughOutlinedFunction(TestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| @skipIfLLVMTargetMissing("AArch64") | |
| def test_corefile_backtrace(self): | |
| """Corefile test that we can backtrace through an OUTLINED_FUNCTION that has the epilogue of a function.""" | |
| target = self.dbg.CreateTarget("") | |
| exe = "binary.json" | |
| with open(exe) as f: | |
| exe_json = json.load(f) | |
| exe_uuid = exe_json["uuid"] | |
| target.AddModule(exe, "", exe_uuid) | |
| self.assertTrue(target.IsValid()) | |
| core = self.getBuildArtifact("core") | |
| self.yaml2macho_core("arm64-outlined-epilogue-core.yaml", core, exe_uuid) | |
| if self.TraceOn(): | |
| self.runCmd("log enable lldb unwind") | |
| process = target.LoadCore(core) | |
| self.assertTrue(process.IsValid()) | |
| if self.TraceOn(): | |
| self.runCmd("target list") | |
| self.runCmd("image list") | |
| self.runCmd("target modules dump sections") | |
| self.runCmd("target modules dump symtab") | |
| self.backtrace_through_outlined_epilogue(process) | |
| @skipIf(oslist=no_match([lldbplatformutil.getDarwinOSTriples()])) | |
| @skipIf(archs=no_match(["aarch64", "arm64", "arm64e"])) | |
| def test_live_process_backtrace(self): | |
| """Live process test that we can backtrace through two OUTLINED_FUNCTIONs with incorrect unwind instructions.""" | |
| self.build() | |
| target, process, thread, bp = lldbutil.run_to_name_breakpoint( | |
| self, "foo_midfunction" | |
| ) | |
| self.assertTrue(target.IsValid()) | |
| self.assertTrue(process.IsValid()) | |
| self.backtrace_through_outlined_midfunction(process) | |
| target.BreakpointCreateByName("foo_epilogue") | |
| process.Continue() | |
| self.assertTrue(process.IsValid()) | |
| self.backtrace_through_outlined_epilogue(process) | |
| # Test for the behavior of an architectural default unwind plan | |
| # getting above OUTLINED_FUNCTION_2. The OUTLINED_FUNCTION_2 | |
| # DWARF debug_frame/compact unwind instructions are invalid and | |
| # the entire stack frame will be rejected by the unwinder -- the | |
| # OUTLINED_FUNCTION_2 won't appear on the backtrace. | |
| def backtrace_through_outlined_midfunction(self, process): | |
| if self.TraceOn(): | |
| self.runCmd("bt") | |
| thread = process.GetThreadAtIndex(0) | |
| self.assertTrue(thread.IsValid()) | |
| important_frames = 4 | |
| # We may have a dyld `start` stack frame, or not; not relevant | |
| # to this test. | |
| self.assertGreaterEqual(thread.GetNumFrames(), important_frames) | |
| stackframe_names = [ | |
| "foo_midfunction", | |
| "OUTLINED_FUNCTION_2", | |
| "sub_main_function", | |
| "main", | |
| ] | |
| for i, name in enumerate(stackframe_names): | |
| self.assertEqual(name, thread.GetFrameAtIndex(i).GetSymbol().GetName()) | |
| # Test for the behavior of an architectural default unwind plan | |
| # getting above OUTLINED_FUNCTION_3. If we do an instruction analysis | |
| # unwind we'll have a duplication of OUTLINED_FUNCTION_3 in the | |
| # backtrace. | |
| def backtrace_through_outlined_epilogue(self, process): | |
| if self.TraceOn(): | |
| self.runCmd("bt") | |
| thread = process.GetThreadAtIndex(0) | |
| self.assertTrue(thread.IsValid()) | |
| important_frames = 4 | |
| # We may have a dyld `start` stack frame, or not; not relevant | |
| # to this test. | |
| self.assertGreaterEqual(thread.GetNumFrames(), important_frames) | |
| stackframe_names = [ | |
| "foo_epilogue", | |
| "OUTLINED_FUNCTION_3", | |
| "sub_main_function", | |
| "main", | |
| ] | |
| for i, name in enumerate(stackframe_names): | |
| self.assertEqual(name, thread.GetFrameAtIndex(i).GetSymbol().GetName()) |