Skip to content

Commit 2f2e31c

Browse files
jeffreytan81jeffreytan81
and
jeffreytan81
authored
Initial step in targets DAP support (llvm#86623)
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 <[email protected]>
1 parent 02660e2 commit 2f2e31c

File tree

12 files changed

+296
-9
lines changed

12 files changed

+296
-9
lines changed

lldb/include/lldb/API/SBLineEntry.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class LLDB_API SBLineEntry {
2929

3030
lldb::SBAddress GetEndAddress() const;
3131

32+
lldb::SBAddress
33+
GetSameLineContiguousAddressRangeEnd(bool include_inlined_functions) const;
34+
3235
explicit operator bool() const;
3336

3437
bool IsValid() const;

lldb/include/lldb/API/SBSymbolContextList.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class LLDB_API SBSymbolContextList {
4444
protected:
4545
friend class SBModule;
4646
friend class SBTarget;
47+
friend class SBCompileUnit;
4748

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

lldb/include/lldb/API/SBTarget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,10 @@ class LLDB_API SBTarget {
879879
uint32_t count,
880880
const char *flavor_string);
881881

882+
lldb::SBInstructionList ReadInstructions(lldb::SBAddress start_addr,
883+
lldb::SBAddress end_addr,
884+
const char *flavor_string);
885+
882886
lldb::SBInstructionList GetInstructions(lldb::SBAddress base_addr,
883887
const void *buf, size_t size);
884888

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -811,23 +811,34 @@ def request_next(self, threadId):
811811
command_dict = {"command": "next", "type": "request", "arguments": args_dict}
812812
return self.send_recv(command_dict)
813813

814-
def request_stepIn(self, threadId):
814+
def request_stepIn(self, threadId, targetId):
815815
if self.exit_status is not None:
816-
raise ValueError("request_continue called after process exited")
817-
args_dict = {"threadId": threadId}
816+
raise ValueError("request_stepIn called after process exited")
817+
args_dict = {"threadId": threadId, "targetId": targetId}
818818
command_dict = {"command": "stepIn", "type": "request", "arguments": args_dict}
819819
return self.send_recv(command_dict)
820820

821+
def request_stepInTargets(self, frameId):
822+
if self.exit_status is not None:
823+
raise ValueError("request_stepInTargets called after process exited")
824+
args_dict = {"frameId": frameId}
825+
command_dict = {
826+
"command": "stepInTargets",
827+
"type": "request",
828+
"arguments": args_dict,
829+
}
830+
return self.send_recv(command_dict)
831+
821832
def request_stepOut(self, threadId):
822833
if self.exit_status is not None:
823-
raise ValueError("request_continue called after process exited")
834+
raise ValueError("request_stepOut called after process exited")
824835
args_dict = {"threadId": threadId}
825836
command_dict = {"command": "stepOut", "type": "request", "arguments": args_dict}
826837
return self.send_recv(command_dict)
827838

828839
def request_pause(self, threadId=None):
829840
if self.exit_status is not None:
830-
raise ValueError("request_continue called after process exited")
841+
raise ValueError("request_pause called after process exited")
831842
if threadId is None:
832843
threadId = self.get_thread_id()
833844
args_dict = {"threadId": threadId}

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ def set_global(self, name, value, id=None):
218218
"""Set a top level global variable only."""
219219
return self.dap_server.request_setVariable(2, name, str(value), id=id)
220220

221-
def stepIn(self, threadId=None, waitForStop=True):
222-
self.dap_server.request_stepIn(threadId=threadId)
221+
def stepIn(self, threadId=None, targetId=None, waitForStop=True):
222+
self.dap_server.request_stepIn(threadId=threadId, targetId=targetId)
223223
if waitForStop:
224224
return self.dap_server.wait_for_stopped()
225225
return None

lldb/source/API/SBLineEntry.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ SBAddress SBLineEntry::GetEndAddress() const {
6767
return sb_address;
6868
}
6969

70+
SBAddress SBLineEntry::GetSameLineContiguousAddressRangeEnd(
71+
bool include_inlined_functions) const {
72+
LLDB_INSTRUMENT_VA(this);
73+
74+
SBAddress sb_address;
75+
if (m_opaque_up) {
76+
AddressRange line_range = m_opaque_up->GetSameLineContiguousAddressRange(
77+
include_inlined_functions);
78+
79+
sb_address.SetAddress(line_range.GetBaseAddress());
80+
sb_address.OffsetAddress(line_range.GetByteSize());
81+
}
82+
return sb_address;
83+
}
84+
7085
bool SBLineEntry::IsValid() const {
7186
LLDB_INSTRUMENT_VA(this);
7287
return this->operator bool();

lldb/source/API/SBTarget.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,30 @@ lldb::SBInstructionList SBTarget::ReadInstructions(lldb::SBAddress base_addr,
20112011
return sb_instructions;
20122012
}
20132013

2014+
lldb::SBInstructionList SBTarget::ReadInstructions(lldb::SBAddress start_addr,
2015+
lldb::SBAddress end_addr,
2016+
const char *flavor_string) {
2017+
LLDB_INSTRUMENT_VA(this, start_addr, end_addr, flavor_string);
2018+
2019+
SBInstructionList sb_instructions;
2020+
2021+
TargetSP target_sp(GetSP());
2022+
if (target_sp) {
2023+
lldb::addr_t start_load_addr = start_addr.GetLoadAddress(*this);
2024+
lldb::addr_t end_load_addr = end_addr.GetLoadAddress(*this);
2025+
if (end_load_addr > start_load_addr) {
2026+
lldb::addr_t size = end_load_addr - start_load_addr;
2027+
2028+
AddressRange range(start_load_addr, size);
2029+
const bool force_live_memory = true;
2030+
sb_instructions.SetDisassembler(Disassembler::DisassembleRange(
2031+
target_sp->GetArchitecture(), nullptr, flavor_string, *target_sp,
2032+
range, force_live_memory));
2033+
}
2034+
}
2035+
return sb_instructions;
2036+
}
2037+
20142038
lldb::SBInstructionList SBTarget::GetInstructions(lldb::SBAddress base_addr,
20152039
const void *buf,
20162040
size_t size) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
ENABLE_THREADS := YES
3+
4+
CXX_SOURCES := main.cpp
5+
6+
include Makefile.rules
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Test lldb-dap stepInTargets request
3+
"""
4+
5+
import dap_server
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
import lldbdap_testcase
9+
from lldbsuite.test import lldbutil
10+
11+
12+
class TestDAP_stepInTargets(lldbdap_testcase.DAPTestCaseBase):
13+
@skipIf(
14+
archs=no_match(["x86_64"])
15+
) # InstructionControlFlowKind for ARM is not supported yet.
16+
def test_basic(self):
17+
"""
18+
Tests the basic stepping in targets with directly calls.
19+
"""
20+
program = self.getBuildArtifact("a.out")
21+
self.build_and_launch(program)
22+
source = "main.cpp"
23+
24+
breakpoint_line = line_number(source, "// set breakpoint here")
25+
lines = [breakpoint_line]
26+
# Set breakpoint in the thread function so we can step the threads
27+
breakpoint_ids = self.set_source_breakpoints(source, lines)
28+
self.assertEqual(
29+
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
30+
)
31+
self.continue_to_breakpoints(breakpoint_ids)
32+
33+
threads = self.dap_server.get_threads()
34+
self.assertEqual(len(threads), 1, "expect one thread")
35+
tid = threads[0]["id"]
36+
37+
leaf_frame = self.dap_server.get_stackFrame()
38+
self.assertIsNotNone(leaf_frame, "expect a leaf frame")
39+
40+
# Request all step in targets list and verify the response.
41+
step_in_targets_response = self.dap_server.request_stepInTargets(
42+
leaf_frame["id"]
43+
)
44+
self.assertEqual(step_in_targets_response["success"], True, "expect success")
45+
self.assertIn(
46+
"body", step_in_targets_response, "expect body field in response body"
47+
)
48+
self.assertIn(
49+
"targets",
50+
step_in_targets_response["body"],
51+
"expect targets field in response body",
52+
)
53+
54+
step_in_targets = step_in_targets_response["body"]["targets"]
55+
self.assertEqual(len(step_in_targets), 3, "expect 3 step in targets")
56+
57+
# Verify the target names are correct.
58+
self.assertEqual(step_in_targets[0]["label"], "bar()", "expect bar()")
59+
self.assertEqual(step_in_targets[1]["label"], "bar2()", "expect bar2()")
60+
self.assertEqual(
61+
step_in_targets[2]["label"], "foo(int, int)", "expect foo(int, int)"
62+
)
63+
64+
# Choose to step into second target and verify that we are in bar2()
65+
self.stepIn(threadId=tid, targetId=step_in_targets[1]["id"], waitForStop=True)
66+
leaf_frame = self.dap_server.get_stackFrame()
67+
self.assertIsNotNone(leaf_frame, "expect a leaf frame")
68+
self.assertEqual(leaf_frame["name"], "bar2()")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
int foo(int val, int extra) { return val + extra; }
3+
4+
int bar() { return 22; }
5+
6+
int bar2() { return 54; }
7+
8+
int main(int argc, char const *argv[]) {
9+
foo(bar(), bar2()); // set breakpoint here
10+
return 0;
11+
}

lldb/tools/lldb-dap/DAP.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ struct DAP {
162162
std::vector<std::string> exit_commands;
163163
std::vector<std::string> stop_commands;
164164
std::vector<std::string> terminate_commands;
165+
// Map step in target id to list of function targets that user can choose.
166+
llvm::DenseMap<lldb::addr_t, std::string> step_in_targets;
165167
// A copy of the last LaunchRequest or AttachRequest so we can reuse its
166168
// arguments if we get a RestartRequest.
167169
std::optional<llvm::json::Object> last_launch_or_attach_request;

0 commit comments

Comments
 (0)