Summary ------- While dogfooding lldb-dap, I observed that VSCode frequently displays certain stack frames as greyed out. Although these frames have valid debug information, double-clicking them shows disassembly instead of source code. However, running `bt` from the LLDB command line correctly displays source file and line information for these same frames, indicating this is an lldb-dap specific issue. Root Cause ---------- Investigation revealed that `DAP::ResolveSource()` incorrectly uses a frame's PC address directly to determine whether valid source line information exists. This approach works for leaf frames, but fails for non-leaf (caller) frames where the PC points to the return address immediately after a call instruction. This return address may fall into compiler-generated code with no associated line information, even though the actual call site has valid source location data. The correct approach is to use the symbol context's line entry, which LLDB resolves by effectively checking PC-1 for non-leaf frames, properly identifying the line information for the call instruction rather than the return address. Testing ------- Manually tested with VSCode debugging sessions on production workloads. Verified that non-leaf frames now correctly display source code instead of disassembly view. Before the change symptom: <img width="1013" height="216" alt="image" src="https://github.com/user-attachments/assets/9487fbc0-f438-4892-a8d2-1437dc25399b" /> And here is after the fix: <img width="1068" height="198" alt="image" src="https://github.com/user-attachments/assets/0d2ebaa7-cca6-4983-a1d1-1a26ae62c86f" /> --------- Co-authored-by: Jeffrey Tan <jeffreytan@fb.com>
67 lines
2.8 KiB
Python
67 lines
2.8 KiB
Python
"""
|
|
Test lldb-dap stackTrace request for compiler generated code
|
|
"""
|
|
|
|
import os
|
|
|
|
import lldbdap_testcase
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
|
|
|
|
class TestDAP_stackTraceCompilerGeneratedCode(lldbdap_testcase.DAPTestCaseBase):
|
|
def test_non_leaf_frame_compiler_generate_code(self):
|
|
"""
|
|
Test that non-leaf frames with compiler-generated code are properly resolved.
|
|
|
|
This test verifies that LLDB correctly handles stack frames containing
|
|
compiler-generated code (code without valid source location information).
|
|
When a non-leaf frame contains compiler-generated code immediately after a
|
|
call instruction, LLDB should resolve the frame's source location to the
|
|
call instruction's line, rather than to the compiler-generated code that
|
|
follows, which lacks proper symbolication information.
|
|
"""
|
|
program = self.getBuildArtifact("a.out")
|
|
self.build_and_launch(program)
|
|
source = "main.c"
|
|
|
|
# Set breakpoint inside bar() function
|
|
lines = [line_number(source, "// breakpoint here")]
|
|
breakpoint_ids = self.set_source_breakpoints(source, lines)
|
|
self.assertEqual(
|
|
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
|
|
)
|
|
|
|
self.continue_to_breakpoints(breakpoint_ids)
|
|
|
|
# Get the stack frames: [0] = bar(), [1] = foo(), [2] = main()
|
|
stack_frames = self.get_stackFrames()
|
|
self.assertGreater(len(stack_frames), 2, "Expected more than 2 stack frames")
|
|
|
|
# Examine the foo() frame (stack_frames[1])
|
|
# This is the critical frame containing compiler-generated code
|
|
foo_frame = stack_frames[1]
|
|
|
|
# Verify that the frame's line number points to the bar() call,
|
|
# not to the compiler-generated code after it
|
|
foo_call_bar_source_line = foo_frame.get("line")
|
|
self.assertEqual(
|
|
foo_call_bar_source_line,
|
|
line_number(source, "foo call bar"),
|
|
"Expected foo call bar to be the source line of the frame",
|
|
)
|
|
|
|
# Verify the source file name is correctly resolved
|
|
foo_source_name = foo_frame.get("source", {}).get("name")
|
|
self.assertEqual(
|
|
foo_source_name, "main.c", "Expected foo source name to be main.c"
|
|
)
|
|
|
|
# When lldb fails to symbolicate a frame it will emit a fake assembly
|
|
# source with path of format <module>`<symbol> or <module>`<address> with
|
|
# sourceReference to retrieve disassembly source file.
|
|
# Verify that this didn't happen - the path should be a real file path.
|
|
foo_path = foo_frame.get("source", {}).get("path")
|
|
self.assertNotIn("`", foo_path, "Expected foo source path to not contain `")
|
|
self.continue_to_exit()
|