Skip to content

Commit

Permalink
Initial step in targets DAP support (#86623)
Browse files Browse the repository at this point in the history
This patch provides the initial implementation for the "Step Into
Specific/Step In Targets" feature in VSCode DAP.

The implementation disassembles all the call instructions in step range
and try to resolve operand name (assuming one operand) using debug info.
Later, the call target function name is chosen by end user and specified
in the StepInto() API call.

It is v1 because of using the existing step in target function name API.
This implementation has several limitations:
* Won't for indirect/virtual function call -- in most cases, our
disassembler won't be able to solve the indirect call target
address/name.
* Won't work for target function without debug info -- if the target
function has symbol but not debug info, the existing
ThreadPlanStepInRange won't stop.
* Relying on function names can be fragile -- if there is some middle
glue/thunk code, our disassembler can only resolve the glue/thunk code's
name not the real target function name. It can be fragile to depend
compiler/linker emits the same names for both.
* Does not support step into raw address call sites -- it is a valid
scenario that in Visual Studio debugger, user can explicitly choose a
raw address to step into which land in the function without debug
info/symbol, then choose UI to load the debug info on-demand for that
module/frame to continue exploring.

A more reliable design could be extending the ThreadPlanStepInRange to
support step in based on call-site instruction offset/PC which I will
propose in next iteration.

---------

Co-authored-by: jeffreytan81 <jeffreytan@fb.com>
  • Loading branch information
jeffreytan81 and jeffreytan81 committed Apr 25, 2024
1 parent 02660e2 commit 2f2e31c
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 9 deletions.
3 changes: 3 additions & 0 deletions lldb/include/lldb/API/SBLineEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class LLDB_API SBLineEntry {

lldb::SBAddress GetEndAddress() const;

lldb::SBAddress
GetSameLineContiguousAddressRangeEnd(bool include_inlined_functions) const;

explicit operator bool() const;

bool IsValid() const;
Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/API/SBSymbolContextList.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class LLDB_API SBSymbolContextList {
protected:
friend class SBModule;
friend class SBTarget;
friend class SBCompileUnit;

lldb_private::SymbolContextList *operator->() const;

Expand Down
4 changes: 4 additions & 0 deletions lldb/include/lldb/API/SBTarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,10 @@ class LLDB_API SBTarget {
uint32_t count,
const char *flavor_string);

lldb::SBInstructionList ReadInstructions(lldb::SBAddress start_addr,
lldb::SBAddress end_addr,
const char *flavor_string);

lldb::SBInstructionList GetInstructions(lldb::SBAddress base_addr,
const void *buf, size_t size);

Expand Down
21 changes: 16 additions & 5 deletions lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,23 +811,34 @@ def request_next(self, threadId):
command_dict = {"command": "next", "type": "request", "arguments": args_dict}
return self.send_recv(command_dict)

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

def request_stepInTargets(self, frameId):
if self.exit_status is not None:
raise ValueError("request_stepInTargets called after process exited")
args_dict = {"frameId": frameId}
command_dict = {
"command": "stepInTargets",
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)

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

def request_pause(self, threadId=None):
if self.exit_status is not None:
raise ValueError("request_continue called after process exited")
raise ValueError("request_pause called after process exited")
if threadId is None:
threadId = self.get_thread_id()
args_dict = {"threadId": threadId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ 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, waitForStop=True):
self.dap_server.request_stepIn(threadId=threadId)
def stepIn(self, threadId=None, targetId=None, waitForStop=True):
self.dap_server.request_stepIn(threadId=threadId, targetId=targetId)
if waitForStop:
return self.dap_server.wait_for_stopped()
return None
Expand Down
15 changes: 15 additions & 0 deletions lldb/source/API/SBLineEntry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ SBAddress SBLineEntry::GetEndAddress() const {
return sb_address;
}

SBAddress SBLineEntry::GetSameLineContiguousAddressRangeEnd(
bool include_inlined_functions) const {
LLDB_INSTRUMENT_VA(this);

SBAddress sb_address;
if (m_opaque_up) {
AddressRange line_range = m_opaque_up->GetSameLineContiguousAddressRange(
include_inlined_functions);

sb_address.SetAddress(line_range.GetBaseAddress());
sb_address.OffsetAddress(line_range.GetByteSize());
}
return sb_address;
}

bool SBLineEntry::IsValid() const {
LLDB_INSTRUMENT_VA(this);
return this->operator bool();
Expand Down
24 changes: 24 additions & 0 deletions lldb/source/API/SBTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,30 @@ lldb::SBInstructionList SBTarget::ReadInstructions(lldb::SBAddress base_addr,
return sb_instructions;
}

lldb::SBInstructionList SBTarget::ReadInstructions(lldb::SBAddress start_addr,
lldb::SBAddress end_addr,
const char *flavor_string) {
LLDB_INSTRUMENT_VA(this, start_addr, end_addr, flavor_string);

SBInstructionList sb_instructions;

TargetSP target_sp(GetSP());
if (target_sp) {
lldb::addr_t start_load_addr = start_addr.GetLoadAddress(*this);
lldb::addr_t end_load_addr = end_addr.GetLoadAddress(*this);
if (end_load_addr > start_load_addr) {
lldb::addr_t size = end_load_addr - start_load_addr;

AddressRange range(start_load_addr, size);
const bool force_live_memory = true;
sb_instructions.SetDisassembler(Disassembler::DisassembleRange(
target_sp->GetArchitecture(), nullptr, flavor_string, *target_sp,
range, force_live_memory));
}
}
return sb_instructions;
}

lldb::SBInstructionList SBTarget::GetInstructions(lldb::SBAddress base_addr,
const void *buf,
size_t size) {
Expand Down
6 changes: 6 additions & 0 deletions lldb/test/API/tools/lldb-dap/stepInTargets/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

ENABLE_THREADS := YES

CXX_SOURCES := main.cpp

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
Test lldb-dap stepInTargets request
"""

import dap_server
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase
from lldbsuite.test import lldbutil


class TestDAP_stepInTargets(lldbdap_testcase.DAPTestCaseBase):
@skipIf(
archs=no_match(["x86_64"])
) # InstructionControlFlowKind for ARM is not supported yet.
def test_basic(self):
"""
Tests the basic stepping in targets with directly calls.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
source = "main.cpp"

breakpoint_line = line_number(source, "// set breakpoint here")
lines = [breakpoint_line]
# Set breakpoint in the thread function so we can step the threads
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)

threads = self.dap_server.get_threads()
self.assertEqual(len(threads), 1, "expect one thread")
tid = threads[0]["id"]

leaf_frame = self.dap_server.get_stackFrame()
self.assertIsNotNone(leaf_frame, "expect a leaf frame")

# Request all step in targets list and verify the response.
step_in_targets_response = self.dap_server.request_stepInTargets(
leaf_frame["id"]
)
self.assertEqual(step_in_targets_response["success"], True, "expect success")
self.assertIn(
"body", step_in_targets_response, "expect body field in response body"
)
self.assertIn(
"targets",
step_in_targets_response["body"],
"expect targets field in response body",
)

step_in_targets = step_in_targets_response["body"]["targets"]
self.assertEqual(len(step_in_targets), 3, "expect 3 step in targets")

# Verify the target names are correct.
self.assertEqual(step_in_targets[0]["label"], "bar()", "expect bar()")
self.assertEqual(step_in_targets[1]["label"], "bar2()", "expect bar2()")
self.assertEqual(
step_in_targets[2]["label"], "foo(int, int)", "expect foo(int, int)"
)

# Choose to step into second target and verify that we are in bar2()
self.stepIn(threadId=tid, targetId=step_in_targets[1]["id"], waitForStop=True)
leaf_frame = self.dap_server.get_stackFrame()
self.assertIsNotNone(leaf_frame, "expect a leaf frame")
self.assertEqual(leaf_frame["name"], "bar2()")
11 changes: 11 additions & 0 deletions lldb/test/API/tools/lldb-dap/stepInTargets/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

int foo(int val, int extra) { return val + extra; }

int bar() { return 22; }

int bar2() { return 54; }

int main(int argc, char const *argv[]) {
foo(bar(), bar2()); // set breakpoint here
return 0;
}
2 changes: 2 additions & 0 deletions lldb/tools/lldb-dap/DAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ struct DAP {
std::vector<std::string> exit_commands;
std::vector<std::string> stop_commands;
std::vector<std::string> terminate_commands;
// Map step in target id to list of function targets that user can choose.
llvm::DenseMap<lldb::addr_t, std::string> step_in_targets;
// A copy of the last LaunchRequest or AttachRequest so we can reuse its
// arguments if we get a RestartRequest.
std::optional<llvm::json::Object> last_launch_or_attach_request;
Expand Down
Loading

0 comments on commit 2f2e31c

Please sign in to comment.