[lldb] Fix frame-format string missing space when module is invalid (#172767)

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 if 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 <ismail@bennani.ma>
This commit is contained in:
Med Ismail Bennani 2025-12-18 13:38:58 +01:00 committed by GitHub
parent 8fe1dddce6
commit 6767b86c34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 154 additions and 2 deletions

View File

@ -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,

View File

@ -552,3 +552,89 @@ class ScriptedFrameProviderTestCase(TestBase):
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())

View File

@ -314,3 +314,69 @@ class PythonSourceFrameProvider(ScriptedFrameProvider):
# 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