https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/172767
>From bad0eb0f2f7ab1286110a4ad48d890f69559e83b Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Thu, 18 Dec 2025 01:54:06 +0100 Subject: [PATCH] [lldb] Fix frame-format string missing space when module is invalid This patch is a follow-up to 96c733e to fix a missing space in the frame.pc format entity. This space was intended to be prepended to the module format entity scope but in case the module is not valid, which is often the case for python pc-less scripted frames, the space between the pc and the function name is missing. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/source/Core/CoreProperties.td | 4 +- .../TestScriptedFrameProvider.py | 86 +++++++++++++++++++ .../test_frame_providers.py | 66 ++++++++++++++ 3 files changed, 154 insertions(+), 2 deletions(-) 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 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
