Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lldb/source/Core/CoreProperties.td
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not familiar with the details, but I'd expect to return some frame object here to follow the pattern of the rest.

If it returns a number does something else look that up in the real frames?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DavidSpickett Exactly, if we return an integer (which is a valid frame index), it will create a BorrowStackFrame out of it and include it in the new backtrace: https://github.com/llvm/llvm-project/blob/main/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp#L168-L177

return None
Loading