Skip to content

Commit a52be0c

Browse files
santhoshe447Santhosh Kumar EllendulaSanthosh Kumar Ellendula
authored
[lldb-dap] Added "port" property to vscode "attach" command. (llvm#91570)
Adding a "port" property to the VsCode "attach" command likely extends the functionality of the debugger configuration to allow attaching to a process using PID or PORT number. Currently, the "Attach" configuration lets the user specify a pid. We tell the user to use the attachCommands property to run "gdb-remote ". Followed the below conditions for "attach" command with "port" and "pid" We should add a "port" property. If port is specified and pid is not, use that port to attach. If both port and pid are specified, return an error saying that the user can't specify both pid and port. Ex - launch.json { "version": "0.2.0", "configurations": [ { "name": "lldb-dap Debug", "type": "lldb-dap", "request": "attach", "gdb-remote-port":1234, "program": "${workspaceFolder}/a.out", "args": [], "stopOnEntry": false, "cwd": "${workspaceFolder}", "env": [], } ] } --------- Co-authored-by: Santhosh Kumar Ellendula <sellendu@hu-sellendu-hyd.qualcomm.com> Co-authored-by: Santhosh Kumar Ellendula <sellendu@hu-sellendu-lv.qualcomm.com>
1 parent 3834199 commit a52be0c

File tree

7 files changed

+373
-147
lines changed

7 files changed

+373
-147
lines changed

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

+6
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,8 @@ def request_attach(
574574
coreFile=None,
575575
postRunCommands=None,
576576
sourceMap=None,
577+
gdbRemotePort=None,
578+
gdbRemoteHostname=None,
577579
):
578580
args_dict = {}
579581
if pid is not None:
@@ -603,6 +605,10 @@ def request_attach(
603605
args_dict["postRunCommands"] = postRunCommands
604606
if sourceMap:
605607
args_dict["sourceMap"] = sourceMap
608+
if gdbRemotePort is not None:
609+
args_dict["gdb-remote-port"] = gdbRemotePort
610+
if gdbRemoteHostname is not None:
611+
args_dict["gdb-remote-hostname"] = gdbRemoteHostname
606612
command_dict = {"command": "attach", "type": "request", "arguments": args_dict}
607613
return self.send_recv(command_dict)
608614

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

+21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import dap_server
55
from lldbsuite.test.lldbtest import *
6+
from lldbsuite.test import lldbplatformutil
7+
import lldbgdbserverutils
68

79

810
class DAPTestCaseBase(TestBase):
@@ -299,6 +301,8 @@ def attach(
299301
sourceMap=None,
300302
sourceInitFile=False,
301303
expectFailure=False,
304+
gdbRemotePort=None,
305+
gdbRemoteHostname=None,
302306
):
303307
"""Build the default Makefile target, create the DAP debug adaptor,
304308
and attach to the process.
@@ -329,6 +333,8 @@ def cleanup():
329333
coreFile=coreFile,
330334
postRunCommands=postRunCommands,
331335
sourceMap=sourceMap,
336+
gdbRemotePort=gdbRemotePort,
337+
gdbRemoteHostname=gdbRemoteHostname,
332338
)
333339
if expectFailure:
334340
return response
@@ -485,3 +491,18 @@ def build_and_launch(
485491
launchCommands=launchCommands,
486492
expectFailure=expectFailure,
487493
)
494+
495+
def getBuiltinDebugServerTool(self):
496+
# Tries to find simulation/lldb-server/gdbserver tool path.
497+
server_tool = None
498+
if lldbplatformutil.getPlatform() == "linux":
499+
server_tool = lldbgdbserverutils.get_lldb_server_exe()
500+
if server_tool is None:
501+
self.dap_server.request_disconnect(terminateDebuggee=True)
502+
self.assertIsNotNone(server_tool, "lldb-server not found.")
503+
elif lldbplatformutil.getPlatform() == "macosx":
504+
server_tool = lldbgdbserverutils.get_debugserver_exe()
505+
if server_tool is None:
506+
self.dap_server.request_disconnect(terminateDebuggee=True)
507+
self.assertIsNotNone(server_tool, "debugserver not found.")
508+
return server_tool

lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py

+146
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from lldbsuite.test import configuration
1414
from textwrap import dedent
1515
import shutil
16+
import select
1617

1718

1819
def _get_support_exe(basename):
@@ -969,3 +970,148 @@ def __str__(self):
969970
self._output_queue,
970971
self._accumulated_output,
971972
)
973+
974+
975+
# A class representing a pipe for communicating with debug server.
976+
# This class includes menthods to open the pipe and read the port number from it.
977+
if lldbplatformutil.getHostPlatform() == "windows":
978+
import ctypes
979+
import ctypes.wintypes
980+
from ctypes.wintypes import BOOL, DWORD, HANDLE, LPCWSTR, LPDWORD, LPVOID
981+
982+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
983+
984+
PIPE_ACCESS_INBOUND = 1
985+
FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000
986+
FILE_FLAG_OVERLAPPED = 0x40000000
987+
PIPE_TYPE_BYTE = 0
988+
PIPE_REJECT_REMOTE_CLIENTS = 8
989+
INVALID_HANDLE_VALUE = -1
990+
ERROR_ACCESS_DENIED = 5
991+
ERROR_IO_PENDING = 997
992+
993+
class OVERLAPPED(ctypes.Structure):
994+
_fields_ = [
995+
("Internal", LPVOID),
996+
("InternalHigh", LPVOID),
997+
("Offset", DWORD),
998+
("OffsetHigh", DWORD),
999+
("hEvent", HANDLE),
1000+
]
1001+
1002+
def __init__(self):
1003+
super(OVERLAPPED, self).__init__(
1004+
Internal=0, InternalHigh=0, Offset=0, OffsetHigh=0, hEvent=None
1005+
)
1006+
1007+
LPOVERLAPPED = ctypes.POINTER(OVERLAPPED)
1008+
1009+
CreateNamedPipe = kernel32.CreateNamedPipeW
1010+
CreateNamedPipe.restype = HANDLE
1011+
CreateNamedPipe.argtypes = (
1012+
LPCWSTR,
1013+
DWORD,
1014+
DWORD,
1015+
DWORD,
1016+
DWORD,
1017+
DWORD,
1018+
DWORD,
1019+
LPVOID,
1020+
)
1021+
1022+
ConnectNamedPipe = kernel32.ConnectNamedPipe
1023+
ConnectNamedPipe.restype = BOOL
1024+
ConnectNamedPipe.argtypes = (HANDLE, LPOVERLAPPED)
1025+
1026+
CreateEvent = kernel32.CreateEventW
1027+
CreateEvent.restype = HANDLE
1028+
CreateEvent.argtypes = (LPVOID, BOOL, BOOL, LPCWSTR)
1029+
1030+
GetOverlappedResultEx = kernel32.GetOverlappedResultEx
1031+
GetOverlappedResultEx.restype = BOOL
1032+
GetOverlappedResultEx.argtypes = (HANDLE, LPOVERLAPPED, LPDWORD, DWORD, BOOL)
1033+
1034+
ReadFile = kernel32.ReadFile
1035+
ReadFile.restype = BOOL
1036+
ReadFile.argtypes = (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED)
1037+
1038+
CloseHandle = kernel32.CloseHandle
1039+
CloseHandle.restype = BOOL
1040+
CloseHandle.argtypes = (HANDLE,)
1041+
1042+
class Pipe(object):
1043+
def __init__(self, prefix):
1044+
while True:
1045+
self.name = "lldb-" + str(random.randrange(1e10))
1046+
full_name = "\\\\.\\pipe\\" + self.name
1047+
self._handle = CreateNamedPipe(
1048+
full_name,
1049+
PIPE_ACCESS_INBOUND
1050+
| FILE_FLAG_FIRST_PIPE_INSTANCE
1051+
| FILE_FLAG_OVERLAPPED,
1052+
PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,
1053+
1,
1054+
4096,
1055+
4096,
1056+
0,
1057+
None,
1058+
)
1059+
if self._handle != INVALID_HANDLE_VALUE:
1060+
break
1061+
if ctypes.get_last_error() != ERROR_ACCESS_DENIED:
1062+
raise ctypes.WinError(ctypes.get_last_error())
1063+
1064+
self._overlapped = OVERLAPPED()
1065+
self._overlapped.hEvent = CreateEvent(None, True, False, None)
1066+
result = ConnectNamedPipe(self._handle, self._overlapped)
1067+
assert result == 0
1068+
if ctypes.get_last_error() != ERROR_IO_PENDING:
1069+
raise ctypes.WinError(ctypes.get_last_error())
1070+
1071+
def finish_connection(self, timeout):
1072+
if not GetOverlappedResultEx(
1073+
self._handle,
1074+
self._overlapped,
1075+
ctypes.byref(DWORD(0)),
1076+
timeout * 1000,
1077+
True,
1078+
):
1079+
raise ctypes.WinError(ctypes.get_last_error())
1080+
1081+
def read(self, size, timeout):
1082+
buf = ctypes.create_string_buffer(size)
1083+
if not ReadFile(
1084+
self._handle, ctypes.byref(buf), size, None, self._overlapped
1085+
):
1086+
if ctypes.get_last_error() != ERROR_IO_PENDING:
1087+
raise ctypes.WinError(ctypes.get_last_error())
1088+
read = DWORD(0)
1089+
if not GetOverlappedResultEx(
1090+
self._handle, self._overlapped, ctypes.byref(read), timeout * 1000, True
1091+
):
1092+
raise ctypes.WinError(ctypes.get_last_error())
1093+
return buf.raw[0 : read.value]
1094+
1095+
def close(self):
1096+
CloseHandle(self._overlapped.hEvent)
1097+
CloseHandle(self._handle)
1098+
1099+
else:
1100+
1101+
class Pipe(object):
1102+
def __init__(self, prefix):
1103+
self.name = os.path.join(prefix, "stub_port_number")
1104+
os.mkfifo(self.name)
1105+
self._fd = os.open(self.name, os.O_RDONLY | os.O_NONBLOCK)
1106+
1107+
def finish_connection(self, timeout):
1108+
pass
1109+
1110+
def read(self, size, timeout):
1111+
(readers, _, _) = select.select([self._fd], [], [], timeout)
1112+
if self._fd not in readers:
1113+
raise TimeoutError
1114+
return os.read(self._fd, size)
1115+
1116+
def close(self):
1117+
os.close(self._fd)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
Test lldb-dap "port" configuration to "attach" request
3+
"""
4+
5+
6+
import dap_server
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test import lldbutil
10+
from lldbsuite.test import lldbplatformutil
11+
from lldbgdbserverutils import Pipe
12+
import lldbdap_testcase
13+
import os
14+
import shutil
15+
import subprocess
16+
import tempfile
17+
import threading
18+
import sys
19+
import socket
20+
21+
22+
class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase):
23+
default_timeout = 20
24+
25+
def set_and_hit_breakpoint(self, continueToExit=True):
26+
source = "main.c"
27+
main_source_path = os.path.join(os.getcwd(), source)
28+
breakpoint1_line = line_number(main_source_path, "// breakpoint 1")
29+
lines = [breakpoint1_line]
30+
# Set breakpoint in the thread function so we can step the threads
31+
breakpoint_ids = self.set_source_breakpoints(main_source_path, lines)
32+
self.assertEqual(
33+
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
34+
)
35+
self.continue_to_breakpoints(breakpoint_ids)
36+
if continueToExit:
37+
self.continue_to_exit()
38+
39+
def get_debug_server_command_line_args(self):
40+
args = []
41+
if lldbplatformutil.getPlatform() == "linux":
42+
args = ["gdbserver"]
43+
elif lldbplatformutil.getPlatform() == "macosx":
44+
args = ["--listen"]
45+
if lldb.remote_platform:
46+
args += ["*:0"]
47+
else:
48+
args += ["localhost:0"]
49+
return args
50+
51+
def get_debug_server_pipe(self):
52+
pipe = Pipe(self.getBuildDir())
53+
self.addTearDownHook(lambda: pipe.close())
54+
pipe.finish_connection(self.default_timeout)
55+
return pipe
56+
57+
@skipIfWindows
58+
@skipIfNetBSD
59+
def test_by_port(self):
60+
"""
61+
Tests attaching to a process by port.
62+
"""
63+
self.build_and_create_debug_adaptor()
64+
program = self.getBuildArtifact("a.out")
65+
66+
debug_server_tool = self.getBuiltinDebugServerTool()
67+
68+
pipe = self.get_debug_server_pipe()
69+
args = self.get_debug_server_command_line_args()
70+
args += [program]
71+
args += ["--named-pipe", pipe.name]
72+
73+
self.process = self.spawnSubprocess(
74+
debug_server_tool, args, install_remote=False
75+
)
76+
77+
# Read the port number from the debug server pipe.
78+
port = pipe.read(10, self.default_timeout)
79+
# Trim null byte, convert to int
80+
port = int(port[:-1])
81+
self.assertIsNotNone(
82+
port, " Failed to read the port number from debug server pipe"
83+
)
84+
85+
self.attach(program=program, gdbRemotePort=port, sourceInitFile=True)
86+
self.set_and_hit_breakpoint(continueToExit=True)
87+
self.process.terminate()
88+
89+
@skipIfWindows
90+
@skipIfNetBSD
91+
def test_by_port_and_pid(self):
92+
"""
93+
Tests attaching to a process by process ID and port number.
94+
"""
95+
self.build_and_create_debug_adaptor()
96+
program = self.getBuildArtifact("a.out")
97+
98+
# It is not necessary to launch "lldb-server" to obtain the actual port and pid for attaching.
99+
# However, when providing the port number and pid directly, "lldb-dap" throws an error message, which is expected.
100+
# So, used random pid and port numbers here.
101+
102+
pid = 1354
103+
port = 1234
104+
105+
response = self.attach(
106+
program=program,
107+
pid=pid,
108+
gdbRemotePort=port,
109+
sourceInitFile=True,
110+
expectFailure=True,
111+
)
112+
if not (response and response["success"]):
113+
self.assertFalse(
114+
response["success"], "The user can't specify both pid and port"
115+
)
116+
117+
@skipIfWindows
118+
@skipIfNetBSD
119+
def test_by_invalid_port(self):
120+
"""
121+
Tests attaching to a process by invalid port number 0.
122+
"""
123+
self.build_and_create_debug_adaptor()
124+
program = self.getBuildArtifact("a.out")
125+
126+
port = 0
127+
response = self.attach(
128+
program=program, gdbRemotePort=port, sourceInitFile=True, expectFailure=True
129+
)
130+
if not (response and response["success"]):
131+
self.assertFalse(
132+
response["success"],
133+
"The user can't attach with invalid port (%s)" % port,
134+
)
135+
136+
@skipIfWindows
137+
@skipIfNetBSD
138+
def test_by_illegal_port(self):
139+
"""
140+
Tests attaching to a process by illegal/greater port number 65536
141+
"""
142+
self.build_and_create_debug_adaptor()
143+
program = self.getBuildArtifact("a.out")
144+
145+
port = 65536
146+
args = [program]
147+
debug_server_tool = self.getBuiltinDebugServerTool()
148+
self.process = self.spawnSubprocess(
149+
debug_server_tool, args, install_remote=False
150+
)
151+
152+
response = self.attach(
153+
program=program, gdbRemotePort=port, sourceInitFile=True, expectFailure=True
154+
)
155+
if not (response and response["success"]):
156+
self.assertFalse(
157+
response["success"],
158+
"The user can't attach with illegal port (%s)" % port,
159+
)
160+
self.process.terminate()

0 commit comments

Comments
 (0)