diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td index a54d5538f4c0c..99bb5a3fc6f73 100644 --- a/lldb/source/Core/CoreProperties.td +++ b/lldb/source/Core/CoreProperties.td @@ -59,7 +59,7 @@ let Definition = "debugger" in { Desc<"The default disassembly format string to use when disassembling instruction sequences.">; def FrameFormat: Property<"frame-format", "FormatEntity">, Global, - DefaultStringValue<"frame #${frame.index}: {${ansi.fg.cyan}${frame.pc}${ansi.normal}}{ ${module.file.basename}{`}}{${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">, + DefaultStringValue<"frame #${frame.index}: {${ansi.fg.cyan}${frame.pc}${ansi.normal} }{${module.file.basename}{`}}{${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">, Desc<"The default frame format string to use when displaying stack frame information for threads.">; def NotiftVoid: Property<"notify-void", "Boolean">, Global, @@ -235,7 +235,7 @@ let Definition = "debugger" in { Desc<"If true, LLDB will automatically escape non-printable and escape characters when formatting strings.">; def FrameFormatUnique: Property<"frame-format-unique", "FormatEntity">, Global, - DefaultStringValue<"frame #${frame.index}: {${ansi.fg.cyan}${frame.pc}${ansi.normal}}{ ${module.file.basename}{`}}{${function.name-without-args}{${frame.no-debug}${function.pc-offset}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">, + DefaultStringValue<"frame #${frame.index}: {${ansi.fg.cyan}${frame.pc}${ansi.normal} }{${module.file.basename}{`}}{${function.name-without-args}{${frame.no-debug}${function.pc-offset}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">, Desc<"The default frame format string to use when displaying stack frame information for threads from thread backtrace unique.">; def ShowAutosuggestion: Property<"show-autosuggestion", "Boolean">, Global, diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py index afe344b3f2ffb..e2b0f0f9cd50d 100644 --- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py +++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py @@ -552,3 +552,89 @@ def test_python_source_frames(self): frame3 = thread.GetFrameAtIndex(3) self.assertIsNotNone(frame3) self.assertIn("thread_func", frame3.GetFunctionName()) + + def test_valid_pc_no_module_frames(self): + """Test that frames with valid PC but no module display correctly in backtrace.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Get original frame count. + original_frame_count = thread.GetNumFrames() + self.assertGreaterEqual( + original_frame_count, 2, "Should have at least 2 real frames" + ) + + # Import the provider. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Register the ValidPCNoModuleFrameProvider. + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.ValidPCNoModuleFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify we have 2 more frames (the synthetic frames). + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count + 2, + "Should have original frames + 2 synthetic frames", + ) + + # Verify first two frames have valid PCs and function names. + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual( + frame0.GetFunctionName(), + "unknown_function_1", + "First frame should be unknown_function_1", + ) + self.assertTrue(frame0.IsSynthetic(), "Frame should be marked as synthetic") + self.assertEqual( + frame0.GetPC(), 0x1234000, "First frame should have PC 0x1234000" + ) + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual( + frame1.GetFunctionName(), + "unknown_function_2", + "Second frame should be unknown_function_2", + ) + self.assertTrue(frame1.IsSynthetic(), "Frame should be marked as synthetic") + self.assertEqual( + frame1.GetPC(), 0x5678000, "Second frame should have PC 0x5678000" + ) + + # Verify the frames display properly in backtrace. + # The backtrace should show the PC values without crashing or displaying + # invalid addresses like 0xffffffffffffffff. + self.runCmd("bt") + output = self.res.GetOutput() + + # Should show function names. + self.assertIn("unknown_function_1", output) + self.assertIn("unknown_function_2", output) + + # Should show PC addresses in hex format. + self.assertIn("0x0000000001234000", output) + self.assertIn("0x0000000005678000", output) + + # Verify PC and function name are properly separated by space. + self.assertIn("0x0000000001234000 unknown_function_1", output) + self.assertIn("0x0000000005678000 unknown_function_2", output) + + # Should NOT show invalid address. + self.assertNotIn("0xffffffffffffffff", output.lower()) + + # Verify frame 2 is the original real frame 0. + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertIn("thread_func", frame2.GetFunctionName()) diff --git a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py index 76f859760bf4f..e4367192af50d 100644 --- a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py +++ b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py @@ -314,3 +314,69 @@ def get_frame_at_index(self, index): # Pass through original frames return index - 3 return None + + +class ValidPCNoModuleFrame(ScriptedFrame): + """Scripted frame with a valid PC but no associated module.""" + + def __init__(self, thread, idx, pc, function_name): + args = lldb.SBStructuredData() + super().__init__(thread, args) + + self.idx = idx + self.pc = pc + self.function_name = function_name + + def get_id(self): + """Return the frame index.""" + return self.idx + + def get_pc(self): + """Return the program counter.""" + return self.pc + + def get_function_name(self): + """Return the function name.""" + return self.function_name + + def is_artificial(self): + """Not artificial.""" + return False + + def is_hidden(self): + """Not hidden.""" + return False + + def get_register_context(self): + """No register context.""" + return None + + +class ValidPCNoModuleFrameProvider(ScriptedFrameProvider): + """ + Provider that demonstrates frames with valid PC but no module. + + This tests that backtrace output handles frames that have a valid + program counter but cannot be resolved to any loaded module. + """ + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that prepends frames with valid PC but no module" + + def get_frame_at_index(self, index): + """Return frames with valid PCs but no module information.""" + if index == 0: + # Frame with valid PC (0x1234000) but no module + return ValidPCNoModuleFrame(self.thread, 0, 0x1234000, "unknown_function_1") + elif index == 1: + # Another frame with valid PC (0x5678000) but no module + return ValidPCNoModuleFrame(self.thread, 1, 0x5678000, "unknown_function_2") + elif index - 2 < len(self.input_frames): + # Pass through original frames + return index - 2 + return None