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..4ddf92402ad8a --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/TestDAP_stackTraceCompilerGeneratedCode.py @@ -0,0 +1,66 @@ +""" +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 ` or `
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() 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..dd3fcc295d492 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/main.c @@ -0,0 +1,19 @@ +void bar() { + int val = 32; // breakpoint here +} + +void at_line_zero() {} + +int foo(); + +int main(int argc, char const *argv[]) { + foo(); + return 0; +} + +int foo() { + bar(); // foo call bar +#line 0 "test.cpp" + at_line_zero(); + return 0; +} diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index f009a902f79e7..11aed33886edb 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -657,18 +657,20 @@ std::optional DAP::ResolveSource(const lldb::SBFrame &frame) { if (!frame.IsValid()) return std::nullopt; - const lldb::SBAddress frame_pc = frame.GetPCAddress(); - if (DisplayAssemblySource(debugger, frame_pc)) + 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 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 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);