Skip to content

[lldb-dap] assembly breakpoints #139969

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from 17 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
Expand Up @@ -105,6 +105,36 @@ def dump_dap_log(log_file):
print("========= END =========", file=sys.stderr)


class Source(object):
def __init__(
self, path: Optional[str] = None, source_reference: Optional[int] = None
):
self._name = None
self._path = None
self._source_reference = None

if path is not None:
self._name = os.path.basename(path)
self._path = path
elif source_reference is not None:
self._source_reference = source_reference
else:
raise ValueError("Either path or source_reference must be provided")

def __str__(self):
return f"Source(name={self.name}, path={self.path}), source_reference={self.source_reference})"

def as_dict(self):
source_dict = {}
if self._name is not None:
source_dict["name"] = self._name
if self._path is not None:
source_dict["path"] = self._path
if self._source_reference is not None:
source_dict["sourceReference"] = self._source_reference
return source_dict


class DebugCommunication(object):
def __init__(
self,
Expand Down Expand Up @@ -948,15 +978,13 @@ def request_scopes(self, frameId):
command_dict = {"command": "scopes", "type": "request", "arguments": args_dict}
return self.send_recv(command_dict)

def request_setBreakpoints(self, file_path, line_array, data=None):
def request_setBreakpoints(self, source: Source, line_array, data=None):
"""data is array of parameters for breakpoints in line_array.
Each parameter object is 1:1 mapping with entries in line_entry.
It contains optional location/hitCondition/logMessage parameters.
"""
(dir, base) = os.path.split(file_path)
source_dict = {"name": base, "path": file_path}
args_dict = {
"source": source_dict,
"source": source.as_dict(),
"sourceModified": False,
}
if line_array is not None:
Expand Down Expand Up @@ -1381,7 +1409,7 @@ def run_vscode(dbg, args, options):
else:
source_to_lines[path] = [int(line)]
for source in source_to_lines:
dbg.request_setBreakpoints(source, source_to_lines[source])
dbg.request_setBreakpoints(Source(source), source_to_lines[source])
if options.funcBreakpoints:
dbg.request_setFunctionBreakpoints(options.funcBreakpoints)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import uuid

import dap_server
from dap_server import Source
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbplatformutil
import lldbgdbserverutils
Expand Down Expand Up @@ -55,7 +56,9 @@ def set_source_breakpoints(self, source_path, lines, data=None):
Each object in data is 1:1 mapping with the entry in lines.
It contains optional location/hitCondition/logMessage parameters.
"""
response = self.dap_server.request_setBreakpoints(source_path, lines, data)
response = self.dap_server.request_setBreakpoints(
Source(source_path), lines, data
)
if response is None or not response["success"]:
return []
breakpoints = response["body"]["breakpoints"]
Expand All @@ -64,6 +67,20 @@ def set_source_breakpoints(self, source_path, lines, data=None):
breakpoint_ids.append("%i" % (breakpoint["id"]))
return breakpoint_ids

def set_source_breakpoints_assembly(self, source_reference, lines, data=None):
response = self.dap_server.request_setBreakpoints(
Source(source_reference=source_reference),
lines,
data,
)
if response is None:
return []
breakpoints = response["body"]["breakpoints"]
breakpoint_ids = []
for breakpoint in breakpoints:
breakpoint_ids.append("%i" % (breakpoint["id"]))
return breakpoint_ids

def set_function_breakpoints(self, functions, condition=None, hitCondition=None):
"""Sets breakpoints by function name given an array of function names
and returns an array of strings containing the breakpoint IDs
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-dap/breakpoint-assembly/Makefile
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,83 @@
"""
Test lldb-dap setBreakpoints request in assembly source references.
"""


from lldbsuite.test.decorators import *
from dap_server import Source
import lldbdap_testcase


# @skipIfWindows
class TestDAP_setBreakpointsAssembly(lldbdap_testcase.DAPTestCaseBase):
def test_can_break_in_source_references(self):
"""Tests hitting assembly source breakpoints"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)

assmebly_func_breakpoints = self.set_function_breakpoints(["assembly_func"])
self.continue_to_breakpoints(assmebly_func_breakpoints)

assembly_func_frame = self.get_stackFrames()[0]
self.assertIn(
"sourceReference",
assembly_func_frame.get("source"),
"Expected assembly source frame",
)

line = assembly_func_frame["line"]

# Set an assembly breakpoint in the next line and check that it's hit
source_reference = assembly_func_frame["source"]["sourceReference"]
assembly_breakpoint_ids = self.set_source_breakpoints_assembly(
source_reference, [line + 1]
)
self.continue_to_breakpoints(assembly_breakpoint_ids)

# Continue again and verify it hits in the next function call
self.continue_to_breakpoints(assmebly_func_breakpoints)
self.continue_to_breakpoints(assembly_breakpoint_ids)

# Clear the breakpoint and then check that the assembly breakpoint does not hit next time
self.set_source_breakpoints_assembly(source_reference, [])
self.continue_to_breakpoints(assmebly_func_breakpoints)
self.continue_to_exit()

def test_break_on_invalid_source_reference(self):
"""Tests hitting assembly source breakpoints"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)

# Verify that setting a breakpoint on an invalid source reference fails
response = self.dap_server.request_setBreakpoints(
Source(source_reference=-1), [1]
)
self.assertIsNotNone(response)
breakpoints = response["body"]["breakpoints"]
self.assertEqual(len(breakpoints), 1)
breakpoint = breakpoints[0]
self.assertFalse(
breakpoint["verified"], "Expected breakpoint to not be verified"
)
self.assertIn("message", breakpoint, "Expected message to be present")
self.assertEqual(
breakpoint["message"],
"Invalid sourceReference.",
)

# Verify that setting a breakpoint on a source reference without a symbol also fails
response = self.dap_server.request_setBreakpoints(
Source(source_reference=0), [1]
)
self.assertIsNotNone(response)
breakpoints = response["body"]["breakpoints"]
self.assertEqual(len(breakpoints), 1)
breakpoint = breakpoints[0]
self.assertFalse(
breakpoint["verified"], "Expected breakpoint to not be verified"
)
self.assertIn("message", breakpoint, "Expected message to be present")
self.assertEqual(
breakpoint["message"],
"Breakpoints in assembly without a valid symbol are not supported yet.",
)
14 changes: 14 additions & 0 deletions lldb/test/API/tools/lldb-dap/breakpoint-assembly/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
__attribute__((nodebug)) int assembly_func(int n) {
n += 1;
n += 2;
n += 3;

return n;
}

int main(int argc, char const *argv[]) {
assembly_func(10);
assembly_func(20);
assembly_func(30);
return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Test lldb-dap setBreakpoints request
"""

import dap_server
from dap_server import Source
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
Expand Down Expand Up @@ -58,7 +58,7 @@ def test_breakpoint_events(self):
# Set breakpoints and verify that they got set correctly
dap_breakpoint_ids = []
response = self.dap_server.request_setBreakpoints(
main_source_path, [main_bp_line]
Source(main_source_path), [main_bp_line]
)
self.assertTrue(response["success"])
breakpoints = response["body"]["breakpoints"]
Expand All @@ -70,7 +70,7 @@ def test_breakpoint_events(self):
)

response = self.dap_server.request_setBreakpoints(
foo_source_path, [foo_bp1_line]
Source(foo_source_path), [foo_bp1_line]
)
self.assertTrue(response["success"])
breakpoints = response["body"]["breakpoints"]
Expand Down
30 changes: 19 additions & 11 deletions lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""


import dap_server
from dap_server import Source
import shutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
Expand Down Expand Up @@ -58,7 +58,9 @@ def test_source_map(self):
self.launch(program, sourceMap=source_map)

# breakpoint in main.cpp
response = self.dap_server.request_setBreakpoints(new_main_path, [main_line])
response = self.dap_server.request_setBreakpoints(
Source(new_main_path), [main_line]
)
breakpoints = response["body"]["breakpoints"]
self.assertEqual(len(breakpoints), 1)
breakpoint = breakpoints[0]
Expand All @@ -68,7 +70,9 @@ def test_source_map(self):
self.assertEqual(new_main_path, breakpoint["source"]["path"])

# 2nd breakpoint, which is from a dynamically loaded library
response = self.dap_server.request_setBreakpoints(new_other_path, [other_line])
response = self.dap_server.request_setBreakpoints(
Source(new_other_path), [other_line]
)
breakpoints = response["body"]["breakpoints"]
breakpoint = breakpoints[0]
self.assertEqual(breakpoint["line"], other_line)
Expand All @@ -81,7 +85,9 @@ def test_source_map(self):
self.verify_breakpoint_hit([other_breakpoint_id])

# 2nd breakpoint again, which should be valid at this point
response = self.dap_server.request_setBreakpoints(new_other_path, [other_line])
response = self.dap_server.request_setBreakpoints(
Source(new_other_path), [other_line]
)
breakpoints = response["body"]["breakpoints"]
breakpoint = breakpoints[0]
self.assertEqual(breakpoint["line"], other_line)
Expand Down Expand Up @@ -124,7 +130,7 @@ def test_set_and_clear(self):
self.build_and_launch(program)

# Set 3 breakpoints and verify that they got set correctly
response = self.dap_server.request_setBreakpoints(self.main_path, lines)
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
line_to_id = {}
breakpoints = response["body"]["breakpoints"]
self.assertEqual(
Expand All @@ -149,7 +155,7 @@ def test_set_and_clear(self):
lines.remove(second_line)
# Set 2 breakpoints and verify that the previous breakpoints that were
# set above are still set.
response = self.dap_server.request_setBreakpoints(self.main_path, lines)
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
breakpoints = response["body"]["breakpoints"]
self.assertEqual(
len(breakpoints),
Expand Down Expand Up @@ -194,7 +200,7 @@ def test_set_and_clear(self):
# Now clear all breakpoints for the source file by passing down an
# empty lines array
lines = []
response = self.dap_server.request_setBreakpoints(self.main_path, lines)
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
breakpoints = response["body"]["breakpoints"]
self.assertEqual(
len(breakpoints),
Expand All @@ -214,7 +220,7 @@ def test_set_and_clear(self):
# Now set a breakpoint again in the same source file and verify it
# was added.
lines = [second_line]
response = self.dap_server.request_setBreakpoints(self.main_path, lines)
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
if response:
breakpoints = response["body"]["breakpoints"]
self.assertEqual(
Expand Down Expand Up @@ -265,7 +271,7 @@ def test_clear_breakpoints_unset_breakpoints(self):
self.build_and_launch(program)

# Set one breakpoint and verify that it got set correctly.
response = self.dap_server.request_setBreakpoints(self.main_path, lines)
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
line_to_id = {}
breakpoints = response["body"]["breakpoints"]
self.assertEqual(
Expand All @@ -281,7 +287,7 @@ def test_clear_breakpoints_unset_breakpoints(self):
# Now clear all breakpoints for the source file by not setting the
# lines array.
lines = None
response = self.dap_server.request_setBreakpoints(self.main_path, lines)
response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines)
breakpoints = response["body"]["breakpoints"]
self.assertEqual(len(breakpoints), 0, "expect no source breakpoints")

Expand Down Expand Up @@ -357,7 +363,9 @@ def test_column_breakpoints(self):
# Set two breakpoints on the loop line at different columns.
columns = [13, 39]
response = self.dap_server.request_setBreakpoints(
self.main_path, [loop_line, loop_line], list({"column": c} for c in columns)
Source(self.main_path),
[loop_line, loop_line],
list({"column": c} for c in columns),
)

# Verify the breakpoints were set correctly
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import dap_server
from dap_server import Source
import shutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
Expand Down Expand Up @@ -33,7 +33,9 @@ def instruction_breakpoint_test(self):
self.build_and_launch(program)

# Set source breakpoint 1
response = self.dap_server.request_setBreakpoints(self.main_path, [main_line])
response = self.dap_server.request_setBreakpoints(
Source(self.main_path), [main_line]
)
breakpoints = response["body"]["breakpoints"]
self.assertEqual(len(breakpoints), 1)
breakpoint = breakpoints[0]
Expand Down
Loading
Loading