Skip to content
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
102 changes: 75 additions & 27 deletions lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ def request_setBreakpoints(self, file_path, line_array, data=None):
"sourceModified": False,
}
if line_array is not None:
args_dict["lines"] = "%s" % line_array
args_dict["lines"] = line_array
breakpoints = []
for i, line in enumerate(line_array):
breakpoint_data = None
Expand Down Expand Up @@ -1170,40 +1170,88 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
}
return self.send_recv(command_dict)


class DebugAdaptorServer(DebugCommunication):
def __init__(
self,
executable=None,
port=None,
connection=None,
init_commands=[],
log_file=None,
env=None,
):
self.process = None
self.connection = None
if executable is not None:
adaptor_env = os.environ.copy()
if env is not None:
adaptor_env.update(env)

if log_file:
adaptor_env["LLDBDAP_LOG"] = log_file
self.process = subprocess.Popen(
[executable],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=adaptor_env,
process, connection = DebugAdaptorServer.launch(
executable=executable, connection=connection, env=env, log_file=log_file
)
self.process = process
self.connection = connection

if connection is not None:
scheme, address = connection.split("://")
if scheme == "unix-connect": # unix-connect:///path
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(address)
elif scheme == "connection": # connection://[host]:port
host, port = address.rsplit(":", 1)
# create_connection with try both ipv4 and ipv6.
s = socket.create_connection((host.strip("[]"), int(port)))
else:
raise ValueError("invalid connection: {}".format(connection))
DebugCommunication.__init__(
self, self.process.stdout, self.process.stdin, init_commands, log_file
self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file
)
elif port is not None:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", port))
self.connection = connection
else:
DebugCommunication.__init__(
self, s.makefile("r"), s.makefile("w"), init_commands
self, self.process.stdout, self.process.stdin, init_commands, log_file
)

@classmethod
def launch(cls, /, executable, env=None, log_file=None, connection=None):
adaptor_env = os.environ.copy()
if env is not None:
adaptor_env.update(env)

if log_file:
adaptor_env["LLDBDAP_LOG"] = log_file
args = [executable]

if connection is not None:
args.append("--connection")
args.append(connection)

process = subprocess.Popen(
args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=adaptor_env,
)

if connection is None:
return (process, None)

# lldb-dap will print the listening address once the listener is
# made to stdout. The listener is formatted like
# `connection://host:port` or `unix-connection:///path`.
expected_prefix = "Listening for: "
out = process.stdout.readline().decode()
if not out.startswith(expected_prefix):
self.process.kill()
raise ValueError(
"lldb-dap failed to print listening address, expected '{}', got '{}'".format(
expected_prefix, out
)
)

# If the listener expanded into multiple addresses, use the first.
connection = out.removeprefix(expected_prefix).rstrip("\r\n").split(",", 1)[0]

return (process, connection)

def get_pid(self):
if self.process:
return self.process.pid
Expand Down Expand Up @@ -1369,10 +1417,9 @@ def main():
)

parser.add_option(
"--port",
type="int",
dest="port",
help="Attach a socket to a port instead of using STDIN for VSCode",
"--connection",
dest="connection",
help="Attach a socket connection of using STDIN for VSCode",
default=None,
)

Expand Down Expand Up @@ -1518,15 +1565,16 @@ def main():

(options, args) = parser.parse_args(sys.argv[1:])

if options.vscode_path is None and options.port is None:
if options.vscode_path is None and options.connection is None:
print(
"error: must either specify a path to a Visual Studio Code "
"Debug Adaptor vscode executable path using the --vscode "
"option, or a port to attach to for an existing lldb-dap "
"using the --port option"
"option, or using the --connection option"
)
return
dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
dbg = DebugAdaptorServer(
executable=options.vscode_path, connection=options.connection
)
if options.debug:
raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
if options.replay:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import time
import subprocess

