Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
C_SOURCES := main.c

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -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 <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 `")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertNotIn("`", foo_path, "Expected foo source path to not contain `")
self.assertNotIn("`", foo_path, "Expected foo source path to not contain `")
self.continue_to_exit()

self.continue_to_exit()
Original file line number Diff line number Diff line change
@@ -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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please, forward declare foo() and move the definition to bottom of the file so the line directive does not affect the main() function

bar(); // foo call bar
#line 0 "test.cpp"
at_line_zero();
return 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest using a function call for more predictable code generation

void at_line_zero() {}

int foo() {
  bar(); // foo call bar
#line 0 "test.cpp"
  at_line_zero()

}
12 changes: 7 additions & 5 deletions lldb/tools/lldb-dap/DAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,18 +657,20 @@ 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))
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;

Expand Down
7 changes: 3 additions & 4 deletions lldb/tools/lldb-dap/ProtocolUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion lldb/tools/lldb-dap/ProtocolUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading