Skip to content

Commit 2acd652

Browse files
jeffreytan81Jeffrey Tan
andauthored
Fix lldb-dap non-leaf frame source resolution issue (llvm#165944)
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 <[email protected]>
1 parent 362119d commit 2acd652

File tree

6 files changed

+100
-10
lines changed

6 files changed

+100
-10
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Test lldb-dap stackTrace request for compiler generated code
3+
"""
4+
5+
import os
6+
7+
import lldbdap_testcase
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.lldbtest import *
10+
11+
12+
class TestDAP_stackTraceCompilerGeneratedCode(lldbdap_testcase.DAPTestCaseBase):
13+
def test_non_leaf_frame_compiler_generate_code(self):
14+
"""
15+
Test that non-leaf frames with compiler-generated code are properly resolved.
16+
17+
This test verifies that LLDB correctly handles stack frames containing
18+
compiler-generated code (code without valid source location information).
19+
When a non-leaf frame contains compiler-generated code immediately after a
20+
call instruction, LLDB should resolve the frame's source location to the
21+
call instruction's line, rather than to the compiler-generated code that
22+
follows, which lacks proper symbolication information.
23+
"""
24+
program = self.getBuildArtifact("a.out")
25+
self.build_and_launch(program)
26+
source = "main.c"
27+
28+
# Set breakpoint inside bar() function
29+
lines = [line_number(source, "// breakpoint here")]
30+
breakpoint_ids = self.set_source_breakpoints(source, lines)
31+
self.assertEqual(
32+
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
33+
)
34+
35+
self.continue_to_breakpoints(breakpoint_ids)
36+
37+
# Get the stack frames: [0] = bar(), [1] = foo(), [2] = main()
38+
stack_frames = self.get_stackFrames()
39+
self.assertGreater(len(stack_frames), 2, "Expected more than 2 stack frames")
40+
41+
# Examine the foo() frame (stack_frames[1])
42+
# This is the critical frame containing compiler-generated code
43+
foo_frame = stack_frames[1]
44+
45+
# Verify that the frame's line number points to the bar() call,
46+
# not to the compiler-generated code after it
47+
foo_call_bar_source_line = foo_frame.get("line")
48+
self.assertEqual(
49+
foo_call_bar_source_line,
50+
line_number(source, "foo call bar"),
51+
"Expected foo call bar to be the source line of the frame",
52+
)
53+
54+
# Verify the source file name is correctly resolved
55+
foo_source_name = foo_frame.get("source", {}).get("name")
56+
self.assertEqual(
57+
foo_source_name, "main.c", "Expected foo source name to be main.c"
58+
)
59+
60+
# When lldb fails to symbolicate a frame it will emit a fake assembly
61+
# source with path of format <module>`<symbol> or <module>`<address> with
62+
# sourceReference to retrieve disassembly source file.
63+
# Verify that this didn't happen - the path should be a real file path.
64+
foo_path = foo_frame.get("source", {}).get("path")
65+
self.assertNotIn("`", foo_path, "Expected foo source path to not contain `")
66+
self.continue_to_exit()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
void bar() {
2+
int val = 32; // breakpoint here
3+
}
4+
5+
void at_line_zero() {}
6+
7+
int foo();
8+
9+
int main(int argc, char const *argv[]) {
10+
foo();
11+
return 0;
12+
}
13+
14+
int foo() {
15+
bar(); // foo call bar
16+
#line 0 "test.cpp"
17+
at_line_zero();
18+
return 0;
19+
}

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -657,18 +657,20 @@ std::optional<protocol::Source> DAP::ResolveSource(const lldb::SBFrame &frame) {
657657
if (!frame.IsValid())
658658
return std::nullopt;
659659

660-
const lldb::SBAddress frame_pc = frame.GetPCAddress();
661-
if (DisplayAssemblySource(debugger, frame_pc))
660+
const lldb::SBLineEntry frame_line_entry = frame.GetLineEntry();
661+
if (DisplayAssemblySource(debugger, frame_line_entry)) {
662+
const lldb::SBAddress frame_pc = frame.GetPCAddress();
662663
return ResolveAssemblySource(frame_pc);
664+
}
663665

664-
return CreateSource(frame.GetLineEntry().GetFileSpec());
666+
return CreateSource(frame_line_entry.GetFileSpec());
665667
}
666668

667669
std::optional<protocol::Source> DAP::ResolveSource(lldb::SBAddress address) {
668-
if (DisplayAssemblySource(debugger, address))
670+
lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address);
671+
if (DisplayAssemblySource(debugger, line_entry))
669672
return ResolveAssemblySource(address);
670673

671-
lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address);
672674
if (!line_entry.IsValid())
673675
return std::nullopt;
674676

lldb/tools/lldb-dap/ProtocolUtils.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ using namespace lldb_dap::protocol;
2727
namespace lldb_dap {
2828

2929
static bool ShouldDisplayAssemblySource(
30-
lldb::SBAddress address,
30+
lldb::SBLineEntry line_entry,
3131
lldb::StopDisassemblyType stop_disassembly_display) {
3232
if (stop_disassembly_display == lldb::eStopDisassemblyTypeNever)
3333
return false;
@@ -37,7 +37,6 @@ static bool ShouldDisplayAssemblySource(
3737

3838
// A line entry of 0 indicates the line is compiler generated i.e. no source
3939
// file is associated with the frame.
40-
auto line_entry = address.GetLineEntry();
4140
auto file_spec = line_entry.GetFileSpec();
4241
if (!file_spec.IsValid() || line_entry.GetLine() == 0 ||
4342
line_entry.GetLine() == LLDB_INVALID_LINE_NUMBER)
@@ -174,10 +173,10 @@ bool IsAssemblySource(const protocol::Source &source) {
174173
}
175174

176175
bool DisplayAssemblySource(lldb::SBDebugger &debugger,
177-
lldb::SBAddress address) {
176+
lldb::SBLineEntry line_entry) {
178177
const lldb::StopDisassemblyType stop_disassembly_display =
179178
GetStopDisassemblyDisplay(debugger);
180-
return ShouldDisplayAssemblySource(address, stop_disassembly_display);
179+
return ShouldDisplayAssemblySource(line_entry, stop_disassembly_display);
181180
}
182181

183182
std::string GetLoadAddressString(const lldb::addr_t addr) {

lldb/tools/lldb-dap/ProtocolUtils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ std::optional<protocol::Source> CreateSource(const lldb::SBFileSpec &file);
5353
/// Checks if the given source is for assembly code.
5454
bool IsAssemblySource(const protocol::Source &source);
5555

56-
bool DisplayAssemblySource(lldb::SBDebugger &debugger, lldb::SBAddress address);
56+
bool DisplayAssemblySource(lldb::SBDebugger &debugger,
57+
lldb::SBLineEntry line_entry);
5758

5859
/// Get the address as a 16-digit hex string, e.g. "0x0000000000012345"
5960
std::string GetLoadAddressString(const lldb::addr_t addr);

0 commit comments

Comments
 (0)