import dap_server
from lldbsuite.test.lldbtest import *
Expand All @@ -10,17 +11,18 @@
class DAPTestCaseBase(TestBase):
# set timeout based on whether ASAN was enabled or not. Increase
# timeout by a factor of 10 if ASAN is enabled.
timeoutval = 10 * (10 if ('ASAN_OPTIONS' in os.environ) else 1)
timeoutval = 10 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
NO_DEBUG_INFO_TESTCASE = True

def create_debug_adaptor(self, lldbDAPEnv=None):
def create_debug_adaptor(self, lldbDAPEnv=None, connection=None):
"""Create the Visual Studio Code debug adaptor"""
self.assertTrue(
is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable"
)
log_file_path = self.getBuildArtifact("dap.txt")
self.dap_server = dap_server.DebugAdaptorServer(
executable=self.lldbDAPExec,
connection=connection,
init_commands=self.setUpCommands(),
log_file=log_file_path,
env=lldbDAPEnv,
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-dap/server/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
C_SOURCES := main.c

include Makefile.rules
107 changes: 107 additions & 0 deletions lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""
Test lldb-dap server integration.
"""

import os
import signal
import tempfile

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


class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
def start_server(self, connection):
log_file_path = self.getBuildArtifact("dap.txt")
(process, connection) = dap_server.DebugAdaptorServer.launch(
executable=self.lldbDAPExec,
connection=connection,
log_file=log_file_path,
)

def cleanup():
process.terminate()

self.addTearDownHook(cleanup)

return (process, connection)

def run_debug_session(self, connection, name):
self.dap_server = dap_server.DebugAdaptorServer(
connection=connection,
)
program = self.getBuildArtifact("a.out")
source = "main.c"
breakpoint_line = line_number(source, "// breakpoint")

self.launch(
program,
args=[name],
disconnectAutomatically=False,
)
self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()
self.continue_to_exit()
output = self.get_stdout()
self.assertEqual(output, f"Hello {name}!\r\n")
self.dap_server.request_disconnect()

def test_server_port(self):
"""
Test launching a binary with a lldb-dap in server mode on a specific port.
"""
self.build()
(_, connection) = self.start_server(connection="tcp://localhost:0")
self.run_debug_session(connection, "Alice")
self.run_debug_session(connection, "Bob")

@skipIfWindows
def test_server_unix_socket(self):
"""
Test launching a binary with a lldb-dap in server mode on a unix socket.
"""
dir = tempfile.gettempdir()
name = dir + "/dap-connection-" + str(os.getpid())

def cleanup():
os.unlink(name)

self.addTearDownHook(cleanup)

self.build()
(_, connection) = self.start_server(connection="unix://" + name)
self.run_debug_session(connection, "Alice")
self.run_debug_session(connection, "Bob")

@skipIfWindows
def test_server_interrupt(self):
"""
Test launching a binary with lldb-dap in server mode and shutting down the server while the debug session is still active.
"""
self.build()
(process, connection) = self.start_server(connection="tcp://localhost:0")
self.dap_server = dap_server.DebugAdaptorServer(
connection=connection,
)
program = self.getBuildArtifact("a.out")
source = "main.c"
breakpoint_line = line_number(source, "// breakpoint")

self.launch(
program,
args=["Alice"],
disconnectAutomatically=False,
)
self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()

# Interrupt the server which should disconnect all clients.
process.send_signal(signal.SIGINT)

self.dap_server.wait_for_terminated()
self.assertIsNone(
self.dap_server.exit_status,
"Process exited before interrupting lldb-dap server",
)
10 changes: 10 additions & 0 deletions lldb/test/API/tools/lldb-dap/server/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <stdio.h>

int main(int argc, char const *argv[]) {
if (argc == 2) { // breakpoint 1
printf("Hello %s!\n", argv[1]);
} else {
printf("Hello World!\n");
}
return 0;
}
4 changes: 2 additions & 2 deletions lldb/test/Shell/DAP/TestOptions.test
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# RUN: lldb-dap --help | FileCheck %s
# CHECK: --connection
# CHECK: -g
# CHECK: --help
# CHECK: -h
# CHECK: --port
# CHECK: -p
# CHECK: --repl-mode
# CHECK: --wait-for-debugger

Loading