[Lldb-commits] [lldb] Fix lldb-dap non-leaf frame source resolution issue (PR #165944)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Nov 5 12:31:31 PST 2025
https://github.com/jeffreytan81 updated https://github.com/llvm/llvm-project/pull/165944
>From 0f9f57ffb0bcdfb0ac8706a545c23c8160dc4de1 Mon Sep 17 00:00:00 2001
From: Jeffrey Tan <jeffreytan at fb.com>
Date: Fri, 31 Oct 2025 17:12:00 -0700
Subject: [PATCH 1/2] Fix lldb-dap non-leaf frame source resolution issue
---
lldb/tools/lldb-dap/DAP.cpp | 23 ++++++++++++++++++-----
lldb/tools/lldb-dap/ProtocolUtils.cpp | 7 +++----
lldb/tools/lldb-dap/ProtocolUtils.h | 3 ++-
3 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index f009a902f79e7..ebafda95ff583 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -657,18 +657,31 @@ std::optional<protocol::Source> DAP::ResolveSource(const lldb::SBFrame &frame) {
if (!frame.IsValid())
return std::nullopt;
- const lldb::SBAddress frame_pc = frame.GetPCAddress();
- if (DisplayAssemblySource(debugger, frame_pc))
+ // IMPORTANT: Get line entry from symbol context, NOT from PC address.
+ // When a frame's PC points to a return address (the instruction
+ // after a call), that address may have line number 0 for compiler generated
+ // code.
+ //
+ // EXAMPLE: If PC is at 0x1004 (frame return address after the call
+ // instruction) with no line info, but 0x1003 (in the middle of previous call
+ // instruction) is at line 42, symbol context returns line 42.
+ //
+ // NOTE: This issue is non-deterministic and depends on compiler debug info
+ // generation, making it difficult to create a reliable automated test.
+ const lldb::SBLineEntry frame_line_entry = frame.GetLineEntry();
+ if (DisplayAssemblySource(debugger, frame_line_entry)) {
+ const lldb::SBAddress frame_pc = frame.GetPCAddress();
return ResolveAssemblySource(frame_pc);
+ }
- return CreateSource(frame.GetLineEntry().GetFileSpec());
+ return CreateSource(frame_line_entry.GetFileSpec());
}
std::optional<protocol::Source> DAP::ResolveSource(lldb::SBAddress address) {
- if (DisplayAssemblySource(debugger, address))
+ lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address);
+ if (DisplayAssemblySource(debugger, line_entry))
return ResolveAssemblySource(address);
- lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address);
if (!line_entry.IsValid())
return std::nullopt;
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp
index 868c67ca72986..acf31b03f7af0 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.cpp
+++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp
@@ -27,7 +27,7 @@ using namespace lldb_dap::protocol;
namespace lldb_dap {
static bool ShouldDisplayAssemblySource(
- lldb::SBAddress address,
+ lldb::SBLineEntry line_entry,
lldb::StopDisassemblyType stop_disassembly_display) {
if (stop_disassembly_display == lldb::eStopDisassemblyTypeNever)
return false;
@@ -37,7 +37,6 @@ static bool ShouldDisplayAssemblySource(
// A line entry of 0 indicates the line is compiler generated i.e. no source
// file is associated with the frame.
- auto line_entry = address.GetLineEntry();
auto file_spec = line_entry.GetFileSpec();
if (!file_spec.IsValid() || line_entry.GetLine() == 0 ||
line_entry.GetLine() == LLDB_INVALID_LINE_NUMBER)
@@ -174,10 +173,10 @@ bool IsAssemblySource(const protocol::Source &source) {
}
bool DisplayAssemblySource(lldb::SBDebugger &debugger,
- lldb::SBAddress address) {
+ lldb::SBLineEntry line_entry) {
const lldb::StopDisassemblyType stop_disassembly_display =
GetStopDisassemblyDisplay(debugger);
- return ShouldDisplayAssemblySource(address, stop_disassembly_display);
+ return ShouldDisplayAssemblySource(line_entry, stop_disassembly_display);
}
std::string GetLoadAddressString(const lldb::addr_t addr) {
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.h b/lldb/tools/lldb-dap/ProtocolUtils.h
index a1f7ae0661914..f4d576ba9f608 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.h
+++ b/lldb/tools/lldb-dap/ProtocolUtils.h
@@ -53,7 +53,8 @@ std::optional<protocol::Source> CreateSource(const lldb::SBFileSpec &file);
/// Checks if the given source is for assembly code.
bool IsAssemblySource(const protocol::Source &source);
-bool DisplayAssemblySource(lldb::SBDebugger &debugger, lldb::SBAddress address);
+bool DisplayAssemblySource(lldb::SBDebugger &debugger,
+ lldb::SBLineEntry line_entry);
/// Get the address as a 16-digit hex string, e.g. "0x0000000000012345"
std::string GetLoadAddressString(const lldb::addr_t addr);
>From ca2a8d8b7b708952ec86a6e278bf2c91fbef0b5d Mon Sep 17 00:00:00 2001
From: Jeffrey Tan <jeffreytan at fb.com>
Date: Wed, 5 Nov 2025 12:28:14 -0800
Subject: [PATCH 2/2] Add Test
---
.../stackTraceCompilerGeneratedCode/Makefile | 3 +
...TestDAP_stackTraceCompilerGeneratedCode.py | 82 +++++++++++++++++++
.../stackTraceCompilerGeneratedCode/main.c | 14 ++++
lldb/tools/lldb-dap/DAP.cpp | 4 +-
4 files changed, 100 insertions(+), 3 deletions(-)
create mode 100644 lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/Makefile
create mode 100644 lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/TestDAP_stackTraceCompilerGeneratedCode.py
create mode 100644 lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/main.c
diff --git a/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/Makefile b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/TestDAP_stackTraceCompilerGeneratedCode.py b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/TestDAP_stackTraceCompilerGeneratedCode.py
new file mode 100644
index 0000000000000..4820d549445ab
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/TestDAP_stackTraceCompilerGeneratedCode.py
@@ -0,0 +1,82 @@
+"""
+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):
+ @skipIfWindows
+ 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 that contain
+ compiler-generated code (code without valid source location information).
+ Specifically, it tests that when a non-leaf frame contains compiler-generated
+ code, LLDB properly resolves the source location to the last valid source line
+ before the compiler-generated code, rather than failing to symbolicate the frame.
+
+ Critical Assumption:
+ ====================
+ This test assumes that there is NO extra machine code between the call
+ instruction and the start of the next source line. If the compiler were
+ to insert additional instructions (e.g., for stack frame setup, register
+ spilling, or other optimizations) between:
+ - The call to bar()
+ - The compiler-generated "return 0;" code
+
+ Then the return address might point to valid intermediate code, and this
+ test scenario would not properly verify the fix for compiler-generated code.
+
+ The #line 0 directive ensures the compiler marks subsequent code as having
+ invalid source information, creating the test scenario where LLDB must
+ fall back to the previous valid source line.
+ """
+ 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]
+ print(f"foo_frame: {foo_frame}")
+
+ # 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 `")
diff --git a/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/main.c b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/main.c
new file mode 100644
index 0000000000000..2ac5e506384dd
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/main.c
@@ -0,0 +1,14 @@
+void bar() {
+ int val = 32; // breakpoint here
+}
+
+int foo() {
+ bar(); // foo call bar
+#line 0 "test.cpp"
+ return 0;
+}
+
+int main(int argc, char const *argv[]) {
+ foo();
+ return 0;
+}
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index ebafda95ff583..995c006f1bf55 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -665,9 +665,7 @@ std::optional<protocol::Source> DAP::ResolveSource(const lldb::SBFrame &frame) {
// EXAMPLE: If PC is at 0x1004 (frame return address after the call
// instruction) with no line info, but 0x1003 (in the middle of previous call
// instruction) is at line 42, symbol context returns line 42.
- //
- // NOTE: This issue is non-deterministic and depends on compiler debug info
- // generation, making it difficult to create a reliable automated test.
+
const lldb::SBLineEntry frame_line_entry = frame.GetLineEntry();
if (DisplayAssemblySource(debugger, frame_line_entry)) {
const lldb::SBAddress frame_pc = frame.GetPCAddress();
More information about the lldb-commits
mailing list