https://github.com/medismailben updated 
https://github.com/llvm/llvm-project/pull/172767

>From fedb0e68f4ea4f7497447b4dbddeb0c8cc8de4d0 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <[email protected]>
Date: Thu, 18 Dec 2025 01:49:42 +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              | 87 +++++++++++++++++++
 .../test_frame_providers.py                   | 70 +++++++++++++++
 3 files changed, 159 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..6fb8dbd85b693 100644
--- 
a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ 
b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -552,3 +552,90 @@ 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..1f15c3d0d19d5 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,73 @@ 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

Reply via email to