Skip to content

[lldb-dap] Implement StepGranularity for "next" and "step-in" #105464

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 3 commits into from
Aug 21, 2024
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
Expand Up @@ -816,17 +816,21 @@ def request_launch(
self.wait_for_event(filter=["process", "initialized"])
return response

def request_next(self, threadId):
def request_next(self, threadId, granularity="statement"):
if self.exit_status is not None:
raise ValueError("request_continue called after process exited")
args_dict = {"threadId": threadId}
args_dict = {"threadId": threadId, "granularity": granularity}
command_dict = {"command": "next", "type": "request", "arguments": args_dict}
return self.send_recv(command_dict)

def request_stepIn(self, threadId, targetId):
def request_stepIn(self, threadId, targetId, granularity="statement"):
if self.exit_status is not None:
raise ValueError("request_stepIn called after process exited")
args_dict = {"threadId": threadId, "targetId": targetId}
args_dict = {
"threadId": threadId,
"targetId": targetId,
"granularity": granularity,
}
command_dict = {"command": "stepIn", "type": "request", "arguments": args_dict}
return self.send_recv(command_dict)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,18 @@ def set_global(self, name, value, id=None):
"""Set a top level global variable only."""
return self.dap_server.request_setVariable(2, name, str(value), id=id)

def stepIn(self, threadId=None, targetId=None, waitForStop=True):
self.dap_server.request_stepIn(threadId=threadId, targetId=targetId)
def stepIn(
self, threadId=None, targetId=None, waitForStop=True, granularity="statement"
):
self.dap_server.request_stepIn(
threadId=threadId, targetId=targetId, granularity=granularity
)
if waitForStop:
return self.dap_server.wait_for_stopped()
return None

def stepOver(self, threadId=None, waitForStop=True):
self.dap_server.request_next(threadId=threadId)
def stepOver(self, threadId=None, waitForStop=True, granularity="statement"):
self.dap_server.request_next(threadId=threadId, granularity=granularity)
if waitForStop:
return self.dap_server.wait_for_stopped()
return None
Expand Down
13 changes: 13 additions & 0 deletions lldb/test/API/tools/lldb-dap/step/TestDAP_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,18 @@ def test_step(self):
self.assertEqual(x4, x3, "verify step over variable")
self.assertGreater(line4, line3, "verify step over line")
self.assertEqual(src1, src4, "verify step over source")

# Step a single assembly instruction.
# Unfortunately, there is no portable way to verify the correct
# stepping behavior here, because the generated assembly code
# depends highly on the compiler, its version, the operating
# system, and many more factors.
self.stepOver(
threadId=tid, waitForStop=True, granularity="instruction"
)
self.stepIn(
threadId=tid, waitForStop=True, granularity="instruction"
)

# only step one thread that is at the breakpoint and stop
break
33 changes: 31 additions & 2 deletions lldb/tools/lldb-dap/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,9 @@ void request_initialize(const llvm::json::Object &request) {
body.try_emplace("supportsCompletionsRequest", true);
// The debug adapter supports the disassembly request.
body.try_emplace("supportsDisassembleRequest", true);
// The debug adapter supports stepping granularities (argument `granularity`)
// for the stepping requests.
body.try_emplace("supportsSteppingGranularity", true);

llvm::json::Array completion_characters;
completion_characters.emplace_back(".");
Expand Down Expand Up @@ -1985,6 +1988,14 @@ void request_launch(const llvm::json::Object &request) {
g_dap.SendJSON(CreateEventObject("initialized"));
}

// Check if the step-granularity is `instruction`
static bool hasInstructionGranularity(const llvm::json::Object &requestArgs) {
if (std::optional<llvm::StringRef> value =
requestArgs.getString("granularity"))
return value == "instruction";
return false;
}

// "NextRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
Expand Down Expand Up @@ -2012,6 +2023,11 @@ void request_launch(const llvm::json::Object &request) {
// "threadId": {
// "type": "integer",
// "description": "Execute 'next' for this thread."
// },
// "granularity": {
// "$ref": "#/definitions/SteppingGranularity",
// "description": "Stepping granularity. If no granularity is specified, a
// granularity of `statement` is assumed."
// }
// },
// "required": [ "threadId" ]
Expand All @@ -2032,7 +2048,11 @@ void request_next(const llvm::json::Object &request) {
// Remember the thread ID that caused the resume so we can set the
// "threadCausedFocus" boolean value in the "stopped" events.
g_dap.focus_tid = thread.GetThreadID();
thread.StepOver();
if (hasInstructionGranularity(*arguments)) {
thread.StepInstruction(/*step_over=*/true);
} else {
thread.StepOver();
}
} else {
response["success"] = llvm::json::Value(false);
}
Expand Down Expand Up @@ -3193,6 +3213,11 @@ void request_stackTrace(const llvm::json::Object &request) {
// "targetId": {
// "type": "integer",
// "description": "Optional id of the target to step into."
// },
// "granularity": {
// "$ref": "#/definitions/SteppingGranularity",
// "description": "Stepping granularity. If no granularity is specified, a
// granularity of `statement` is assumed."
// }
// },
// "required": [ "threadId" ]
Expand Down Expand Up @@ -3223,7 +3248,11 @@ void request_stepIn(const llvm::json::Object &request) {
// Remember the thread ID that caused the resume so we can set the
// "threadCausedFocus" boolean value in the "stopped" events.
g_dap.focus_tid = thread.GetThreadID();
thread.StepInto(step_in_target.c_str(), run_mode);
if (hasInstructionGranularity(*arguments)) {
thread.StepInstruction(/*step_over=*/false);
} else {
thread.StepInto(step_in_target.c_str(), run_mode);
}
} else {
response["success"] = llvm::json::Value(false);
}
Expand Down
Loading