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 <[email protected]> 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 f50619eb7dc43a26e5c1525b91bf8db6733ee017 Mon Sep 17 00:00:00 2001 From: Jeffrey Tan <[email protected]> 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 ++++ 3 files changed, 99 insertions(+) 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; +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
