From 960351c9abf51f42d92604ac6297aa5b76ddfba5 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Fri, 17 Nov 2023 15:09:10 +0530 Subject: [PATCH 01/33] [lldb][test] Add the ability to extract the variable value out of the summary. --- .../Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index 9d79872b029a33..0cf9d4fde49488 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -195,6 +195,9 @@ def collect_console(self, duration): def get_local_as_int(self, name, threadId=None): value = self.dap_server.get_local_variable_value(name, threadId=threadId) + # 'value' may have the variable value and summary. + # Extract the variable value since summary can have nonnumeric characters. + value = value.split(" ")[0] if value.startswith("0x"): return int(value, 16) elif value.startswith("0"): From ab44a6991c5bc8ac5764c3f71cbe3acc747b3776 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Fri, 3 May 2024 02:47:05 -0700 Subject: [PATCH 02/33] [lldb-dap] Added "port" property to vscode "attach" command. Adding a "port" property to the VsCode "attach" command likely extends the functionality of the debugger configuratiuon 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", "port":1234, "program": "${workspaceFolder}/a.out", "args": [], "stopOnEntry": false, "cwd": "${workspaceFolder}", "env": [], } ] } --- lldb/include/lldb/lldb-defines.h | 1 + .../Python/lldbsuite/test/lldbtest.py | 9 ++ .../test/tools/lldb-dap/dap_server.py | 6 + .../test/tools/lldb-dap/lldbdap_testcase.py | 20 +++ .../attach/TestDAP_attachByPortNum.py | 120 ++++++++++++++++++ lldb/tools/lldb-dap/lldb-dap.cpp | 36 +++++- lldb/tools/lldb-dap/package.json | 11 ++ 7 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py diff --git a/lldb/include/lldb/lldb-defines.h b/lldb/include/lldb/lldb-defines.h index c7bd019c5c90eb..a1e6ee2ce468cb 100644 --- a/lldb/include/lldb/lldb-defines.h +++ b/lldb/include/lldb/lldb-defines.h @@ -96,6 +96,7 @@ #define LLDB_INVALID_QUEUE_ID 0 #define LLDB_INVALID_CPU_ID UINT32_MAX #define LLDB_INVALID_WATCHPOINT_RESOURCE_ID UINT32_MAX +#define LLDB_INVALID_PORT_NUMBER 0 /// CPU Type definitions #define LLDB_ARCH_DEFAULT "systemArch" diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index 5fd686c143e9f9..fb3cd22959df25 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -1572,6 +1572,15 @@ def findBuiltClang(self): return os.environ["CC"] + def getBuiltServerTool(self, server_tool): + # Tries to find simulation/lldb-server/gdbserver tool at the same folder as the lldb. + lldb_dir = os.path.dirname(lldbtest_config.lldbExec) + path = shutil.which(server_tool, path=lldb_dir) + if path is not None: + return path + + return "" + def yaml2obj(self, yaml_path, obj_path, max_size=None): """ Create an object file at the given path from a yaml file. diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 5838281bcb1a10..96d312565f953e 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -568,6 +568,8 @@ def request_attach( coreFile=None, postRunCommands=None, sourceMap=None, + port=None, + hostname=None ): args_dict = {} if pid is not None: @@ -597,6 +599,10 @@ def request_attach( args_dict["postRunCommands"] = postRunCommands if sourceMap: args_dict["sourceMap"] = sourceMap + if port is not None: + args_dict['port'] = port + if hostname is not None: + args_dict['hostname'] = hostname command_dict = {"command": "attach", "type": "request", "arguments": args_dict} return self.send_recv(command_dict) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index d56ea5dca14beb..ba58a3075c6a2f 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -335,6 +335,26 @@ def cleanup(): response["success"], "attach failed (%s)" % (response["message"]) ) + def attach_by_port(self, program=None, pid=None, disconnectAutomatically=True, waitFor=None, sourceInitFile=False, port=None, hostname=None): + '''Build the default Makefile target, create the VSCode debug adaptor, + and attach to the process. + ''' + # This overloaded function helps to request attach by port number + # Make sure we disconnect and terminate the VSCode debug adaptor even + # if we throw an exception during the test case. + def cleanup(): + if disconnectAutomatically: + self.dap_server.request_disconnect(terminateDebuggee=True) + self.dap_server.terminate() + + # Execute the cleanup function during test case tear down. + self.addTearDownHook(cleanup) + # Initialize and launch the program + self.dap_server.request_initialize(sourceInitFile) + response = self.dap_server.request_attach( + program=program, pid=pid, waitFor=waitFor, port=port, hostname=hostname) + return response + def launch( self, program=None, diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py new file mode 100644 index 00000000000000..0c0ea6ca436166 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -0,0 +1,120 @@ +""" +Test lldb-dap "port" configuration to "attach" request +""" + + +import dap_server +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbdap_testcase +import os +import shutil +import subprocess +import tempfile +import threading +import time +import sys +import re + +class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): + + def runTargetProgramOnPort(self, port=None, program=None): + # sim_tool="hexagon-sim" + # if isIUTarget(): + # sim_tool="iu-sim" + target_sim_path=self.getBuiltServerTool("lldb-server") + if target_sim_path: + target_sim_path +=' g localhost:' + port + ' ' + + self.process = subprocess.Popen([target_sim_path + program], shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + return self.process + + def set_and_hit_breakpoint(self, continueToExit=True): + source = 'main.c' + main_source_path = os.path.join(os.getcwd(), source) + breakpoint1_line = line_number(main_source_path, '// breakpoint 1') + lines = [breakpoint1_line] + # Set breakpoint in the thread function so we can step the threads + breakpoint_ids = self.set_source_breakpoints(main_source_path, lines) + self.assertEqual(len(breakpoint_ids), len(lines), + "expect correct number of breakpoints") + self.continue_to_breakpoints(breakpoint_ids) + if continueToExit: + self.continue_to_exit() + + @skipIfWindows + @skipIfNetBSD # Hangs on NetBSD as well + @skipIfRemote + def test_by_port(self): + ''' + Tests attaching to a process by port. + ''' + self.build_and_create_debug_adaptor() + program = self.getBuildArtifact("a.out") + + port = '2345' + self.process = self.runTargetProgramOnPort(port=port, program=program) + pid=self.process.pid + response = self.attach_by_port(program=program, port=int(port), sourceInitFile=True) + if not (response and response['success']): + self.assertTrue(response['success'], + 'attach failed (%s)' % (response['message'])) + self.set_and_hit_breakpoint(continueToExit=True) + self.process.kill() + os.system('killall hexagon-sim') + + @skipIfWindows + @skipIfNetBSD # Hangs on NetBSD as well + @skipIfRemote + def test_by_port_and_pid(self): + ''' + Tests attaching to a process by process ID and port number. + ''' + self.build_and_create_debug_adaptor() + program = self.getBuildArtifact("a.out") + + port = '2345' + self.process = self.runTargetProgramOnPort(port=port, program=program) + response = self.attach_by_port(program=program,pid=1234, port=int(port), sourceInitFile=True) + if not (response and response['success']): + self.assertFalse(response['success'], "The user can't specify both pid and port") + self.process.kill() + + @skipIfWindows + @skipIfNetBSD # Hangs on NetBSD as well + @skipIfRemote + def test_by_invalid_port(self): + ''' + Tests attaching to a process by invalid port number 0. + ''' + self.build_and_create_debug_adaptor() + program = self.getBuildArtifact("a.out") + + port = '0' + self.process = self.runTargetProgramOnPort(port=port, program=program) + response = self.attach_by_port(program=program, port=int(port), sourceInitFile=True) + if not (response and response['success']): + self.assertFalse(response['success'], "The user can't attach with invalid port (%s)" % port) + self.process.kill() + + @skipIfWindows + @skipIfNetBSD # Hangs on NetBSD as well + @skipIfRemote + def test_by_illegal_port(self): + ''' + Tests attaching to a process by illegal/greater port number 65536 + ''' + self.build_and_create_debug_adaptor() + program = self.getBuildArtifact("a.out") + + port = '65536' + self.process = self.runTargetProgramOnPort(port=port, program=program) + response = self.attach_by_port(program=program, port=int(port), sourceInitFile=True) + if not (response and response['success']): + self.assertFalse(response['success'], "The user can't attach with illegal port (%s)" % port) + self.process.kill() diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 8000d68dea7e36..2280e7217eaaae 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -688,6 +688,8 @@ void request_attach(const llvm::json::Object &request) { auto arguments = request.getObject("arguments"); const lldb::pid_t pid = GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID); + const auto port = GetUnsigned(arguments, "port", LLDB_INVALID_PORT_NUMBER); + llvm::StringRef hostname = GetString(arguments, "hostname"); if (pid != LLDB_INVALID_PROCESS_ID) attach_info.SetProcessID(pid); const auto wait_for = GetBoolean(arguments, "waitFor", false); @@ -749,7 +751,7 @@ void request_attach(const llvm::json::Object &request) { return; } - if (pid == LLDB_INVALID_PROCESS_ID && wait_for) { + if ((pid == LLDB_INVALID_PROCESS_ID || port == LLDB_INVALID_PORT_NUMBER) && wait_for) { char attach_msg[256]; auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg), "Waiting to attach to \"%s\"...", @@ -762,9 +764,35 @@ void request_attach(const llvm::json::Object &request) { // Disable async events so the attach will be successful when we return from // the launch call and the launch will happen synchronously g_dap.debugger.SetAsync(false); - if (core_file.empty()) - g_dap.target.Attach(attach_info, error); - else + if (core_file.empty()) { + if ((pid != LLDB_INVALID_PROCESS_ID) && + (port != LLDB_INVALID_PORT_NUMBER)) { + // If both pid and port numbers are specified. + error.SetErrorString("The user can't specify both pid and port"); + } else if ((pid != LLDB_INVALID_PROCESS_ID) && + (port == LLDB_INVALID_PORT_NUMBER)) { + // If pid is specified and port is not. + g_dap.target.Attach(attach_info, error); + } else if ((port != LLDB_INVALID_PORT_NUMBER) && (port < UINT16_MAX) && + (pid == LLDB_INVALID_PROCESS_ID)) { + // If port is specified and pid is not. + lldb::SBListener listener = g_dap.debugger.GetListener(); + + // If the user hasn't provided the hostname property, default localhost + // being used. + std::string connect_url("connect://localhost:"); + + // If the user has provided hostname other than localhost. + if (!hostname.empty() && !hostname.starts_with("localhost")) { + connect_url = llvm::formatv("connect://{0}:", hostname.data()); + } + connect_url += std::to_string(port); + g_dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", + error); + } else { + error.SetErrorString("Invalid pid/port number specified"); + } + } else g_dap.target.LoadCore(core_file.data(), error); // Reenable async events g_dap.debugger.SetAsync(true); diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 2e8ad074256bf5..624b5710f50fac 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -347,6 +347,17 @@ "type": "string", "description": "The time in seconds to wait for a program to stop when attaching using \"attachCommands\". Defaults to 30 seconds." }, + "port": { + "type": [ + "number", + "string" + ], + "description": "TCP/IP port to attach to. Specifying both pid and port is an error." + }, + "hostname": { + "type": "string", + "description": "The hostname to connect to a remote system. The default hostname being used localhost." + }, "enableAutoVariableSummaries": { "type": "boolean", "description": "Enable auto generated summaries for variables when no summaries exist for a given type. This feature can cause performance delays in large projects when viewing variables.", From 4e2e524f7d468cbd3b8c283635e766bb6de35376 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Wed, 8 May 2024 12:41:43 -0700 Subject: [PATCH 03/33] [lldb-dap] Added "port" property to vscode "attach" command. 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", "port":1234, "program": "${workspaceFolder}/a.out", "args": [], "stopOnEntry": false, "cwd": "${workspaceFolder}", "env": [], } ] } In this patch we have resolved code formatting issues and fixed "test_by_name" failure. --- .../Python/lldbsuite/test/lldbtest.py | 2 +- .../test/tools/lldb-dap/dap_server.py | 6 +- .../test/tools/lldb-dap/lldbdap_testcase.py | 21 +++- .../attach/TestDAP_attachByPortNum.py | 109 ++++++++++-------- lldb/tools/lldb-dap/lldb-dap.cpp | 13 +-- lldb/tools/lldb-dap/package.json | 12 +- 6 files changed, 94 insertions(+), 69 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index fb3cd22959df25..dde3efd8f5f871 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -1572,7 +1572,7 @@ def findBuiltClang(self): return os.environ["CC"] - def getBuiltServerTool(self, server_tool): + def getBuiltinServerTool(self, server_tool): # Tries to find simulation/lldb-server/gdbserver tool at the same folder as the lldb. lldb_dir = os.path.dirname(lldbtest_config.lldbExec) path = shutil.which(server_tool, path=lldb_dir) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 613180bc99af6c..bab16479d26c37 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -573,7 +573,7 @@ def request_attach( postRunCommands=None, sourceMap=None, port=None, - hostname=None + hostname=None, ): args_dict = {} if pid is not None: @@ -604,9 +604,9 @@ def request_attach( if sourceMap: args_dict["sourceMap"] = sourceMap if port is not None: - args_dict['port'] = port + args_dict["port"] = port if hostname is not None: - args_dict['hostname'] = hostname + args_dict["hostname"] = hostname command_dict = {"command": "attach", "type": "request", "arguments": args_dict} return self.send_recv(command_dict) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index ba58a3075c6a2f..922fb1d5784e67 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -335,10 +335,20 @@ def cleanup(): response["success"], "attach failed (%s)" % (response["message"]) ) - def attach_by_port(self, program=None, pid=None, disconnectAutomatically=True, waitFor=None, sourceInitFile=False, port=None, hostname=None): - '''Build the default Makefile target, create the VSCode debug adaptor, - and attach to the process. - ''' + def attach_by_port( + self, + program=None, + pid=None, + disconnectAutomatically=True, + waitFor=None, + sourceInitFile=False, + port=None, + hostname=None, + ): + """Build the default Makefile target, create the VSCode debug adaptor, + and attach to the process. + """ + # This overloaded function helps to request attach by port number # Make sure we disconnect and terminate the VSCode debug adaptor even # if we throw an exception during the test case. @@ -352,7 +362,8 @@ def cleanup(): # Initialize and launch the program self.dap_server.request_initialize(sourceInitFile) response = self.dap_server.request_attach( - program=program, pid=pid, waitFor=waitFor, port=port, hostname=hostname) + program=program, pid=pid, waitFor=waitFor, port=port, hostname=hostname + ) return response def launch( diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 0c0ea6ca436166..4b9965cbb55717 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -15,34 +15,35 @@ import threading import time import sys -import re class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): def runTargetProgramOnPort(self, port=None, program=None): - # sim_tool="hexagon-sim" - # if isIUTarget(): - # sim_tool="iu-sim" - target_sim_path=self.getBuiltServerTool("lldb-server") - if target_sim_path: - target_sim_path +=' g localhost:' + port + ' ' + server_tool = "lldb-server" + server_path = self.getBuiltinServerTool(server_tool) + if server_path: + server_path +=" g localhost:" + port + " " - self.process = subprocess.Popen([target_sim_path + program], shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + self.process = subprocess.Popen( + [server_path + program], + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) return self.process def set_and_hit_breakpoint(self, continueToExit=True): - source = 'main.c' + source = "main.c" main_source_path = os.path.join(os.getcwd(), source) - breakpoint1_line = line_number(main_source_path, '// breakpoint 1') + breakpoint1_line = line_number(main_source_path, "// breakpoint 1") lines = [breakpoint1_line] # Set breakpoint in the thread function so we can step the threads breakpoint_ids = self.set_source_breakpoints(main_source_path, lines) - self.assertEqual(len(breakpoint_ids), len(lines), - "expect correct number of breakpoints") + self.assertEqual( + len(breakpoint_ids), len(lines), "expect correct number of breakpoints" + ) self.continue_to_breakpoints(breakpoint_ids) if continueToExit: self.continue_to_exit() @@ -51,70 +52,86 @@ def set_and_hit_breakpoint(self, continueToExit=True): @skipIfNetBSD # Hangs on NetBSD as well @skipIfRemote def test_by_port(self): - ''' - Tests attaching to a process by port. - ''' + """ + Tests attaching to a process by port. + """ self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = '2345' + port = "2345" self.process = self.runTargetProgramOnPort(port=port, program=program) - pid=self.process.pid - response = self.attach_by_port(program=program, port=int(port), sourceInitFile=True) - if not (response and response['success']): - self.assertTrue(response['success'], - 'attach failed (%s)' % (response['message'])) + pid = self.process.pid + response = self.attach_by_port( + program=program, port=int(port), sourceInitFile=True + ) + if not (response and response["success"]): + self.assertTrue( + response["success"], "attach failed (%s)" % (response["message"]) + ) self.set_and_hit_breakpoint(continueToExit=True) self.process.kill() - os.system('killall hexagon-sim') @skipIfWindows @skipIfNetBSD # Hangs on NetBSD as well @skipIfRemote def test_by_port_and_pid(self): - ''' - Tests attaching to a process by process ID and port number. - ''' + """ + Tests attaching to a process by process ID and port number. + """ self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = '2345' + port = "2345" self.process = self.runTargetProgramOnPort(port=port, program=program) - response = self.attach_by_port(program=program,pid=1234, port=int(port), sourceInitFile=True) - if not (response and response['success']): - self.assertFalse(response['success'], "The user can't specify both pid and port") + response = self.attach_by_port( + program=program, pid=1234, port=int(port), sourceInitFile=True + ) + if not (response and response["success"]): + self.assertFalse( + response["success"], "The user can't specify both pid and port" + ) self.process.kill() @skipIfWindows @skipIfNetBSD # Hangs on NetBSD as well @skipIfRemote def test_by_invalid_port(self): - ''' - Tests attaching to a process by invalid port number 0. - ''' + """ + Tests attaching to a process by invalid port number 0. + """ self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = '0' + port = "0" self.process = self.runTargetProgramOnPort(port=port, program=program) - response = self.attach_by_port(program=program, port=int(port), sourceInitFile=True) - if not (response and response['success']): - self.assertFalse(response['success'], "The user can't attach with invalid port (%s)" % port) + response = self.attach_by_port( + program=program, port=int(port), sourceInitFile=True + ) + if not (response and response["success"]): + self.assertFalse( + response["success"], + "The user can't attach with invalid port (%s)" % port, + ) self.process.kill() @skipIfWindows @skipIfNetBSD # Hangs on NetBSD as well @skipIfRemote def test_by_illegal_port(self): - ''' - Tests attaching to a process by illegal/greater port number 65536 - ''' + """ + Tests attaching to a process by illegal/greater port number 65536 + """ self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = '65536' + port = "65536" self.process = self.runTargetProgramOnPort(port=port, program=program) - response = self.attach_by_port(program=program, port=int(port), sourceInitFile=True) - if not (response and response['success']): - self.assertFalse(response['success'], "The user can't attach with illegal port (%s)" % port) + response = self.attach_by_port( + program=program, port=int(port), sourceInitFile=True + ) + if not (response and response["success"]): + self.assertFalse( + response["success"], + "The user can't attach with illegal port (%s)" % port, + ) self.process.kill() diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 787a732c184102..2b123a01da657a 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -751,7 +751,8 @@ void request_attach(const llvm::json::Object &request) { return; } - if ((pid == LLDB_INVALID_PROCESS_ID || port == LLDB_INVALID_PORT_NUMBER) && wait_for) { + if ((pid == LLDB_INVALID_PROCESS_ID || port == LLDB_INVALID_PORT_NUMBER) && + wait_for) { char attach_msg[256]; auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg), "Waiting to attach to \"%s\"...", @@ -769,12 +770,7 @@ void request_attach(const llvm::json::Object &request) { (port != LLDB_INVALID_PORT_NUMBER)) { // If both pid and port numbers are specified. error.SetErrorString("The user can't specify both pid and port"); - } else if ((pid != LLDB_INVALID_PROCESS_ID) && - (port == LLDB_INVALID_PORT_NUMBER)) { - // If pid is specified and port is not. - g_dap.target.Attach(attach_info, error); - } else if ((port != LLDB_INVALID_PORT_NUMBER) && (port < UINT16_MAX) && - (pid == LLDB_INVALID_PROCESS_ID)) { + } else if ((port != LLDB_INVALID_PORT_NUMBER) && (port < UINT16_MAX)) { // If port is specified and pid is not. lldb::SBListener listener = g_dap.debugger.GetListener(); @@ -790,7 +786,8 @@ void request_attach(const llvm::json::Object &request) { g_dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", error); } else { - error.SetErrorString("Invalid pid/port number specified"); + // Attach by process name or id. + g_dap.target.Attach(attach_info, error); } } else g_dap.target.LoadCore(core_file.data(), error); diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 624b5710f50fac..224115b8ddffbb 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -349,15 +349,15 @@ }, "port": { "type": [ - "number", - "string" - ], + "number", + "string" + ], "description": "TCP/IP port to attach to. Specifying both pid and port is an error." }, "hostname": { - "type": "string", - "description": "The hostname to connect to a remote system. The default hostname being used localhost." - }, + "type": "string", + "description": "The hostname to connect to a remote system. The default hostname being used localhost." + }, "enableAutoVariableSummaries": { "type": "boolean", "description": "Enable auto generated summaries for variables when no summaries exist for a given type. This feature can cause performance delays in large projects when viewing variables.", From 175f1d32c446473252f4ed87632372a607c22579 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Thu, 9 May 2024 01:46:51 -0700 Subject: [PATCH 04/33] [lldb-dap] Added "port" property to vscode "attach" command. Resolved code format issues. --- .../API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 4b9965cbb55717..c1b0d57cffb94a 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -22,7 +22,7 @@ def runTargetProgramOnPort(self, port=None, program=None): server_tool = "lldb-server" server_path = self.getBuiltinServerTool(server_tool) if server_path: - server_path +=" g localhost:" + port + " " + server_path += " g localhost:" + port + " " self.process = subprocess.Popen( [server_path + program], @@ -31,7 +31,7 @@ def runTargetProgramOnPort(self, port=None, program=None): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) - + return self.process def set_and_hit_breakpoint(self, continueToExit=True): @@ -43,7 +43,7 @@ def set_and_hit_breakpoint(self, continueToExit=True): breakpoint_ids = self.set_source_breakpoints(main_source_path, lines) self.assertEqual( len(breakpoint_ids), len(lines), "expect correct number of breakpoints" - ) + ) self.continue_to_breakpoints(breakpoint_ids) if continueToExit: self.continue_to_exit() From e5c0aed39eb5903d1d98355bb2af4af134b98546 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Thu, 9 May 2024 01:58:17 -0700 Subject: [PATCH 05/33] Resolved code format issue. --- lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index c1b0d57cffb94a..8962e887cd8b73 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -22,7 +22,7 @@ def runTargetProgramOnPort(self, port=None, program=None): server_tool = "lldb-server" server_path = self.getBuiltinServerTool(server_tool) if server_path: - server_path += " g localhost:" + port + " " + server_path += " g localhost:" + port + " " self.process = subprocess.Popen( [server_path + program], From c502203c6b129ba569e36a1d922bb13c322f3706 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Thu, 9 May 2024 02:04:18 -0700 Subject: [PATCH 06/33] Resolved code format errors. --- lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 8962e887cd8b73..aee92d94d1ae0b 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -16,8 +16,8 @@ import time import sys -class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): +class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): def runTargetProgramOnPort(self, port=None, program=None): server_tool = "lldb-server" server_path = self.getBuiltinServerTool(server_tool) From c1bf2bdaa33353913b8529750d41da482ce18161 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Wed, 15 May 2024 15:16:44 -0700 Subject: [PATCH 07/33] [lldb-dap] Added "port" property to vscode "attach" command - Addressed review comments Addressed all review comments. --- .../test/tools/lldb-dap/lldbdap_testcase.py | 36 +++-------------- .../attach/TestDAP_attachByPortNum.py | 39 +++++++++++-------- lldb/tools/lldb-dap/lldb-dap.cpp | 2 +- 3 files changed, 29 insertions(+), 48 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index 922fb1d5784e67..d95a80482a60b1 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -297,6 +297,9 @@ def attach( sourceMap=None, sourceInitFile=False, expectFailure=False, + port=None, + hostname=None, + ): """Build the default Makefile target, create the DAP debug adaptor, and attach to the process. @@ -327,6 +330,8 @@ def cleanup(): coreFile=coreFile, postRunCommands=postRunCommands, sourceMap=sourceMap, + port=port, + hostname=hostname, ) if expectFailure: return response @@ -335,37 +340,6 @@ def cleanup(): response["success"], "attach failed (%s)" % (response["message"]) ) - def attach_by_port( - self, - program=None, - pid=None, - disconnectAutomatically=True, - waitFor=None, - sourceInitFile=False, - port=None, - hostname=None, - ): - """Build the default Makefile target, create the VSCode debug adaptor, - and attach to the process. - """ - - # This overloaded function helps to request attach by port number - # Make sure we disconnect and terminate the VSCode debug adaptor even - # if we throw an exception during the test case. - def cleanup(): - if disconnectAutomatically: - self.dap_server.request_disconnect(terminateDebuggee=True) - self.dap_server.terminate() - - # Execute the cleanup function during test case tear down. - self.addTearDownHook(cleanup) - # Initialize and launch the program - self.dap_server.request_initialize(sourceInitFile) - response = self.dap_server.request_attach( - program=program, pid=pid, waitFor=waitFor, port=port, hostname=hostname - ) - return response - def launch( self, program=None, diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index aee92d94d1ae0b..cfa20260fed397 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -7,6 +7,8 @@ from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil +from lldbsuite.test import lldbplatformutil +import lldbgdbserverutils import lldbdap_testcase import os import shutil @@ -19,13 +21,22 @@ class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): def runTargetProgramOnPort(self, port=None, program=None): - server_tool = "lldb-server" - server_path = self.getBuiltinServerTool(server_tool) - if server_path: - server_path += " g localhost:" + port + " " + server_tool = None + if (lldbplatformutil.getPlatform() == "linux"): + server_tool = lldbgdbserverutils.get_lldb_server_exe() + if server_tool is None: + self.dap_server.request_disconnect(terminateDebuggee=True) + self.assertIsNotNone(server_tool, "lldb-server not found.") + server_tool += " g localhost:" + port + " " + elif (lldbplatformutil.getPlatform() == "macosx"): + server_tool = lldbgdbserverutils.get_debugserver_exe() + if server_tool is None: + self.dap_server.request_disconnect(terminateDebuggee=True) + self.assertIsNotNone(server_tool, "debugserver not found.") + server_tool += " --listen localhost:" + port + " " self.process = subprocess.Popen( - [server_path + program], + [server_tool + program], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -61,13 +72,9 @@ def test_by_port(self): port = "2345" self.process = self.runTargetProgramOnPort(port=port, program=program) pid = self.process.pid - response = self.attach_by_port( + response = self.attach( program=program, port=int(port), sourceInitFile=True ) - if not (response and response["success"]): - self.assertTrue( - response["success"], "attach failed (%s)" % (response["message"]) - ) self.set_and_hit_breakpoint(continueToExit=True) self.process.kill() @@ -83,8 +90,8 @@ def test_by_port_and_pid(self): port = "2345" self.process = self.runTargetProgramOnPort(port=port, program=program) - response = self.attach_by_port( - program=program, pid=1234, port=int(port), sourceInitFile=True + response = self.attach( + program=program, pid=1234, port=int(port), sourceInitFile=True, expectFailure=True ) if not (response and response["success"]): self.assertFalse( @@ -104,8 +111,8 @@ def test_by_invalid_port(self): port = "0" self.process = self.runTargetProgramOnPort(port=port, program=program) - response = self.attach_by_port( - program=program, port=int(port), sourceInitFile=True + response = self.attach( + program=program, port=int(port), sourceInitFile=True, expectFailure=True ) if not (response and response["success"]): self.assertFalse( @@ -126,8 +133,8 @@ def test_by_illegal_port(self): port = "65536" self.process = self.runTargetProgramOnPort(port=port, program=program) - response = self.attach_by_port( - program=program, port=int(port), sourceInitFile=True + response = self.attach( + program=program, port=int(port), sourceInitFile=True, expectFailure=True ) if not (response and response["success"]): self.assertFalse( diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index a403e7ae26bfd2..c9cd6ffe1db433 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -758,7 +758,7 @@ void request_attach(const llvm::json::Object &request) { (port != LLDB_INVALID_PORT_NUMBER)) { // If both pid and port numbers are specified. error.SetErrorString("The user can't specify both pid and port"); - } else if ((port != LLDB_INVALID_PORT_NUMBER) && (port < UINT16_MAX)) { + } else if (port != LLDB_INVALID_PORT_NUMBER) { // If port is specified and pid is not. lldb::SBListener listener = g_dap.debugger.GetListener(); From 5590afc46aff9765f314139bbf4a446ca0f21b0c Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Wed, 15 May 2024 15:32:29 -0700 Subject: [PATCH 08/33] [lldb-dap] Added "port" property to vscode "attach" command - fix code format resolved code format issues. --- .../test/tools/lldb-dap/lldbdap_testcase.py | 1 - .../lldb-dap/attach/TestDAP_attachByPortNum.py | 18 ++++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index d95a80482a60b1..a2bef31ceb0ce1 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -299,7 +299,6 @@ def attach( expectFailure=False, port=None, hostname=None, - ): """Build the default Makefile target, create the DAP debug adaptor, and attach to the process. diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index cfa20260fed397..e5cdc256a7abc1 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -22,18 +22,18 @@ class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): def runTargetProgramOnPort(self, port=None, program=None): server_tool = None - if (lldbplatformutil.getPlatform() == "linux"): + if lldbplatformutil.getPlatform() == "linux": server_tool = lldbgdbserverutils.get_lldb_server_exe() if server_tool is None: self.dap_server.request_disconnect(terminateDebuggee=True) self.assertIsNotNone(server_tool, "lldb-server not found.") - server_tool += " g localhost:" + port + " " - elif (lldbplatformutil.getPlatform() == "macosx"): + server_tool += " g localhost:" + port + " " + elif lldbplatformutil.getPlatform() == "macosx": server_tool = lldbgdbserverutils.get_debugserver_exe() if server_tool is None: self.dap_server.request_disconnect(terminateDebuggee=True) self.assertIsNotNone(server_tool, "debugserver not found.") - server_tool += " --listen localhost:" + port + " " + server_tool += " --listen localhost:" + port + " " self.process = subprocess.Popen( [server_tool + program], @@ -72,9 +72,7 @@ def test_by_port(self): port = "2345" self.process = self.runTargetProgramOnPort(port=port, program=program) pid = self.process.pid - response = self.attach( - program=program, port=int(port), sourceInitFile=True - ) + response = self.attach(program=program, port=int(port), sourceInitFile=True) self.set_and_hit_breakpoint(continueToExit=True) self.process.kill() @@ -91,7 +89,11 @@ def test_by_port_and_pid(self): port = "2345" self.process = self.runTargetProgramOnPort(port=port, program=program) response = self.attach( - program=program, pid=1234, port=int(port), sourceInitFile=True, expectFailure=True + program=program, + pid=1234, + port=int(port), + sourceInitFile=True, + expectFailure=True, ) if not (response and response["success"]): self.assertFalse( From 023b51e61e3e5127dc2289ea47e2e4d3e7a6db26 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 28 May 2024 11:11:33 -0700 Subject: [PATCH 09/33] [lldb-dap] Added "port" property to vscode "attach" command - resolved all review comments. All review comments have been addressed, and the attach-by-port tests have been verified on Linux machine. Although the functionality is intended to support both Linux and macOS, we were unable to verify it on macOS due to lack of access. --- lldb/include/lldb/lldb-defines.h | 1 - .../Python/lldbsuite/test/lldbtest.py | 9 ---- .../test/tools/lldb-dap/lldbdap_testcase.py | 20 ++++++++ .../attach/TestDAP_attachByPortNum.py | 50 +++++++++---------- lldb/tools/lldb-dap/lldb-dap.cpp | 11 ++-- 5 files changed, 49 insertions(+), 42 deletions(-) diff --git a/lldb/include/lldb/lldb-defines.h b/lldb/include/lldb/lldb-defines.h index a1e6ee2ce468cb..c7bd019c5c90eb 100644 --- a/lldb/include/lldb/lldb-defines.h +++ b/lldb/include/lldb/lldb-defines.h @@ -96,7 +96,6 @@ #define LLDB_INVALID_QUEUE_ID 0 #define LLDB_INVALID_CPU_ID UINT32_MAX #define LLDB_INVALID_WATCHPOINT_RESOURCE_ID UINT32_MAX -#define LLDB_INVALID_PORT_NUMBER 0 /// CPU Type definitions #define LLDB_ARCH_DEFAULT "systemArch" diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index 5a93dc93c3eee9..1ad8ab6e6e462d 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -1637,15 +1637,6 @@ def findBuiltClang(self): return os.environ["CC"] - def getBuiltinServerTool(self, server_tool): - # Tries to find simulation/lldb-server/gdbserver tool at the same folder as the lldb. - lldb_dir = os.path.dirname(lldbtest_config.lldbExec) - path = shutil.which(server_tool, path=lldb_dir) - if path is not None: - return path - - return "" - def yaml2obj(self, yaml_path, obj_path, max_size=None): """ Create an object file at the given path from a yaml file. diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index a2bef31ceb0ce1..15d8a94a1f7149 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -3,6 +3,8 @@ import dap_server from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbplatformutil +import lldbgdbserverutils class DAPTestCaseBase(TestBase): @@ -487,3 +489,21 @@ def build_and_launch( launchCommands=launchCommands, expectFailure=expectFailure, ) + + def getBuiltinServerToolWithPortArg(self, port): + # Tries to find simulation/lldb-server/gdbserver tool and + # configure the server arguments to attach with given port numeber. + server_tool = None + if lldbplatformutil.getPlatform() == "linux": + server_tool = lldbgdbserverutils.get_lldb_server_exe() + if server_tool is None: + self.dap_server.request_disconnect(terminateDebuggee=True) + self.assertIsNotNone(server_tool, "lldb-server not found.") + server_tool += " g localhost:" + str(port) + " " + elif lldbplatformutil.getPlatform() == "macosx": + server_tool = lldbgdbserverutils.get_debugserver_exe() + if server_tool is None: + self.dap_server.request_disconnect(terminateDebuggee=True) + self.assertIsNotNone(server_tool, "debugserver not found.") + server_tool += " --listen localhost:" + str(port) + " " + return server_tool diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index e5cdc256a7abc1..6933bd04fdda47 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -17,24 +17,19 @@ import threading import time import sys +import socket class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): - def runTargetProgramOnPort(self, port=None, program=None): - server_tool = None - if lldbplatformutil.getPlatform() == "linux": - server_tool = lldbgdbserverutils.get_lldb_server_exe() - if server_tool is None: - self.dap_server.request_disconnect(terminateDebuggee=True) - self.assertIsNotNone(server_tool, "lldb-server not found.") - server_tool += " g localhost:" + port + " " - elif lldbplatformutil.getPlatform() == "macosx": - server_tool = lldbgdbserverutils.get_debugserver_exe() - if server_tool is None: - self.dap_server.request_disconnect(terminateDebuggee=True) - self.assertIsNotNone(server_tool, "debugserver not found.") - server_tool += " --listen localhost:" + port + " " + def get_free_port(self): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(('', 0)) + port = s.getsockname()[1] + s.close() + return port + def runTargetProgramOnPort(self, port=None, program=None): + server_tool = self.getBuiltinServerToolWithPortArg(port) self.process = subprocess.Popen( [server_tool + program], shell=True, @@ -60,7 +55,7 @@ def set_and_hit_breakpoint(self, continueToExit=True): self.continue_to_exit() @skipIfWindows - @skipIfNetBSD # Hangs on NetBSD as well + @skipIfNetBSD @skipIfRemote def test_by_port(self): """ @@ -69,15 +64,15 @@ def test_by_port(self): self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = "2345" + port = self.get_free_port() self.process = self.runTargetProgramOnPort(port=port, program=program) pid = self.process.pid - response = self.attach(program=program, port=int(port), sourceInitFile=True) + response = self.attach(program=program, port=port, sourceInitFile=True) self.set_and_hit_breakpoint(continueToExit=True) self.process.kill() @skipIfWindows - @skipIfNetBSD # Hangs on NetBSD as well + @skipIfNetBSD @skipIfRemote def test_by_port_and_pid(self): """ @@ -86,12 +81,13 @@ def test_by_port_and_pid(self): self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = "2345" + port = self.get_free_port() self.process = self.runTargetProgramOnPort(port=port, program=program) + pid = self.process.pid response = self.attach( program=program, - pid=1234, - port=int(port), + pid=pid, + port=port, sourceInitFile=True, expectFailure=True, ) @@ -102,7 +98,7 @@ def test_by_port_and_pid(self): self.process.kill() @skipIfWindows - @skipIfNetBSD # Hangs on NetBSD as well + @skipIfNetBSD @skipIfRemote def test_by_invalid_port(self): """ @@ -111,10 +107,10 @@ def test_by_invalid_port(self): self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = "0" + port = 0 self.process = self.runTargetProgramOnPort(port=port, program=program) response = self.attach( - program=program, port=int(port), sourceInitFile=True, expectFailure=True + program=program, port=port, sourceInitFile=True, expectFailure=True ) if not (response and response["success"]): self.assertFalse( @@ -124,7 +120,7 @@ def test_by_invalid_port(self): self.process.kill() @skipIfWindows - @skipIfNetBSD # Hangs on NetBSD as well + @skipIfNetBSD @skipIfRemote def test_by_illegal_port(self): """ @@ -133,10 +129,10 @@ def test_by_illegal_port(self): self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = "65536" + port = 65536 self.process = self.runTargetProgramOnPort(port=port, program=program) response = self.attach( - program=program, port=int(port), sourceInitFile=True, expectFailure=True + program=program, port=port, sourceInitFile=True, expectFailure=True ) if not (response and response["success"]): self.assertFalse( diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 1a45ae3d7adbdc..9fedad8a41482a 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -672,11 +672,12 @@ void request_attach(const llvm::json::Object &request) { lldb::SBError error; FillResponse(request, response); lldb::SBAttachInfo attach_info; + const int invalid_port = 0; auto arguments = request.getObject("arguments"); const lldb::pid_t pid = GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID); - const auto port = GetUnsigned(arguments, "port", LLDB_INVALID_PORT_NUMBER); - llvm::StringRef hostname = GetString(arguments, "hostname"); + const auto port = GetUnsigned(arguments, "port", invalid_port); + llvm::StringRef hostname = GetString(arguments, "hostname", "localhost"); if (pid != LLDB_INVALID_PROCESS_ID) attach_info.SetProcessID(pid); const auto wait_for = GetBoolean(arguments, "waitFor", false); @@ -738,7 +739,7 @@ void request_attach(const llvm::json::Object &request) { return; } - if ((pid == LLDB_INVALID_PROCESS_ID || port == LLDB_INVALID_PORT_NUMBER) && + if ((pid == LLDB_INVALID_PROCESS_ID || port == invalid_port) && wait_for) { char attach_msg[256]; auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg), @@ -754,10 +755,10 @@ void request_attach(const llvm::json::Object &request) { g_dap.debugger.SetAsync(false); if (core_file.empty()) { if ((pid != LLDB_INVALID_PROCESS_ID) && - (port != LLDB_INVALID_PORT_NUMBER)) { + (port != invalid_port)) { // If both pid and port numbers are specified. error.SetErrorString("The user can't specify both pid and port"); - } else if (port != LLDB_INVALID_PORT_NUMBER) { + } else if (port != invalid_port) { // If port is specified and pid is not. lldb::SBListener listener = g_dap.debugger.GetListener(); From d19eb88231cb1ca3d7519061811cbacf34402194 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 28 May 2024 11:27:04 -0700 Subject: [PATCH 10/33] [lldb-dap] Added "port" property to vscode "attach" command -resolved format check issues. --- .../API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py | 2 +- lldb/tools/lldb-dap/lldb-dap.cpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 6933bd04fdda47..f220b454883018 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -23,7 +23,7 @@ class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): def get_free_port(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(('', 0)) + s.bind(("", 0)) port = s.getsockname()[1] s.close() return port diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 9fedad8a41482a..e5165459dd9148 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -739,8 +739,7 @@ void request_attach(const llvm::json::Object &request) { return; } - if ((pid == LLDB_INVALID_PROCESS_ID || port == invalid_port) && - wait_for) { + if ((pid == LLDB_INVALID_PROCESS_ID || port == invalid_port) && wait_for) { char attach_msg[256]; auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg), "Waiting to attach to \"%s\"...", @@ -754,8 +753,7 @@ void request_attach(const llvm::json::Object &request) { // the launch call and the launch will happen synchronously g_dap.debugger.SetAsync(false); if (core_file.empty()) { - if ((pid != LLDB_INVALID_PROCESS_ID) && - (port != invalid_port)) { + if ((pid != LLDB_INVALID_PROCESS_ID) && (port != invalid_port)) { // If both pid and port numbers are specified. error.SetErrorString("The user can't specify both pid and port"); } else if (port != invalid_port) { From fe9a4b685eb089cadcc914ba1583bcbc085ada5a Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Thu, 30 May 2024 04:24:09 -0700 Subject: [PATCH 11/33] [lldb-dap] Added "port" property to vscode "attach" command. #91570 Resolved few review commennts. --- .../API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py | 4 ---- lldb/tools/lldb-dap/lldb-dap.cpp | 7 +------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index f220b454883018..2198fc77b8326e 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -56,7 +56,6 @@ def set_and_hit_breakpoint(self, continueToExit=True): @skipIfWindows @skipIfNetBSD - @skipIfRemote def test_by_port(self): """ Tests attaching to a process by port. @@ -73,7 +72,6 @@ def test_by_port(self): @skipIfWindows @skipIfNetBSD - @skipIfRemote def test_by_port_and_pid(self): """ Tests attaching to a process by process ID and port number. @@ -99,7 +97,6 @@ def test_by_port_and_pid(self): @skipIfWindows @skipIfNetBSD - @skipIfRemote def test_by_invalid_port(self): """ Tests attaching to a process by invalid port number 0. @@ -121,7 +118,6 @@ def test_by_invalid_port(self): @skipIfWindows @skipIfNetBSD - @skipIfRemote def test_by_illegal_port(self): """ Tests attaching to a process by illegal/greater port number 65536 diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index e5165459dd9148..985652f4801ebb 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -762,12 +762,7 @@ void request_attach(const llvm::json::Object &request) { // If the user hasn't provided the hostname property, default localhost // being used. - std::string connect_url("connect://localhost:"); - - // If the user has provided hostname other than localhost. - if (!hostname.empty() && !hostname.starts_with("localhost")) { - connect_url = llvm::formatv("connect://{0}:", hostname.data()); - } + std::string connect_url = llvm::formatv("connect://{0}:", hostname.data()); connect_url += std::to_string(port); g_dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", error); From c91b02de2896cc6ce5a9ab9493dedb44244c329c Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Thu, 30 May 2024 05:24:23 -0700 Subject: [PATCH 12/33] [lldb-dap] Added "port" property to vscode "attach" command. Resolved code format issues. --- lldb/tools/lldb-dap/lldb-dap.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 985652f4801ebb..ac862dc4ea1d28 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -762,7 +762,8 @@ void request_attach(const llvm::json::Object &request) { // If the user hasn't provided the hostname property, default localhost // being used. - std::string connect_url = llvm::formatv("connect://{0}:", hostname.data()); + std::string connect_url = + llvm::formatv("connect://{0}:", hostname.data()); connect_url += std::to_string(port); g_dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", error); From de29728f0ccf7c82035eba751062608b85833d5a Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 4 Jun 2024 10:36:02 -0700 Subject: [PATCH 13/33] [lldb-dap] Added "port" property to vscode "attach" command - addressed review comments. Used named pipe to read debug server listening port. Since we are only using "gdb-remote" process plugin, changed to "gdb-remote-port" and "gdb-remote-hostname" from "port" and "hostname" --- .../test/tools/lldb-dap/dap_server.py | 12 +- .../test/tools/lldb-dap/lldbdap_testcase.py | 15 +- .../attach/TestDAP_attachByPortNum.py | 129 +++++++++++++----- lldb/tools/lldb-dap/lldb-dap.cpp | 14 +- lldb/tools/lldb-dap/package.json | 4 +- 5 files changed, 115 insertions(+), 59 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index bab16479d26c37..fbfa851c1356c6 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -572,8 +572,8 @@ def request_attach( coreFile=None, postRunCommands=None, sourceMap=None, - port=None, - hostname=None, + gdbRemotePort=None, + gdbRemoteHostname=None, ): args_dict = {} if pid is not None: @@ -603,10 +603,10 @@ def request_attach( args_dict["postRunCommands"] = postRunCommands if sourceMap: args_dict["sourceMap"] = sourceMap - if port is not None: - args_dict["port"] = port - if hostname is not None: - args_dict["hostname"] = hostname + if gdbRemotePort is not None: + args_dict["gdb-remote-port"] = gdbRemotePort + if gdbRemoteHostname is not None: + args_dict["gdb-remote-hostname"] = gdbRemoteHostname command_dict = {"command": "attach", "type": "request", "arguments": args_dict} return self.send_recv(command_dict) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index 15d8a94a1f7149..086c6c5f906558 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -299,8 +299,8 @@ def attach( sourceMap=None, sourceInitFile=False, expectFailure=False, - port=None, - hostname=None, + gdbRemotePort=None, + gdbRemoteHostname=None, ): """Build the default Makefile target, create the DAP debug adaptor, and attach to the process. @@ -331,8 +331,8 @@ def cleanup(): coreFile=coreFile, postRunCommands=postRunCommands, sourceMap=sourceMap, - port=port, - hostname=hostname, + gdbRemotePort=gdbRemotePort, + gdbRemoteHostname=gdbRemoteHostname, ) if expectFailure: return response @@ -490,20 +490,17 @@ def build_and_launch( expectFailure=expectFailure, ) - def getBuiltinServerToolWithPortArg(self, port): - # Tries to find simulation/lldb-server/gdbserver tool and - # configure the server arguments to attach with given port numeber. + def getBuiltinDebugServerTool(self): + # Tries to find simulation/lldb-server/gdbserver tool path. server_tool = None if lldbplatformutil.getPlatform() == "linux": server_tool = lldbgdbserverutils.get_lldb_server_exe() if server_tool is None: self.dap_server.request_disconnect(terminateDebuggee=True) self.assertIsNotNone(server_tool, "lldb-server not found.") - server_tool += " g localhost:" + str(port) + " " elif lldbplatformutil.getPlatform() == "macosx": server_tool = lldbgdbserverutils.get_debugserver_exe() if server_tool is None: self.dap_server.request_disconnect(terminateDebuggee=True) self.assertIsNotNone(server_tool, "debugserver not found.") - server_tool += " --listen localhost:" + str(port) + " " return server_tool diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 2198fc77b8326e..5c247462350c6e 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -8,38 +8,39 @@ from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil from lldbsuite.test import lldbplatformutil -import lldbgdbserverutils import lldbdap_testcase import os import shutil import subprocess import tempfile import threading -import time import sys import socket +import select -class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): - def get_free_port(self): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(("", 0)) - port = s.getsockname()[1] - s.close() - return port - - def runTargetProgramOnPort(self, port=None, program=None): - server_tool = self.getBuiltinServerToolWithPortArg(port) - self.process = subprocess.Popen( - [server_tool + program], - shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) +# A class representing a pipe for communicating with debug server. +# This class includes menthods to open the pipe and read the port number from it. +class Pipe(object): + def __init__(self, prefix): + self.name = os.path.join(prefix, "stub_port_number") + os.mkfifo(self.name) + self._fd = os.open(self.name, os.O_RDONLY | os.O_NONBLOCK) + + def finish_connection(self, timeout): + pass + + def read(self, size, timeout): + (readers, _, _) = select.select([self._fd], [], [], timeout) + if self._fd not in readers: + raise TimeoutError + return os.read(self._fd, size) - return self.process + def close(self): + os.close(self._fd) +class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): + default_timeout = 20 def set_and_hit_breakpoint(self, continueToExit=True): source = "main.c" main_source_path = os.path.join(os.getcwd(), source) @@ -54,21 +55,54 @@ def set_and_hit_breakpoint(self, continueToExit=True): if continueToExit: self.continue_to_exit() + def get_debug_server_command_line_args(self): + args = [] + if lldbplatformutil.getPlatform() == "linux": + args = ["gdbserver"] + elif lldbplatformutil.getPlatform() == "macosx": + args = ["--listen"] + if lldb.remote_platform: + args += ["*:0"] + else: + args += ["localhost:0"] + return args + + def get_debug_server_pipe(self): + pipe = Pipe(self.getBuildDir()) + self.addTearDownHook(lambda: pipe.close()) + pipe.finish_connection(self.default_timeout) + return pipe + @skipIfWindows @skipIfNetBSD def test_by_port(self): """ Tests attaching to a process by port. """ + self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") + + debug_server_tool = self.getBuiltinDebugServerTool() - port = self.get_free_port() - self.process = self.runTargetProgramOnPort(port=port, program=program) - pid = self.process.pid - response = self.attach(program=program, port=port, sourceInitFile=True) + pipe = self.get_debug_server_pipe() + args = self.get_debug_server_command_line_args() + args += [program] + args += ["--named-pipe", pipe.name] + + self.process = self.spawnSubprocess( + debug_server_tool, args, install_remote=False + ) + + # Read the port number from the debug server pipe. + port = pipe.read(10, 30) + # Trim null byte, convert to int + port = int(port[:-1]) + self.assertIsNotNone(port, " Failed to read the port number from debug server pipe") + + self.attach(program=program, gdbRemotePort=port, sourceInitFile=True) self.set_and_hit_breakpoint(continueToExit=True) - self.process.kill() + self.process.terminate() @skipIfWindows @skipIfNetBSD @@ -79,13 +113,28 @@ def test_by_port_and_pid(self): self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - port = self.get_free_port() - self.process = self.runTargetProgramOnPort(port=port, program=program) + debug_server_tool = self.getBuiltinDebugServerTool() + pipe = self.get_debug_server_pipe() + args = self.get_debug_server_command_line_args() + args += [program] + args += ["--named-pipe", pipe.name] + + self.process = self.spawnSubprocess( + debug_server_tool, args, install_remote=False + ) + + # Read the port number from the debug server pipe. + port = pipe.read(10, 30) + # Trim null byte, convert to int + port = int(port[:-1]) + self.assertIsNotNone(port, " Failed to read the port number from debug server pipe") + pid = self.process.pid + response = self.attach( program=program, pid=pid, - port=port, + gdbRemotePort=port, sourceInitFile=True, expectFailure=True, ) @@ -93,7 +142,7 @@ def test_by_port_and_pid(self): self.assertFalse( response["success"], "The user can't specify both pid and port" ) - self.process.kill() + self.process.terminate() @skipIfWindows @skipIfNetBSD @@ -105,16 +154,21 @@ def test_by_invalid_port(self): program = self.getBuildArtifact("a.out") port = 0 - self.process = self.runTargetProgramOnPort(port=port, program=program) + args = [program] + debug_server_tool = self.getBuiltinDebugServerTool() + self.process = self.spawnSubprocess( + debug_server_tool, args, install_remote=False + ) + response = self.attach( - program=program, port=port, sourceInitFile=True, expectFailure=True + program=program, gdbRemotePort=port, sourceInitFile=True, expectFailure=True ) if not (response and response["success"]): self.assertFalse( response["success"], "The user can't attach with invalid port (%s)" % port, ) - self.process.kill() + self.process.terminate() @skipIfWindows @skipIfNetBSD @@ -126,13 +180,18 @@ def test_by_illegal_port(self): program = self.getBuildArtifact("a.out") port = 65536 - self.process = self.runTargetProgramOnPort(port=port, program=program) + args = [program] + debug_server_tool = self.getBuiltinDebugServerTool() + self.process = self.spawnSubprocess( + debug_server_tool, args, install_remote=False + ) + response = self.attach( - program=program, port=port, sourceInitFile=True, expectFailure=True + program=program, gdbRemotePort=port, sourceInitFile=True, expectFailure=True ) if not (response and response["success"]): self.assertFalse( response["success"], "The user can't attach with illegal port (%s)" % port, ) - self.process.kill() + self.process.terminate() diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index ac862dc4ea1d28..b852a470f6d4bd 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -676,8 +676,8 @@ void request_attach(const llvm::json::Object &request) { auto arguments = request.getObject("arguments"); const lldb::pid_t pid = GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID); - const auto port = GetUnsigned(arguments, "port", invalid_port); - llvm::StringRef hostname = GetString(arguments, "hostname", "localhost"); + const auto gdb_remote_port = GetUnsigned(arguments, "gdb-remote-port", invalid_port); + llvm::StringRef gdb_remote_hostname = GetString(arguments, "gdb-remote-hostname", "localhost"); if (pid != LLDB_INVALID_PROCESS_ID) attach_info.SetProcessID(pid); const auto wait_for = GetBoolean(arguments, "waitFor", false); @@ -739,7 +739,7 @@ void request_attach(const llvm::json::Object &request) { return; } - if ((pid == LLDB_INVALID_PROCESS_ID || port == invalid_port) && wait_for) { + if ((pid == LLDB_INVALID_PROCESS_ID || gdb_remote_port == invalid_port) && wait_for) { char attach_msg[256]; auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg), "Waiting to attach to \"%s\"...", @@ -753,18 +753,18 @@ void request_attach(const llvm::json::Object &request) { // the launch call and the launch will happen synchronously g_dap.debugger.SetAsync(false); if (core_file.empty()) { - if ((pid != LLDB_INVALID_PROCESS_ID) && (port != invalid_port)) { + if ((pid != LLDB_INVALID_PROCESS_ID) && (gdb_remote_port != invalid_port)) { // If both pid and port numbers are specified. error.SetErrorString("The user can't specify both pid and port"); - } else if (port != invalid_port) { + } else if (gdb_remote_port != invalid_port) { // If port is specified and pid is not. lldb::SBListener listener = g_dap.debugger.GetListener(); // If the user hasn't provided the hostname property, default localhost // being used. std::string connect_url = - llvm::formatv("connect://{0}:", hostname.data()); - connect_url += std::to_string(port); + llvm::formatv("connect://{0}:", gdb_remote_hostname.data()); + connect_url += std::to_string(gdb_remote_port); g_dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", error); } else { diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index c9f05b7c572b4d..62cf69b3597286 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -348,14 +348,14 @@ "type": "string", "description": "The time in seconds to wait for a program to stop when attaching using \"attachCommands\". Defaults to 30 seconds." }, - "port": { + "gdb-remote-port": { "type": [ "number", "string" ], "description": "TCP/IP port to attach to. Specifying both pid and port is an error." }, - "hostname": { + "gdb-remote-hostname": { "type": "string", "description": "The hostname to connect to a remote system. The default hostname being used localhost." }, From ac84b5334435e976489037a2779785db84f48bf9 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 4 Jun 2024 11:03:38 -0700 Subject: [PATCH 14/33] [lldb-dap] Added "port" property to vscode "attach" command - resolved code format issues. --- .../attach/TestDAP_attachByPortNum.py | 33 +++++++++++-------- lldb/tools/lldb-dap/lldb-dap.cpp | 12 ++++--- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 5c247462350c6e..e49c2eabeb50d9 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -39,8 +39,10 @@ def read(self, size, timeout): def close(self): os.close(self._fd) + class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): default_timeout = 20 + def set_and_hit_breakpoint(self, continueToExit=True): source = "main.c" main_source_path = os.path.join(os.getcwd(), source) @@ -58,7 +60,7 @@ def set_and_hit_breakpoint(self, continueToExit=True): def get_debug_server_command_line_args(self): args = [] if lldbplatformutil.getPlatform() == "linux": - args = ["gdbserver"] + args = ["gdbserver"] elif lldbplatformutil.getPlatform() == "macosx": args = ["--listen"] if lldb.remote_platform: @@ -72,33 +74,34 @@ def get_debug_server_pipe(self): self.addTearDownHook(lambda: pipe.close()) pipe.finish_connection(self.default_timeout) return pipe - + @skipIfWindows @skipIfNetBSD def test_by_port(self): """ Tests attaching to a process by port. """ - self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - + debug_server_tool = self.getBuiltinDebugServerTool() pipe = self.get_debug_server_pipe() args = self.get_debug_server_command_line_args() args += [program] args += ["--named-pipe", pipe.name] - + self.process = self.spawnSubprocess( debug_server_tool, args, install_remote=False ) - + # Read the port number from the debug server pipe. - port = pipe.read(10, 30) + port = pipe.read(10, self.default_timeout) # Trim null byte, convert to int port = int(port[:-1]) - self.assertIsNotNone(port, " Failed to read the port number from debug server pipe") + self.assertIsNotNone( + port, " Failed to read the port number from debug server pipe" + ) self.attach(program=program, gdbRemotePort=port, sourceInitFile=True) self.set_and_hit_breakpoint(continueToExit=True) @@ -118,19 +121,21 @@ def test_by_port_and_pid(self): args = self.get_debug_server_command_line_args() args += [program] args += ["--named-pipe", pipe.name] - + self.process = self.spawnSubprocess( debug_server_tool, args, install_remote=False ) - + # Read the port number from the debug server pipe. - port = pipe.read(10, 30) + port = pipe.read(10, self.default_timeout) # Trim null byte, convert to int port = int(port[:-1]) - self.assertIsNotNone(port, " Failed to read the port number from debug server pipe") - + self.assertIsNotNone( + port, " Failed to read the port number from debug server pipe" + ) + pid = self.process.pid - + response = self.attach( program=program, pid=pid, diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index b852a470f6d4bd..ab2b9ced1f33b4 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -676,8 +676,10 @@ void request_attach(const llvm::json::Object &request) { auto arguments = request.getObject("arguments"); const lldb::pid_t pid = GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID); - const auto gdb_remote_port = GetUnsigned(arguments, "gdb-remote-port", invalid_port); - llvm::StringRef gdb_remote_hostname = GetString(arguments, "gdb-remote-hostname", "localhost"); + const auto gdb_remote_port = + GetUnsigned(arguments, "gdb-remote-port", invalid_port); + llvm::StringRef gdb_remote_hostname = + GetString(arguments, "gdb-remote-hostname", "localhost"); if (pid != LLDB_INVALID_PROCESS_ID) attach_info.SetProcessID(pid); const auto wait_for = GetBoolean(arguments, "waitFor", false); @@ -739,7 +741,8 @@ void request_attach(const llvm::json::Object &request) { return; } - if ((pid == LLDB_INVALID_PROCESS_ID || gdb_remote_port == invalid_port) && wait_for) { + if ((pid == LLDB_INVALID_PROCESS_ID || gdb_remote_port == invalid_port) && + wait_for) { char attach_msg[256]; auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg), "Waiting to attach to \"%s\"...", @@ -753,7 +756,8 @@ void request_attach(const llvm::json::Object &request) { // the launch call and the launch will happen synchronously g_dap.debugger.SetAsync(false); if (core_file.empty()) { - if ((pid != LLDB_INVALID_PROCESS_ID) && (gdb_remote_port != invalid_port)) { + if ((pid != LLDB_INVALID_PROCESS_ID) && + (gdb_remote_port != invalid_port)) { // If both pid and port numbers are specified. error.SetErrorString("The user can't specify both pid and port"); } else if (gdb_remote_port != invalid_port) { From 08f40aaa5975fd2cab6e7fd8c69b66c0ab97dd59 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 10 Jun 2024 05:27:50 -0700 Subject: [PATCH 15/33] [lldb-dap] Added "port" property to vscode "attach" command - fixed review comments. --- .../API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py | 7 ------- lldb/tools/lldb-dap/lldb-dap.cpp | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index e49c2eabeb50d9..12c41be3bc6e07 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -159,12 +159,6 @@ def test_by_invalid_port(self): program = self.getBuildArtifact("a.out") port = 0 - args = [program] - debug_server_tool = self.getBuiltinDebugServerTool() - self.process = self.spawnSubprocess( - debug_server_tool, args, install_remote=False - ) - response = self.attach( program=program, gdbRemotePort=port, sourceInitFile=True, expectFailure=True ) @@ -173,7 +167,6 @@ def test_by_invalid_port(self): response["success"], "The user can't attach with invalid port (%s)" % port, ) - self.process.terminate() @skipIfWindows @skipIfNetBSD diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index ab2b9ced1f33b4..408356350dc21f 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -678,7 +678,7 @@ void request_attach(const llvm::json::Object &request) { GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID); const auto gdb_remote_port = GetUnsigned(arguments, "gdb-remote-port", invalid_port); - llvm::StringRef gdb_remote_hostname = + const auto gdb_remote_hostname = GetString(arguments, "gdb-remote-hostname", "localhost"); if (pid != LLDB_INVALID_PROCESS_ID) attach_info.SetProcessID(pid); @@ -767,7 +767,7 @@ void request_attach(const llvm::json::Object &request) { // If the user hasn't provided the hostname property, default localhost // being used. std::string connect_url = - llvm::formatv("connect://{0}:", gdb_remote_hostname.data()); + llvm::formatv("connect://{0}:", gdb_remote_hostname); connect_url += std::to_string(gdb_remote_port); g_dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", error); From 2a0bc63c222ce14602c5e9ebfff1283b84b2aa46 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 10 Jun 2024 05:54:29 -0700 Subject: [PATCH 16/33] [lldb-dap] Added "port" property to vscode "attach" command - resolved review comments. --- .../attach/TestDAP_attachByPortNum.py | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 12c41be3bc6e07..7e8068af100b4d 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -116,25 +116,11 @@ def test_by_port_and_pid(self): self.build_and_create_debug_adaptor() program = self.getBuildArtifact("a.out") - debug_server_tool = self.getBuiltinDebugServerTool() - pipe = self.get_debug_server_pipe() - args = self.get_debug_server_command_line_args() - args += [program] - args += ["--named-pipe", pipe.name] - - self.process = self.spawnSubprocess( - debug_server_tool, args, install_remote=False - ) - - # Read the port number from the debug server pipe. - port = pipe.read(10, self.default_timeout) - # Trim null byte, convert to int - port = int(port[:-1]) - self.assertIsNotNone( - port, " Failed to read the port number from debug server pipe" - ) - - pid = self.process.pid + # It is not necessary to launch "lldb-server" to obtain the actual port and pid for attaching. + # However, when providing the port number and pid directly, "lldb-dap" throws an error message, which is expected. + # So, used random pid and port numbers here. + pid = 1354 + port = 1234 response = self.attach( program=program, @@ -147,7 +133,6 @@ def test_by_port_and_pid(self): self.assertFalse( response["success"], "The user can't specify both pid and port" ) - self.process.terminate() @skipIfWindows @skipIfNetBSD From 656c34a5e28bde2e75629553bda17690eef30896 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 10 Jun 2024 09:49:14 -0700 Subject: [PATCH 17/33] [lldb-dap] Added "port" property to vscode "attach" command. I have shifted "Pipe" class to common location "lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py" --- .../tools/lldb-server/lldbgdbserverutils.py | 146 ++++++++++++++++++ .../attach/TestDAP_attachByPortNum.py | 26 +--- .../commandline/TestGdbRemoteConnection.py | 144 +---------------- 3 files changed, 150 insertions(+), 166 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py index d1a4119bac7815..5253839c7405dd 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py @@ -13,6 +13,7 @@ from lldbsuite.test import configuration from textwrap import dedent import shutil +import select def _get_support_exe(basename): @@ -969,3 +970,148 @@ def __str__(self): self._output_queue, self._accumulated_output, ) + + +# A class representing a pipe for communicating with debug server. +# This class includes menthods to open the pipe and read the port number from it. +if lldbplatformutil.getHostPlatform() == "windows": + import ctypes + import ctypes.wintypes + from ctypes.wintypes import BOOL, DWORD, HANDLE, LPCWSTR, LPDWORD, LPVOID + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + + PIPE_ACCESS_INBOUND = 1 + FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 + FILE_FLAG_OVERLAPPED = 0x40000000 + PIPE_TYPE_BYTE = 0 + PIPE_REJECT_REMOTE_CLIENTS = 8 + INVALID_HANDLE_VALUE = -1 + ERROR_ACCESS_DENIED = 5 + ERROR_IO_PENDING = 997 + + class OVERLAPPED(ctypes.Structure): + _fields_ = [ + ("Internal", LPVOID), + ("InternalHigh", LPVOID), + ("Offset", DWORD), + ("OffsetHigh", DWORD), + ("hEvent", HANDLE), + ] + + def __init__(self): + super(OVERLAPPED, self).__init__( + Internal=0, InternalHigh=0, Offset=0, OffsetHigh=0, hEvent=None + ) + + LPOVERLAPPED = ctypes.POINTER(OVERLAPPED) + + CreateNamedPipe = kernel32.CreateNamedPipeW + CreateNamedPipe.restype = HANDLE + CreateNamedPipe.argtypes = ( + LPCWSTR, + DWORD, + DWORD, + DWORD, + DWORD, + DWORD, + DWORD, + LPVOID, + ) + + ConnectNamedPipe = kernel32.ConnectNamedPipe + ConnectNamedPipe.restype = BOOL + ConnectNamedPipe.argtypes = (HANDLE, LPOVERLAPPED) + + CreateEvent = kernel32.CreateEventW + CreateEvent.restype = HANDLE + CreateEvent.argtypes = (LPVOID, BOOL, BOOL, LPCWSTR) + + GetOverlappedResultEx = kernel32.GetOverlappedResultEx + GetOverlappedResultEx.restype = BOOL + GetOverlappedResultEx.argtypes = (HANDLE, LPOVERLAPPED, LPDWORD, DWORD, BOOL) + + ReadFile = kernel32.ReadFile + ReadFile.restype = BOOL + ReadFile.argtypes = (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED) + + CloseHandle = kernel32.CloseHandle + CloseHandle.restype = BOOL + CloseHandle.argtypes = (HANDLE,) + + class Pipe(object): + def __init__(self, prefix): + while True: + self.name = "lldb-" + str(random.randrange(1e10)) + full_name = "\\\\.\\pipe\\" + self.name + self._handle = CreateNamedPipe( + full_name, + PIPE_ACCESS_INBOUND + | FILE_FLAG_FIRST_PIPE_INSTANCE + | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS, + 1, + 4096, + 4096, + 0, + None, + ) + if self._handle != INVALID_HANDLE_VALUE: + break + if ctypes.get_last_error() != ERROR_ACCESS_DENIED: + raise ctypes.WinError(ctypes.get_last_error()) + + self._overlapped = OVERLAPPED() + self._overlapped.hEvent = CreateEvent(None, True, False, None) + result = ConnectNamedPipe(self._handle, self._overlapped) + assert result == 0 + if ctypes.get_last_error() != ERROR_IO_PENDING: + raise ctypes.WinError(ctypes.get_last_error()) + + def finish_connection(self, timeout): + if not GetOverlappedResultEx( + self._handle, + self._overlapped, + ctypes.byref(DWORD(0)), + timeout * 1000, + True, + ): + raise ctypes.WinError(ctypes.get_last_error()) + + def read(self, size, timeout): + buf = ctypes.create_string_buffer(size) + if not ReadFile( + self._handle, ctypes.byref(buf), size, None, self._overlapped + ): + if ctypes.get_last_error() != ERROR_IO_PENDING: + raise ctypes.WinError(ctypes.get_last_error()) + read = DWORD(0) + if not GetOverlappedResultEx( + self._handle, self._overlapped, ctypes.byref(read), timeout * 1000, True + ): + raise ctypes.WinError(ctypes.get_last_error()) + return buf.raw[0 : read.value] + + def close(self): + CloseHandle(self._overlapped.hEvent) + CloseHandle(self._handle) + +else: + + class Pipe(object): + def __init__(self, prefix): + self.name = os.path.join(prefix, "stub_port_number") + os.mkfifo(self.name) + self._fd = os.open(self.name, os.O_RDONLY | os.O_NONBLOCK) + + def finish_connection(self, timeout): + pass + + def read(self, size, timeout): + (readers, _, _) = select.select([self._fd], [], [], timeout) + if self._fd not in readers: + raise TimeoutError + return os.read(self._fd, size) + + def close(self): + os.close(self._fd) diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py index 7e8068af100b4d..fbabc857bd0e0b 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py @@ -8,6 +8,7 @@ from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil from lldbsuite.test import lldbplatformutil +from lldbgdbserverutils import Pipe import lldbdap_testcase import os import shutil @@ -16,28 +17,6 @@ import threading import sys import socket -import select - - -# A class representing a pipe for communicating with debug server. -# This class includes menthods to open the pipe and read the port number from it. -class Pipe(object): - def __init__(self, prefix): - self.name = os.path.join(prefix, "stub_port_number") - os.mkfifo(self.name) - self._fd = os.open(self.name, os.O_RDONLY | os.O_NONBLOCK) - - def finish_connection(self, timeout): - pass - - def read(self, size, timeout): - (readers, _, _) = select.select([self._fd], [], [], timeout) - if self._fd not in readers: - raise TimeoutError - return os.read(self._fd, size) - - def close(self): - os.close(self._fd) class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase): @@ -119,7 +98,8 @@ def test_by_port_and_pid(self): # It is not necessary to launch "lldb-server" to obtain the actual port and pid for attaching. # However, when providing the port number and pid directly, "lldb-dap" throws an error message, which is expected. # So, used random pid and port numbers here. - pid = 1354 + + pid = 1354 port = 1234 response = self.attach( diff --git a/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py b/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py index 853b7ad5ef2901..ed600d396fad44 100644 --- a/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py +++ b/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py @@ -1,153 +1,11 @@ import gdbremote_testcase import random -import select import socket from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbgdbserverutils import Server import lldbsuite.test.lldbplatformutil - -if lldbplatformutil.getHostPlatform() == "windows": - import ctypes - import ctypes.wintypes - from ctypes.wintypes import BOOL, DWORD, HANDLE, LPCWSTR, LPDWORD, LPVOID - - kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) - - PIPE_ACCESS_INBOUND = 1 - FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 - FILE_FLAG_OVERLAPPED = 0x40000000 - PIPE_TYPE_BYTE = 0 - PIPE_REJECT_REMOTE_CLIENTS = 8 - INVALID_HANDLE_VALUE = -1 - ERROR_ACCESS_DENIED = 5 - ERROR_IO_PENDING = 997 - - class OVERLAPPED(ctypes.Structure): - _fields_ = [ - ("Internal", LPVOID), - ("InternalHigh", LPVOID), - ("Offset", DWORD), - ("OffsetHigh", DWORD), - ("hEvent", HANDLE), - ] - - def __init__(self): - super(OVERLAPPED, self).__init__( - Internal=0, InternalHigh=0, Offset=0, OffsetHigh=0, hEvent=None - ) - - LPOVERLAPPED = ctypes.POINTER(OVERLAPPED) - - CreateNamedPipe = kernel32.CreateNamedPipeW - CreateNamedPipe.restype = HANDLE - CreateNamedPipe.argtypes = ( - LPCWSTR, - DWORD, - DWORD, - DWORD, - DWORD, - DWORD, - DWORD, - LPVOID, - ) - - ConnectNamedPipe = kernel32.ConnectNamedPipe - ConnectNamedPipe.restype = BOOL - ConnectNamedPipe.argtypes = (HANDLE, LPOVERLAPPED) - - CreateEvent = kernel32.CreateEventW - CreateEvent.restype = HANDLE - CreateEvent.argtypes = (LPVOID, BOOL, BOOL, LPCWSTR) - - GetOverlappedResultEx = kernel32.GetOverlappedResultEx - GetOverlappedResultEx.restype = BOOL - GetOverlappedResultEx.argtypes = (HANDLE, LPOVERLAPPED, LPDWORD, DWORD, BOOL) - - ReadFile = kernel32.ReadFile - ReadFile.restype = BOOL - ReadFile.argtypes = (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED) - - CloseHandle = kernel32.CloseHandle - CloseHandle.restype = BOOL - CloseHandle.argtypes = (HANDLE,) - - class Pipe(object): - def __init__(self, prefix): - while True: - self.name = "lldb-" + str(random.randrange(1e10)) - full_name = "\\\\.\\pipe\\" + self.name - self._handle = CreateNamedPipe( - full_name, - PIPE_ACCESS_INBOUND - | FILE_FLAG_FIRST_PIPE_INSTANCE - | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS, - 1, - 4096, - 4096, - 0, - None, - ) - if self._handle != INVALID_HANDLE_VALUE: - break - if ctypes.get_last_error() != ERROR_ACCESS_DENIED: - raise ctypes.WinError(ctypes.get_last_error()) - - self._overlapped = OVERLAPPED() - self._overlapped.hEvent = CreateEvent(None, True, False, None) - result = ConnectNamedPipe(self._handle, self._overlapped) - assert result == 0 - if ctypes.get_last_error() != ERROR_IO_PENDING: - raise ctypes.WinError(ctypes.get_last_error()) - - def finish_connection(self, timeout): - if not GetOverlappedResultEx( - self._handle, - self._overlapped, - ctypes.byref(DWORD(0)), - timeout * 1000, - True, - ): - raise ctypes.WinError(ctypes.get_last_error()) - - def read(self, size, timeout): - buf = ctypes.create_string_buffer(size) - if not ReadFile( - self._handle, ctypes.byref(buf), size, None, self._overlapped - ): - if ctypes.get_last_error() != ERROR_IO_PENDING: - raise ctypes.WinError(ctypes.get_last_error()) - read = DWORD(0) - if not GetOverlappedResultEx( - self._handle, self._overlapped, ctypes.byref(read), timeout * 1000, True - ): - raise ctypes.WinError(ctypes.get_last_error()) - return buf.raw[0 : read.value] - - def close(self): - CloseHandle(self._overlapped.hEvent) - CloseHandle(self._handle) - -else: - - class Pipe(object): - def __init__(self, prefix): - self.name = os.path.join(prefix, "stub_port_number") - os.mkfifo(self.name) - self._fd = os.open(self.name, os.O_RDONLY | os.O_NONBLOCK) - - def finish_connection(self, timeout): - pass - - def read(self, size, timeout): - (readers, _, _) = select.select([self._fd], [], [], timeout) - if self._fd not in readers: - raise TimeoutError - return os.read(self._fd, size) - - def close(self): - os.close(self._fd) +from lldbgdbserverutils import Pipe class TestGdbRemoteConnection(gdbremote_testcase.GdbRemoteTestCaseBase): From fe36e25552d79a91c0a1dd0d93fcf23fdcd0ae28 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 22 Jul 2024 12:40:54 -0700 Subject: [PATCH 18/33] [lldb-dap] Updated README.md for newly added attach properties. Added "gdb-remote-port" and "gdb-remote-hostname" attach properties usage in README.md. This was missed in PR https://github.com/llvm/llvm-project/pull/91570 --- lldb/tools/lldb-dap/README.md | 31 +++++++++++++++++++++++++++++++ lldb/tools/lldb-dap/package.json | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md index 8ecbaf7ce98163..11a14d29ab51e2 100644 --- a/lldb/tools/lldb-dap/README.md +++ b/lldb/tools/lldb-dap/README.md @@ -157,6 +157,20 @@ locally on port `2345`. } ``` +You can also use the `gdb-remote-port` parameter to send an attach request +to a debug server running on the current machine, +instead of using the custom command `attachCommands`. + +```javascript +{ + "name": "Local Debug Server", + "type": "lldb-dap", + "request": "attach", + "program": "/tmp/a.out", + "gdb-remote-port": 2345, +} +``` + #### Connect to a Debug Server on Another Machine This connects to a debug server running on another machine with hostname @@ -173,6 +187,23 @@ port `5678` of that other machine. } ``` +You can also use the `gdb-remote-hostname` and `gdb-remote-port` parameters +to send an attach request to a debug server running on a different machine, +instead of custom command `attachCommands`. +The default hostname being used `localhost`. + + +```javascript +{ + "name": "Local Debug Server", + "type": "lldb-dap", + "request": "attach", + "program": "/tmp/a.out", + "gdb-remote-port": 5678, + "gdb-remote-hostname": "hostname", +} +``` + ## Custom debugger commands The `lldb-dap` tool includes additional custom commands to support the Debug diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index fd5de30ed28dd7..66d9efb37d9810 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -353,7 +353,7 @@ "number", "string" ], - "description": "TCP/IP port to attach to. Specifying both pid and port is an error." + "description": "TCP/IP port to attach to a remote systerm. Specifying both pid and port is an error." }, "gdb-remote-hostname": { "type": "string", From 10d16b8b2109c3152e1f5d9cee628e92ef3b5e29 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 23 Jul 2024 23:34:00 -0700 Subject: [PATCH 19/33] [lldb-dap] Updated README.md for newly added attach properties. #99926 Corrected spelling mistakes. --- lldb/tools/lldb-dap/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 66d9efb37d9810..4ff9c2287ac7b2 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -353,7 +353,7 @@ "number", "string" ], - "description": "TCP/IP port to attach to a remote systerm. Specifying both pid and port is an error." + "description": "TCP/IP port to attach to a remote system. Specifying both pid and port is an error." }, "gdb-remote-hostname": { "type": "string", From 58affe702b485d160d97be05051bce067a3972a8 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Wed, 24 Jul 2024 23:48:02 -0700 Subject: [PATCH 20/33] [lldb-dap] Bump the version to 0.2.3 Bump the lldb-dap version to 0.2.3. --- lldb/tools/lldb-dap/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 4ff9c2287ac7b2..97e4efe7bac19d 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -1,7 +1,7 @@ { "name": "lldb-dap", "displayName": "LLDB DAP", - "version": "0.2.2", + "version": "0.2.3", "publisher": "llvm-vs-code-extensions", "homepage": "https://lldb.llvm.org", "description": "LLDB debugging from VSCode", From 7e221825b8537f69e19cf366c446d9a09a4768fe Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 20 Aug 2024 12:21:12 -0700 Subject: [PATCH 21/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Added support for "supportsInstructionBreakpoints" capability and now it this command is triggered when we set instruction breakpoint. We need this support as part of enabling disassembly view debugging. Following features should work as part of this feature enablement: 1. Settings breakpoints in disassembly view: Unsetting the breakpoint is not happening from the disassembly view. Currently we need to unset breakpoint manually from the breakpoint List. Multiple breakpoints are getting set for the same $ 2. Step over, step into, continue in the disassembly view The format for DisassembleRequest and DisassembleResponse at https://raw.githubusercontent.com/microsoft/vscode/master/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts . --- .../test/tools/lldb-dap/dap_server.py | 14 ++ .../lldb-dap/instruction-breakpoint/Makefile | 6 + .../TestDAP_instruction_breakpoint.py | 76 ++++++++ .../lldb-dap/instruction-breakpoint/main.cpp | 18 ++ lldb/tools/lldb-dap/CMakeLists.txt | 1 + lldb/tools/lldb-dap/DAP.h | 2 + lldb/tools/lldb-dap/DAPForward.h | 1 + lldb/tools/lldb-dap/InstructionBreakpoint.cpp | 27 +++ lldb/tools/lldb-dap/InstructionBreakpoint.h | 36 ++++ lldb/tools/lldb-dap/JSONUtils.cpp | 96 +++++++++ lldb/tools/lldb-dap/JSONUtils.h | 11 ++ lldb/tools/lldb-dap/lldb-dap.cpp | 184 +++++++++++++++++- 12 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile create mode 100644 lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py create mode 100644 lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp create mode 100644 lldb/tools/lldb-dap/InstructionBreakpoint.cpp create mode 100644 lldb/tools/lldb-dap/InstructionBreakpoint.h diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index a324af57b61df3..98b599608946c0 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -1095,6 +1095,20 @@ def terminate(self): self.send.close() # self.recv.close() + def request_setInstructionBreakpoints(self, memory_reference=[]): + breakpoints = [] + for i in memory_reference: + args_dict = { + "instructionReference": i, + } + breakpoints.append(args_dict) + args_dict = {"breakpoints": breakpoints} + command_dict = { + "command": "setInstructionBreakpoints", + "type": "request", + "arguments": args_dict, + } + return self.send_recv(command_dict) class DebugAdaptorServer(DebugCommunication): def __init__( diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile new file mode 100644 index 00000000000000..714cd70b0bd35c --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main-copy.cpp +CXXFLAGS_EXTRAS := -O1 -g +include Makefile.rules + +main-copy.cpp: main.cpp + cp -f $< $@ diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py new file mode 100644 index 00000000000000..06a56e3861d4a4 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py @@ -0,0 +1,76 @@ +import dap_server +import shutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbdap_testcase +import os +import lldb + + +class TestDAP_InstructionBreakpointTestCase(lldbdap_testcase.DAPTestCaseBase): + + NO_DEBUG_INFO_TESTCASE = True + def setUp(self): + lldbdap_testcase.DAPTestCaseBase.setUp(self) + + self.main_basename = 'main-copy.cpp' + self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename)) + + def test_instruction_breakpoint(self): + self.build() + self.instruction_breakpoint_test() + + def instruction_breakpoint_test(self): + """Sample test to ensure SBFrame::Disassemble produces SOME output""" + # Create a target by the debugger. + target = self.createTestTarget() + + #build target and create DAP + #self.build_and_create_debug_adaptor() + + main_line = line_number('main.cpp', 'breakpoint 1') + + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Set source breakpoint 1 + response = self.dap_server.request_setBreakpoints(self.main_path, [main_line]) + breakpoints = response['body']['breakpoints'] + self.assertEquals(len(breakpoints), 1) + breakpoint = breakpoints[0] + self.assertEqual(breakpoint['line'], main_line, "incorrect breakpoint source line") + self.assertTrue(breakpoint['verified'], "breakpoint is not verified") + self.assertEqual(self.main_basename, breakpoint['source']['name'], "incorrect source name") + self.assertEqual(self.main_path, breakpoint['source']['path'], "incorrect source file path") + other_breakpoint_id = breakpoint['id'] + + # Continue and then verifiy the breakpoint + self.dap_server.request_continue() + self.verify_breakpoint_hit([other_breakpoint_id]) + + # now we check the stack trace making sure that we got mapped source paths + frames = self.dap_server.request_stackTrace()['body']['stackFrames'] + intstructionPointerReference = [] + setIntstructionBreakpoints = [] + intstructionPointerReference.append(frames[0]['instructionPointerReference']) + self.assertEqual(frames[0]['source']['name'], self.main_basename, "incorrect source name") + self.assertEqual(frames[0]['source']['path'], self.main_path, "incorrect source file path") + + # Check disassembly view + instruction = self.disassemble(frameIndex=0) + self.assertEqual(instruction["address"], intstructionPointerReference[0], "current breakpoint reference is not in the disaasembly view") + + # Get next instruction address to set instruction breakpoint + disassembled_instruction_list = self.dap_server.disassembled_instructions + instruction_addr_list = list(disassembled_instruction_list.keys()) + index = instruction_addr_list.index(intstructionPointerReference[0]) + if len(instruction_addr_list) >= (index + 1): + next_inst_addr = int(instruction_addr_list[index + 1], 16) + if next_inst_addr is not 0: + setIntstructionBreakpoints.append(hex(next_inst_addr)) + instruction_breakpoint_response = self.dap_server.request_setInstructionBreakpoints(setIntstructionBreakpoints) + inst_breakpoints = instruction_breakpoint_response['body']['breakpoints'] + self.assertEqual(inst_breakpoints[0]['instructionReference'], hex(next_inst_addr), "Instruction breakpoint has not been resolved or failed to relocate the instruction breakpoint") + self.dap_server.request_continue() + self.verify_breakpoint_hit([inst_breakpoints[0]['id']]) diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp new file mode 100644 index 00000000000000..c4b72144b7110b --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp @@ -0,0 +1,18 @@ +#include +#include + +int function(int x) { + + if (x == 0)// breakpoint 1 + return x; + + if ((x % 2) != 0) + return x; + else + return function(x-1) + x; +} + +int main(int argc, char const *argv[]) { + int n = function(2); + return n; +} \ No newline at end of file diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index f8f0d86453f585..d68098bf7b3266 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -38,6 +38,7 @@ add_lldb_tool(lldb-dap SourceBreakpoint.cpp DAP.cpp Watchpoint.cpp + InstructionBreakpoint.cpp LINK_LIBS liblldb diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 7828272aa15a7d..8be5a5a95aa38a 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -68,6 +68,8 @@ namespace lldb_dap { typedef llvm::DenseMap SourceBreakpointMap; typedef llvm::StringMap FunctionBreakpointMap; +typedef llvm::DenseMap + InstructionBreakpointMap; enum class OutputType { Console, Stdout, Stderr, Telemetry }; enum DAPBroadcasterBits { diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h index 8c79488fae8dbf..159d999a63c820 100644 --- a/lldb/tools/lldb-dap/DAPForward.h +++ b/lldb/tools/lldb-dap/DAPForward.h @@ -15,6 +15,7 @@ struct ExceptionBreakpoint; struct FunctionBreakpoint; struct SourceBreakpoint; struct Watchpoint; +struct InstructionBreakpoint; } // namespace lldb_dap namespace lldb { diff --git a/lldb/tools/lldb-dap/InstructionBreakpoint.cpp b/lldb/tools/lldb-dap/InstructionBreakpoint.cpp new file mode 100644 index 00000000000000..d4d8db5c51ec15 --- /dev/null +++ b/lldb/tools/lldb-dap/InstructionBreakpoint.cpp @@ -0,0 +1,27 @@ +//===-- InstructionBreakpoint.cpp ------------------------------------*- C++ +//-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "InstructionBreakpoint.h" +#include "DAP.h" + +namespace lldb_dap { + +// Instruction Breakpoint +InstructionBreakpoint::InstructionBreakpoint(const llvm::json::Object &obj) + : Breakpoint(obj), instructionReference(LLDB_INVALID_ADDRESS), id(0), + offset(GetSigned(obj, "offset", 0)) { + GetString(obj, "instructionReference").getAsInteger(0, instructionReference); + instructionReference += offset; +} + +void InstructionBreakpoint::SetInstructionBreakpoint() { + bp = g_dap.target.BreakpointCreateByAddress(instructionReference); + id = bp.GetID(); +} +} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/InstructionBreakpoint.h b/lldb/tools/lldb-dap/InstructionBreakpoint.h new file mode 100644 index 00000000000000..a9b94f648aeab7 --- /dev/null +++ b/lldb/tools/lldb-dap/InstructionBreakpoint.h @@ -0,0 +1,36 @@ +//===-- InstructionBreakpoint.h --------------------------------------*- C++ +//-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_INSTRUCTIONBREAKPOINT_H +#define LLDB_TOOLS_LLDB_DAP_INSTRUCTIONBREAKPOINT_H + +#include "Breakpoint.h" +#include "llvm/ADT/StringRef.h" + +namespace lldb_dap { + +// Instruction Breakpoint +struct InstructionBreakpoint : public Breakpoint { + + lldb::addr_t instructionReference; + int32_t id; + int32_t offset; + + InstructionBreakpoint() + : Breakpoint(), instructionReference(LLDB_INVALID_ADDRESS), id(0), + offset(0) {} + InstructionBreakpoint(const llvm::json::Object &obj); + + // Set instruction breakpoint in LLDB as a new breakpoint + void SetInstructionBreakpoint(); +}; + +} // namespace lldb_dap + +#endif diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index a8b85f55939e17..5a2a6c044ea4b1 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -766,6 +766,102 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { return llvm::json::Value(std::move(object)); } +// Response to `setInstructionBreakpoints` request. +// "Breakpoint": { +// "type": "object", +// "description": "Response to `setInstructionBreakpoints` request.", +// "properties": { +// "id": { +// "type": "number", +// "description": "The identifier for the breakpoint. It is needed if +// breakpoint events are used to update or remove breakpoints." +// }, +// "verified": { +// "type": "boolean", +// "description": "If true, the breakpoint could be set (but not +// necessarily at the desired location." +// }, +// "message": { +// "type": "string", +// "description": "A message about the state of the breakpoint. +// This is shown to the user and can be used to explain why a breakpoint +// could not be verified." +// }, +// "source": { +// "type": "Source", +// "description": "The source where the breakpoint is located." +// }, +// "line": { +// "type": "number", +// "description": "The start line of the actual range covered by the +// breakpoint." +// }, +// "column": { +// "type": "number", +// "description": "The start column of the actual range covered by the +// breakpoint." +// }, +// "endLine": { +// "type": "number", +// "description": "The end line of the actual range covered by the +// breakpoint." +// }, +// "endColumn": { +// "type": "number", +// "description": "The end column of the actual range covered by the +// breakpoint. If no end line is given, then the end column is assumed to +// be in the start line." +// }, +// "instructionReference": { +// "type": "string", +// "description": "A memory reference to where the breakpoint is set." +// }, +// "offset": { +// "type": "number", +// "description": "The offset from the instruction reference. +// This can be negative." +// }, +// }, +// "required": [ "id", "verified", "line"] +// } +llvm::json::Value CreateInstructionBreakpoint(lldb::SBBreakpoint &bp) { + llvm::json::Object object; + if (!bp.IsValid()) { + return llvm::json::Value(std::move(object)); + } + object.try_emplace("verified", bp.GetNumResolvedLocations() > 0); + object.try_emplace("id", bp.GetID()); + + lldb::SBBreakpointLocation bp_loc; + const auto num_locs = bp.GetNumLocations(); + for (size_t i = 0; i < num_locs; ++i) { + bp_loc = bp.GetLocationAtIndex(i); + if (bp_loc.IsResolved()) + break; + } + // If not locations are resolved, use the first location. + if (!bp_loc.IsResolved()) + bp_loc = bp.GetLocationAtIndex(0); + auto bp_addr = bp_loc.GetAddress(); + + lldb::addr_t address_load = bp_addr.GetLoadAddress(g_dap.target); + std::string address_hex; + llvm::raw_string_ostream addr_strm(address_hex); + if (address_load != LLDB_INVALID_ADDRESS) { + addr_strm << llvm::format_hex(address_load, 0); + addr_strm.flush(); + object.try_emplace("instructionReference", address_hex); + } + + if (bp_addr.IsValid()) { + auto line_entry = bp_addr.GetLineEntry(); + const auto line = line_entry.GetLine(); + if (line != UINT32_MAX) + object.try_emplace("line", line); + } + return llvm::json::Value(std::move(object)); +} + // "Thread": { // "type": "object", // "description": "A Thread", diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 1515f5ba2e5f4d..693f35ee07e5f6 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -322,6 +322,17 @@ llvm::json::Value CreateSource(llvm::StringRef source_path); /// definition outlined by Microsoft. llvm::json::Value CreateStackFrame(lldb::SBFrame &frame); +/// Create a "instruction" object for a LLDB disassemble object as described in +/// the Visual Studio Code debug adaptor definition. +/// +/// \param[in] bp +/// The LLDB instruction object used to populate the disassembly +/// instruction. +/// \return +/// A "Scope" JSON object with that follows the formal JSON +/// definition outlined by Microsoft. +llvm::json::Value CreateInstructionBreakpoint(lldb::SBBreakpoint &bp); + /// Create a "Thread" object for a LLDB thread object. /// /// This function will fill in the following keys in the returned diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index f50a6c17310739..8a0c99393a0838 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -1627,12 +1627,12 @@ void request_initialize(const llvm::json::Object &request) { "lldb-dap", "Commands for managing lldb-dap."); if (GetBoolean(arguments, "supportsStartDebuggingRequest", false)) { cmd.AddCommand( - "startDebugging", new StartDebuggingRequestHandler(), + "startDebugging", &g_dap.start_debugging_request_handler, "Sends a startDebugging request from the debug adapter to the client " "to start a child debug session of the same type as the caller."); } cmd.AddCommand( - "repl-mode", new ReplModeRequestHandler(), + "repl-mode", &g_dap.repl_mode_request_handler, "Get or set the repl behavior of lldb-dap evaluation requests."); g_dap.progress_event_thread = std::thread(ProgressEventThreadFunction); @@ -1718,6 +1718,8 @@ void request_initialize(const llvm::json::Object &request) { body.try_emplace("supportsLogPoints", true); // The debug adapter supports data watchpoints. body.try_emplace("supportsDataBreakpoints", true); + // The debug adapter support for instruction breakpoint. + body.try_emplace("supportsInstructionBreakpoints", true); // Put in non-DAP specification lldb specific information. llvm::json::Object lldb_json; @@ -4046,6 +4048,181 @@ void request__testGetTargetBreakpoints(const llvm::json::Object &request) { g_dap.SendJSON(llvm::json::Value(std::move(response))); } +// SetInstructionBreakpoints request; value of command field is +// 'setInstructionBreakpoints'. Replaces all existing instruction breakpoints. +// Typically, instruction breakpoints would be set from a disassembly window. To +// clear all instruction breakpoints, specify an empty array. When an +// instruction breakpoint is hit, a `stopped` event (with reason `instruction +// breakpoint`) is generated. Clients should only call this request if the +// corresponding capability `supportsInstructionBreakpoints` is true. interface +// SetInstructionBreakpointsRequest extends Request { +// command: 'setInstructionBreakpoints'; +// arguments: SetInstructionBreakpointsArguments; +// } +// interface SetInstructionBreakpointsArguments { +// The instruction references of the breakpoints +// breakpoints: InstructionBreakpoint[]; +// } +// "InstructionBreakpoint ": { +// "type": "object", +// "description": "Properties of a breakpoint passed to the +// setInstructionBreakpoints request.", "properties": { +// "instructionReference": { +// "type": "string", +// "description": "The instruction reference of the breakpoint. +// This should be a memory or instruction pointer reference from an +// EvaluateResponse, Variable, StackFrame, GotoTarget, or Breakpoint." +// }, +// "offset": { +// "type": "number", +// "description": "The offset from the instruction reference. +// This can be negative." +// }, +// "condition": { +// "type": "string", +// "description": "An expression for conditional breakpoints. +// It is only honored by a debug adapter if the corresponding capability +// supportsConditionalBreakpoints` is true." +// }, +// "hitCondition": { +// "type": "string", +// "description": "An expression that controls how many hits of the +// breakpoint are ignored. The debug adapter is expected to interpret the +// expression as needed. The attribute is only honored by a debug adapter +// if the corresponding capability `supportsHitConditionalBreakpoints` is +// true." +// }, +// } +// interface SetInstructionBreakpointsResponse extends Response { +// body: { +// Information about the breakpoints. The array elements correspond to the +// elements of the `breakpoints` array. +// breakpoints: Breakpoint[]; +// }; +// } +// Response to `setInstructionBreakpoints` request. +// "Breakpoint": { +// "type": "object", +// "description": "Response to `setInstructionBreakpoints` request.", +// "properties": { +// "id": { +// "type": "number", +// "description": "The identifier for the breakpoint. It is needed if +// breakpoint events are used to update or remove breakpoints." +// }, +// "verified": { +// "type": "boolean", +// "description": "If true, the breakpoint could be set (but not +// necessarily at the desired location." +// }, +// "message": { +// "type": "string", +// "description": "A message about the state of the breakpoint. +// This is shown to the user and can be used to explain why a breakpoint +// could not be verified." +// }, +// "source": { +// "type": "Source", +// "description": "The source where the breakpoint is located." +// }, +// "line": { +// "type": "number", +// "description": "The start line of the actual range covered by the +// breakpoint." +// }, +// "column": { +// "type": "number", +// "description": "The start column of the actual range covered by the +// breakpoint." +// }, +// "endLine": { +// "type": "number", +// "description": "The end line of the actual range covered by the +// breakpoint." +// }, +// "endColumn": { +// "type": "number", +// "description": "The end column of the actual range covered by the +// breakpoint. If no end line is given, then the end column is assumed to +// be in the start line." +// }, +// "instructionReference": { +// "type": "string", +// "description": "A memory reference to where the breakpoint is set." +// }, +// "offset": { +// "type": "number", +// "description": "The offset from the instruction reference. +// This can be negative." +// }, +// }, +// "required": [ "id", "verified", "line"] +// } +void request_setInstructionBreakpoints(const llvm::json::Object &request) { + llvm::json::Object response; + llvm::json::Array response_breakpoints; + llvm::json::Object body; + FillResponse(request, response); + + auto arguments = request.getObject("arguments"); + auto breakpoints = arguments->getArray("breakpoints"); + + // It holds active instruction brealpoint list received from DAP. + InstructionBreakpointMap request_ibp; + if (breakpoints) { + for (const auto &bp : *breakpoints) { + auto bp_obj = bp.getAsObject(); + if (bp_obj) { + // Read instruction breakpoint request. + InstructionBreakpoint inst_bp(*bp_obj); + // Store them into map for reference. + request_ibp[inst_bp.instructionReference] = std::move(inst_bp); + } + } + + // Store removed instruction breakpoint list to delete them further. + std::vector removed_ibp; + + // Iterate previouse active instruction breakpoint list. + for (auto &prev_ibp : g_dap.instruction_breakpoints) { + // Find previouse instruction breakpoint reference address in newly + // received instruction breakpoint list. + auto inst_reference = request_ibp.find(prev_ibp.first); + // Request for remove and delete the breakpoint, if the prev instruction + // breakpoint ID is not available in active instrcation breakpoint list. + // Means delete removed breakpoint instance. + if (inst_reference == request_ibp.end()) { + g_dap.target.BreakpointDelete(prev_ibp.second.id); + removed_ibp.push_back(prev_ibp.first); + } else { + // Instead of recreating breakpoint instance, update the breakpoint if + // there are any conditional changes. + prev_ibp.second.UpdateBreakpoint(inst_reference->second); + request_ibp.erase(inst_reference); + response_breakpoints.emplace_back( + CreateInstructionBreakpoint(prev_ibp.second.bp)); + } + } + // Update Prev instruction breakpoint list. + for (const auto &name : removed_ibp) { + g_dap.instruction_breakpoints.erase(name); + } + + for (auto &req_bpi : request_ibp) { + // Add this breakpoint info to the response + g_dap.instruction_breakpoints[req_bpi.first] = std::move(req_bpi.second); + InstructionBreakpoint &new_bp = + g_dap.instruction_breakpoints[req_bpi.first]; + new_bp.SetInstructionBreakpoint(); + response_breakpoints.emplace_back(CreateInstructionBreakpoint(new_bp.bp)); + } + } + + body.try_emplace("breakpoints", std::move(response_breakpoints)); + response.try_emplace("body", std::move(body)); + g_dap.SendJSON(llvm::json::Value(std::move(response))); +} + void RegisterRequestCallbacks() { g_dap.RegisterRequestCallback("attach", request_attach); g_dap.RegisterRequestCallback("completions", request_completions); @@ -4078,6 +4255,9 @@ void RegisterRequestCallbacks() { g_dap.RegisterRequestCallback("threads", request_threads); g_dap.RegisterRequestCallback("variables", request_variables); g_dap.RegisterRequestCallback("disassemble", request_disassemble); + // Instruction breapoint request + g_dap.RegisterRequestCallback("setInstructionBreakpoints", + request_setInstructionBreakpoints); // Custom requests g_dap.RegisterRequestCallback("compileUnits", request_compileUnits); g_dap.RegisterRequestCallback("modules", request_modules); From ffe8dbcbaf6fec2602f4d1330833e6540c493314 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 20 Aug 2024 12:42:57 -0700 Subject: [PATCH 22/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Resolved code format issues. --- .../TestDAP_instruction_breakpoint.py | 64 +++++++++++++------ .../lldb-dap/instruction-breakpoint/main.cpp | 4 +- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py index 06a56e3861d4a4..24d55404778c9d 100644 --- a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py @@ -9,12 +9,12 @@ class TestDAP_InstructionBreakpointTestCase(lldbdap_testcase.DAPTestCaseBase): - NO_DEBUG_INFO_TESTCASE = True + def setUp(self): lldbdap_testcase.DAPTestCaseBase.setUp(self) - self.main_basename = 'main-copy.cpp' + self.main_basename = "main-copy.cpp" self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename)) def test_instruction_breakpoint(self): @@ -26,40 +26,52 @@ def instruction_breakpoint_test(self): # Create a target by the debugger. target = self.createTestTarget() - #build target and create DAP - #self.build_and_create_debug_adaptor() - - main_line = line_number('main.cpp', 'breakpoint 1') + main_line = line_number("main.cpp", "breakpoint 1") program = self.getBuildArtifact("a.out") self.build_and_launch(program) # Set source breakpoint 1 response = self.dap_server.request_setBreakpoints(self.main_path, [main_line]) - breakpoints = response['body']['breakpoints'] + breakpoints = response["body"]["breakpoints"] self.assertEquals(len(breakpoints), 1) breakpoint = breakpoints[0] - self.assertEqual(breakpoint['line'], main_line, "incorrect breakpoint source line") - self.assertTrue(breakpoint['verified'], "breakpoint is not verified") - self.assertEqual(self.main_basename, breakpoint['source']['name'], "incorrect source name") - self.assertEqual(self.main_path, breakpoint['source']['path'], "incorrect source file path") - other_breakpoint_id = breakpoint['id'] + self.assertEqual( + breakpoint["line"], main_line, "incorrect breakpoint source line" + ) + self.assertTrue(breakpoint["verified"], "breakpoint is not verified") + self.assertEqual( + self.main_basename, breakpoint["source"]["name"], "incorrect source name" + ) + self.assertEqual( + self.main_path, breakpoint["source"]["path"], "incorrect source file path" + ) + other_breakpoint_id = breakpoint["id"] # Continue and then verifiy the breakpoint self.dap_server.request_continue() self.verify_breakpoint_hit([other_breakpoint_id]) # now we check the stack trace making sure that we got mapped source paths - frames = self.dap_server.request_stackTrace()['body']['stackFrames'] + frames = self.dap_server.request_stackTrace()["body"]["stackFrames"] intstructionPointerReference = [] setIntstructionBreakpoints = [] - intstructionPointerReference.append(frames[0]['instructionPointerReference']) - self.assertEqual(frames[0]['source']['name'], self.main_basename, "incorrect source name") - self.assertEqual(frames[0]['source']['path'], self.main_path, "incorrect source file path") + intstructionPointerReference.append(frames[0]["instructionPointerReference"]) + self.assertEqual( + frames[0]["source"]["name"], self.main_basename, "incorrect source name" + ) + self.assertEqual( + frames[0]["source"]["path"], self.main_path, "incorrect source file path" + ) + # Check disassembly view instruction = self.disassemble(frameIndex=0) - self.assertEqual(instruction["address"], intstructionPointerReference[0], "current breakpoint reference is not in the disaasembly view") + self.assertEqual( + instruction["address"], + intstructionPointerReference[0], + "current breakpoint reference is not in the disaasembly view", + ) # Get next instruction address to set instruction breakpoint disassembled_instruction_list = self.dap_server.disassembled_instructions @@ -69,8 +81,18 @@ def instruction_breakpoint_test(self): next_inst_addr = int(instruction_addr_list[index + 1], 16) if next_inst_addr is not 0: setIntstructionBreakpoints.append(hex(next_inst_addr)) - instruction_breakpoint_response = self.dap_server.request_setInstructionBreakpoints(setIntstructionBreakpoints) - inst_breakpoints = instruction_breakpoint_response['body']['breakpoints'] - self.assertEqual(inst_breakpoints[0]['instructionReference'], hex(next_inst_addr), "Instruction breakpoint has not been resolved or failed to relocate the instruction breakpoint") + instruction_breakpoint_response = ( + self.dap_server.request_setInstructionBreakpoints( + setIntstructionBreakpoints + ) + ) + inst_breakpoints = instruction_breakpoint_response["body"][ + "breakpoints" + ] + self.assertEqual( + inst_breakpoints[0]["instructionReference"], + hex(next_inst_addr), + "Instruction breakpoint has not been resolved or failed to relocate the instruction breakpoint", + ) self.dap_server.request_continue() - self.verify_breakpoint_hit([inst_breakpoints[0]['id']]) + self.verify_breakpoint_hit([inst_breakpoints[0]["id"]]) diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp index c4b72144b7110b..3c710d64171570 100644 --- a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp @@ -3,13 +3,13 @@ int function(int x) { - if (x == 0)// breakpoint 1 + if (x == 0) // breakpoint 1 return x; if ((x % 2) != 0) return x; else - return function(x-1) + x; + return function(x - 1) + x; } int main(int argc, char const *argv[]) { From 4d87a93cb74806e80798a1083a841246b3c53804 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 20 Aug 2024 12:53:32 -0700 Subject: [PATCH 23/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Resolved code format issues. --- .../instruction-breakpoint/TestDAP_instruction_breakpoint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py index 24d55404778c9d..ed1a6bf250c761 100644 --- a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py @@ -64,7 +64,6 @@ def instruction_breakpoint_test(self): frames[0]["source"]["path"], self.main_path, "incorrect source file path" ) - # Check disassembly view instruction = self.disassemble(frameIndex=0) self.assertEqual( From 18bcb037a91270825926cdd3a611c7b88117e80e Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 20 Aug 2024 15:05:50 -0700 Subject: [PATCH 24/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. The following changes were committed by mistake, so they have been reverted. "startDebugging", &g_dap.start_debugging_request_handler, "repl-mode", &g_dap.repl_mode_request_handler, --- lldb/tools/lldb-dap/lldb-dap.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 8a0c99393a0838..7c0799eaadfb6d 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -1627,12 +1627,12 @@ void request_initialize(const llvm::json::Object &request) { "lldb-dap", "Commands for managing lldb-dap."); if (GetBoolean(arguments, "supportsStartDebuggingRequest", false)) { cmd.AddCommand( - "startDebugging", &g_dap.start_debugging_request_handler, + "startDebugging", new StartDebuggingRequestHandler(), "Sends a startDebugging request from the debug adapter to the client " "to start a child debug session of the same type as the caller."); } cmd.AddCommand( - "repl-mode", &g_dap.repl_mode_request_handler, + "repl-mode", new ReplModeRequestHandler(), "Get or set the repl behavior of lldb-dap evaluation requests."); g_dap.progress_event_thread = std::thread(ProgressEventThreadFunction); From 9893985131ff336327dd431bdfdeaf62fcda5565 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Tue, 20 Aug 2024 21:01:08 -0700 Subject: [PATCH 25/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Few changes were missed in lldb/tools/lldb-dap/DAP.h, so added in this version. --- lldb/tools/lldb-dap/DAP.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 8be5a5a95aa38a..189301e28cf74d 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -54,6 +54,7 @@ #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" #include "IOStream.h" +#include "InstructionBreakpoint.h" #include "ProgressEvent.h" #include "RunInTerminal.h" #include "SourceBreakpoint.h" @@ -158,6 +159,7 @@ struct DAP { std::unique_ptr log; llvm::StringMap source_breakpoints; FunctionBreakpointMap function_breakpoints; + InstructionBreakpointMap instruction_breakpoints; std::optional> exception_breakpoints; llvm::once_flag init_exception_breakpoints_flag; std::vector pre_init_commands; From e0101c12bb6f11f06a00efff9849f276903f623e Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Thu, 22 Aug 2024 09:10:42 -0700 Subject: [PATCH 26/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Resolved review comments. --- .../lldb-dap/instruction-breakpoint/Makefile | 2 +- lldb/tools/lldb-dap/lldb-dap.cpp | 287 +++++++++++------- 2 files changed, 184 insertions(+), 105 deletions(-) diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile index 714cd70b0bd35c..697527c4e55223 100644 --- a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile @@ -1,5 +1,5 @@ CXX_SOURCES := main-copy.cpp -CXXFLAGS_EXTRAS := -O1 -g +CXXFLAGS_EXTRAS := -O0 -g include Makefile.rules main-copy.cpp: main.cpp diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 7c0799eaadfb6d..61f06e70df28fe 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -4048,116 +4048,195 @@ void request__testGetTargetBreakpoints(const llvm::json::Object &request) { g_dap.SendJSON(llvm::json::Value(std::move(response))); } -// SetInstructionBreakpoints request; value of command field is -// 'setInstructionBreakpoints'. Replaces all existing instruction breakpoints. -// Typically, instruction breakpoints would be set from a disassembly window. To -// clear all instruction breakpoints, specify an empty array. When an -// instruction breakpoint is hit, a `stopped` event (with reason `instruction -// breakpoint`) is generated. Clients should only call this request if the -// corresponding capability `supportsInstructionBreakpoints` is true. interface -// SetInstructionBreakpointsRequest extends Request { -// command: 'setInstructionBreakpoints'; -// arguments: SetInstructionBreakpointsArguments; -// } -// interface SetInstructionBreakpointsArguments { -// The instruction references of the breakpoints -// breakpoints: InstructionBreakpoint[]; -// } -// "InstructionBreakpoint ": { -// "type": "object", -// "description": "Properties of a breakpoint passed to the -// setInstructionBreakpoints request.", "properties": { -// "instructionReference": { -// "type": "string", -// "description": "The instruction reference of the breakpoint. -// This should be a memory or instruction pointer reference from an -// EvaluateResponse, Variable, StackFrame, GotoTarget, or Breakpoint." -// }, -// "offset": { -// "type": "number", -// "description": "The offset from the instruction reference. -// This can be negative." -// }, -// "condition": { -// "type": "string", -// "description": "An expression for conditional breakpoints. -// It is only honored by a debug adapter if the corresponding capability -// supportsConditionalBreakpoints` is true." -// }, -// "hitCondition": { -// "type": "string", -// "description": "An expression that controls how many hits of the -// breakpoint are ignored. The debug adapter is expected to interpret the -// expression as needed. The attribute is only honored by a debug adapter -// if the corresponding capability `supportsHitConditionalBreakpoints` is -// true." -// }, -// } -// interface SetInstructionBreakpointsResponse extends Response { -// body: { -// Information about the breakpoints. The array elements correspond to the -// elements of the `breakpoints` array. -// breakpoints: Breakpoint[]; -// }; -// } -// Response to `setInstructionBreakpoints` request. -// "Breakpoint": { -// "type": "object", -// "description": "Response to `setInstructionBreakpoints` request.", -// "properties": { -// "id": { -// "type": "number", -// "description": "The identifier for the breakpoint. It is needed if -// breakpoint events are used to update or remove breakpoints." -// }, -// "verified": { -// "type": "boolean", -// "description": "If true, the breakpoint could be set (but not -// necessarily at the desired location." -// }, -// "message": { -// "type": "string", -// "description": "A message about the state of the breakpoint. -// This is shown to the user and can be used to explain why a breakpoint -// could not be verified." -// }, -// "source": { -// "type": "Source", -// "description": "The source where the breakpoint is located." -// }, -// "line": { -// "type": "number", -// "description": "The start line of the actual range covered by the -// breakpoint." +// "SetInstructionBreakpointsRequest" : { +// "allOf" : [ +// {"$ref" : "#/definitions/Request"}, { +// "type" : "object", +// "description" : +// "Replaces all existing instruction breakpoints. Typically, " +// "instruction breakpoints would be set from a disassembly window. " +// "\nTo clear all instruction breakpoints, specify an empty " +// "array.\nWhen an instruction breakpoint is hit, a `stopped` event " +// "(with reason `instruction breakpoint`) is generated.\nClients " +// "should only call this request if the corresponding capability " +// "`supportsInstructionBreakpoints` is true.", +// "properties" : { +// "command" : {"type" : "string", "enum" : +// ["setInstructionBreakpoints"]}, "arguments" : +// {"$ref" : "#/definitions/SetInstructionBreakpointsArguments"} +// }, +// "required" : [ "command", "arguments" ] +// } +// ] +// }, +// "SetInstructionBreakpointsArguments" +// : { +// "type" : "object", +// "description" : "Arguments for `setInstructionBreakpoints` request", +// "properties" : { +// "breakpoints" : { +// "type" : "array", +// "items" : {"$ref" : "#/definitions/InstructionBreakpoint"}, +// "description" : "The instruction references of the breakpoints" +// } +// }, +// "required" : ["breakpoints"] // }, -// "column": { -// "type": "number", -// "description": "The start column of the actual range covered by the -// breakpoint." +// "SetInstructionBreakpointsResponse" +// : { +// "allOf" : [ +// {"$ref" : "#/definitions/Response"}, { +// "type" : "object", +// "description" : "Response to `setInstructionBreakpoints` request", +// "properties" : { +// "body" : { +// "type" : "object", +// "properties" : { +// "breakpoints" : { +// "type" : "array", +// "items" : {"$ref" : "#/definitions/Breakpoint"}, +// "description" : +// "Information about the breakpoints. The array elements +// " "correspond to the elements of the `breakpoints` +// array." +// } +// }, +// "required" : ["breakpoints"] +// } +// }, +// "required" : ["body"] +// } +// ] // }, -// "endLine": { -// "type": "number", -// "description": "The end line of the actual range covered by the -// breakpoint." +// "InstructionBreakpoint" : { +// "type" : "object", +// "description" : "Properties of a breakpoint passed to the " +// "`setInstructionBreakpoints` request", +// "properties" : { +// "instructionReference" : { +// "type" : "string", +// "description" : +// "The instruction reference of the breakpoint.\nThis should be a " +// "memory or instruction pointer reference from an +// `EvaluateResponse`, " +// "`Variable`, `StackFrame`, `GotoTarget`, or `Breakpoint`." // }, -// "endColumn": { -// "type": "number", -// "description": "The end column of the actual range covered by the -// breakpoint. If no end line is given, then the end column is assumed to -// be in the start line." +// "offset" : { +// "type" : "integer", +// "description" : "The offset from the instruction reference in " +// "bytes.\nThis can be negative." // }, -// "instructionReference": { -// "type": "string", -// "description": "A memory reference to where the breakpoint is set." +// "condition" : { +// "type" : "string", +// "description" : "An expression for conditional breakpoints.\nIt is only +// " +// "honored by a debug adapter if the corresponding " +// "capability `supportsConditionalBreakpoints` is true." // }, -// "offset": { -// "type": "number", -// "description": "The offset from the instruction reference. -// This can be negative." +// "hitCondition" : { +// "type" : "string", +// "description" : "An expression that controls how many hits of the " +// "breakpoint are ignored.\nThe debug adapter is expected +// " "to interpret the expression as needed.\nThe +// attribute " "is only honored by a debug adapter if the +// corresponding " "capability +// `supportsHitConditionalBreakpoints` is true." // }, +// "mode" : { +// "type" : "string", +// "description" : "The mode of this breakpoint. If defined, this must be +// " +// "one of the `breakpointModes` the debug adapter " +// "advertised in its `Capabilities`." +// } // }, -// "required": [ "id", "verified", "line"] -// } +// "required" : ["instructionReference"] +// }, +// "Breakpoint" +// : { +// "type" : "object", +// "description" : +// "Information about a breakpoint created in `setBreakpoints`, " +// "`setFunctionBreakpoints`, `setInstructionBreakpoints`, or " +// "`setDataBreakpoints` requests.", +// "properties" : { +// "id" : { +// "type" : "integer", +// "description" : +// "The identifier for the breakpoint. It is needed if breakpoint +// " "events are used to update or remove breakpoints." +// }, +// "verified" : { +// "type" : "boolean", +// "description" : "If true, the breakpoint could be set (but not " +// "necessarily at the desired location)." +// }, +// "message" : { +// "type" : "string", +// "description" : "A message about the state of the breakpoint.\nThis +// " +// "is shown to the user and can be used to explain +// why " "a breakpoint could not be verified." +// }, +// "source" : { +// "$ref" : "#/definitions/Source", +// "description" : "The source where the breakpoint is located." +// }, +// "line" : { +// "type" : "integer", +// "description" : +// "The start line of the actual range covered by the breakpoint." +// }, +// "column" : { +// "type" : "integer", +// "description" : +// "Start position of the source range covered by the breakpoint. +// " "It is measured in UTF-16 code units and the client +// capability " +// "`columnsStartAt1` determines whether it is 0- or 1-based." +// }, +// "endLine" : { +// "type" : "integer", +// "description" : +// "The end line of the actual range covered by the breakpoint." +// }, +// "endColumn" : { +// "type" : "integer", +// "description" : +// "End position of the source range covered by the breakpoint. It +// " "is measured in UTF-16 code units and the client capability " +// "`columnsStartAt1` determines whether it is 0- or 1-based.\nIf +// " "no end line is given, then the end column is assumed to be +// in " "the start line." +// }, +// "instructionReference" : { +// "type" : "string", +// "description" : "A memory reference to where the breakpoint is +// set." +// }, +// "offset" : { +// "type" : "integer", +// "description" : "The offset from the instruction reference.\nThis " +// "can be negative." +// }, +// "reason" : { +// "type" : "string", +// "description" : +// "A machine-readable explanation of why a breakpoint may not be +// " "verified. If a breakpoint is verified or a specific reason +// is " "not known, the adapter should omit this property. +// Possible " "values include:\n\n- `pending`: Indicates a +// breakpoint might be " "verified in the future, but the adapter +// cannot verify it in the " "current state.\n - `failed`: +// Indicates a breakpoint was not " "able to be verified, and the +// adapter does not believe it can be " "verified without +// intervention.", +// "enum" : [ "pending", "failed" ] +// } +// }, +// "required" : ["verified"] +// }, + void request_setInstructionBreakpoints(const llvm::json::Object &request) { llvm::json::Object response; llvm::json::Array response_breakpoints; @@ -4167,7 +4246,7 @@ void request_setInstructionBreakpoints(const llvm::json::Object &request) { auto arguments = request.getObject("arguments"); auto breakpoints = arguments->getArray("breakpoints"); - // It holds active instruction brealpoint list received from DAP. + // It holds active instruction breakpoint list received from DAP. InstructionBreakpointMap request_ibp; if (breakpoints) { for (const auto &bp : *breakpoints) { From 17f8c6d704588e6befbb1fd5101b88c13520872f Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Thu, 22 Aug 2024 14:03:34 -0700 Subject: [PATCH 27/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Added stop reason for instruction breakpoint. --- lldb/tools/lldb-dap/DAP.cpp | 31 ++++++++++++++++++++++++++++++- lldb/tools/lldb-dap/DAP.h | 4 ++++ lldb/tools/lldb-dap/JSONUtils.cpp | 8 +++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index c3c70e9d739846..1186c65ec351d3 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -67,7 +67,7 @@ static std::string capitalize(llvm::StringRef str) { void DAP::PopulateExceptionBreakpoints() { llvm::call_once(init_exception_breakpoints_flag, [this]() { - exception_breakpoints = std::vector {}; + exception_breakpoints = std::vector{}; if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) { exception_breakpoints->emplace_back("cpp_catch", "C++ Catch", @@ -985,4 +985,33 @@ void DAP::SetThreadFormat(llvm::StringRef format) { } } +InstructionBreakpoint * +DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) { + + for (auto &bp : instruction_breakpoints) { + if (bp.second.id == bp_id) + return &bp.second; + } + return nullptr; +} + +InstructionBreakpoint * +DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) { + const auto num = thread.GetStopReasonDataCount(); + InstructionBreakpoint *inst_bp = nullptr; + for (size_t i = 0; i < num; i += 2) { + // thread.GetStopReasonDataAtIndex(i) will return the bp ID and + // thread.GetStopReasonDataAtIndex(i+1) will return the location + // within that breakpoint. We only care about the bp ID so we can + // see if this is an instruction breakpoint that is getting hit. + lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i); + inst_bp = GetInstructionBreakpoint(bp_id); + // If any breakpoint is not an exception breakpoint, then stop and + // report this as a normal breakpoint + if (inst_bp == nullptr) + return nullptr; + } + return inst_bp; +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 189301e28cf74d..f1aad113d1894c 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -333,6 +333,10 @@ struct DAP { void SetThreadFormat(llvm::StringRef format); + InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id); + + InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread); + private: // Send the JSON in "json_str" to the "out" stream. Correctly send the // "Content-Length:" field followed by the length, followed by the raw diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 5a2a6c044ea4b1..e9a59cbb5fd85a 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -986,7 +986,13 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, body.try_emplace("reason", "exception"); EmplaceSafeString(body, "description", exc_bp->label); } else { - body.try_emplace("reason", "breakpoint"); + InstructionBreakpoint *inst_bp = + g_dap.GetInstructionBPFromStopReason(thread); + if (inst_bp) { + body.try_emplace("reason", "instruction breakpoint"); + } else { + body.try_emplace("reason", "breakpoint"); + } lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0); lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(1); std::string desc_str = From 8c1949857cec8fb30bdd2607a7a14fc6a0e9aeea Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Thu, 22 Aug 2024 14:07:01 -0700 Subject: [PATCH 28/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Added few comments --- lldb/tools/lldb-dap/DAP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 1186c65ec351d3..d14f0494d96f4a 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -1006,7 +1006,7 @@ DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) { // see if this is an instruction breakpoint that is getting hit. lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i); inst_bp = GetInstructionBreakpoint(bp_id); - // If any breakpoint is not an exception breakpoint, then stop and + // If any breakpoint is not an instruction breakpoint, then stop and // report this as a normal breakpoint if (inst_bp == nullptr) return nullptr; From dbad4c593aadd9d6d8ffc50d46ae888b0fc8d40f Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 26 Aug 2024 03:00:28 -0700 Subject: [PATCH 29/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Resolved follwoing review comments : 1. Removed usage of removed_ibp, removed deleted instruction breakpoints deirectly from g_dap.instruction_breakpoints.erase(prev_ibp.first) 2. Reused CreateJsonObject function in CreateInstructionBreakpoint. 3. Updated test cases with stop reason. --- .../test/tools/lldb-dap/lldbdap_testcase.py | 2 +- .../TestDAP_instruction_breakpoint.py | 8 ++--- lldb/tools/lldb-dap/JSONUtils.cpp | 36 ++----------------- lldb/tools/lldb-dap/JSONUtils.h | 2 +- lldb/tools/lldb-dap/lldb-dap.cpp | 14 +++----- 5 files changed, 12 insertions(+), 50 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index 86eba355da83db..c301d3482941de 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -81,7 +81,7 @@ def verify_breakpoint_hit(self, breakpoint_ids): body = stopped_event["body"] if "reason" not in body: continue - if body["reason"] != "breakpoint": + if body["reason"] != "breakpoint" and body["reason"] != "instruction breakpoint": continue if "description" not in body: continue diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py index ed1a6bf250c761..91b04aca7b7bd8 100644 --- a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py @@ -77,9 +77,9 @@ def instruction_breakpoint_test(self): instruction_addr_list = list(disassembled_instruction_list.keys()) index = instruction_addr_list.index(intstructionPointerReference[0]) if len(instruction_addr_list) >= (index + 1): - next_inst_addr = int(instruction_addr_list[index + 1], 16) - if next_inst_addr is not 0: - setIntstructionBreakpoints.append(hex(next_inst_addr)) + next_inst_addr = instruction_addr_list[index + 1] + if len(next_inst_addr) > 2: + setIntstructionBreakpoints.append(next_inst_addr) instruction_breakpoint_response = ( self.dap_server.request_setInstructionBreakpoints( setIntstructionBreakpoints @@ -90,7 +90,7 @@ def instruction_breakpoint_test(self): ] self.assertEqual( inst_breakpoints[0]["instructionReference"], - hex(next_inst_addr), + next_inst_addr, "Instruction breakpoint has not been resolved or failed to relocate the instruction breakpoint", ) self.dap_server.request_continue() diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index c69a7cde40e2aa..7338e7cf41eb03 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -827,41 +827,9 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { // }, // "required": [ "id", "verified", "line"] // } -llvm::json::Value CreateInstructionBreakpoint(lldb::SBBreakpoint &bp) { +llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp) { llvm::json::Object object; - if (!bp.IsValid()) { - return llvm::json::Value(std::move(object)); - } - object.try_emplace("verified", bp.GetNumResolvedLocations() > 0); - object.try_emplace("id", bp.GetID()); - - lldb::SBBreakpointLocation bp_loc; - const auto num_locs = bp.GetNumLocations(); - for (size_t i = 0; i < num_locs; ++i) { - bp_loc = bp.GetLocationAtIndex(i); - if (bp_loc.IsResolved()) - break; - } - // If not locations are resolved, use the first location. - if (!bp_loc.IsResolved()) - bp_loc = bp.GetLocationAtIndex(0); - auto bp_addr = bp_loc.GetAddress(); - - lldb::addr_t address_load = bp_addr.GetLoadAddress(g_dap.target); - std::string address_hex; - llvm::raw_string_ostream addr_strm(address_hex); - if (address_load != LLDB_INVALID_ADDRESS) { - addr_strm << llvm::format_hex(address_load, 0); - addr_strm.flush(); - object.try_emplace("instructionReference", address_hex); - } - - if (bp_addr.IsValid()) { - auto line_entry = bp_addr.GetLineEntry(); - const auto line = line_entry.GetLine(); - if (line != UINT32_MAX) - object.try_emplace("line", line); - } + ibp->CreateJsonObject(object); return llvm::json::Value(std::move(object)); } diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 693f35ee07e5f6..b6356630b72682 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -331,7 +331,7 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame); /// \return /// A "Scope" JSON object with that follows the formal JSON /// definition outlined by Microsoft. -llvm::json::Value CreateInstructionBreakpoint(lldb::SBBreakpoint &bp); +llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp); /// Create a "Thread" object for a LLDB thread object. /// diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index fb0c9f3b1a4a90..f28ff5dcb45742 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -4295,9 +4295,6 @@ void request_setInstructionBreakpoints(const llvm::json::Object &request) { } } - // Store removed instruction breakpoint list to delete them further. - std::vector removed_ibp; - // Iterate previouse active instruction breakpoint list. for (auto &prev_ibp : g_dap.instruction_breakpoints) { // Find previouse instruction breakpoint reference address in newly @@ -4308,20 +4305,17 @@ void request_setInstructionBreakpoints(const llvm::json::Object &request) { // Means delete removed breakpoint instance. if (inst_reference == request_ibp.end()) { g_dap.target.BreakpointDelete(prev_ibp.second.id); - removed_ibp.push_back(prev_ibp.first); + // Update Prev instruction breakpoint list. + g_dap.instruction_breakpoints.erase(prev_ibp.first); } else { // Instead of recreating breakpoint instance, update the breakpoint if // there are any conditional changes. prev_ibp.second.UpdateBreakpoint(inst_reference->second); request_ibp.erase(inst_reference); response_breakpoints.emplace_back( - CreateInstructionBreakpoint(prev_ibp.second.bp)); + CreateInstructionBreakpoint(&prev_ibp.second)); } } - // Update Prev instruction breakpoint list. - for (const auto &name : removed_ibp) { - g_dap.instruction_breakpoints.erase(name); - } for (auto &req_bpi : request_ibp) { // Add this breakpoint info to the response @@ -4329,7 +4323,7 @@ void request_setInstructionBreakpoints(const llvm::json::Object &request) { InstructionBreakpoint &new_bp = g_dap.instruction_breakpoints[req_bpi.first]; new_bp.SetInstructionBreakpoint(); - response_breakpoints.emplace_back(CreateInstructionBreakpoint(new_bp.bp)); + response_breakpoints.emplace_back(CreateInstructionBreakpoint(&new_bp)); } } From bac086e1a093280562bf52cc5ef706f8d612d7c0 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 26 Aug 2024 03:15:32 -0700 Subject: [PATCH 30/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Resolved code format issues. --- .../Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index c301d3482941de..709b7aff11d7f2 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -81,7 +81,10 @@ def verify_breakpoint_hit(self, breakpoint_ids): body = stopped_event["body"] if "reason" not in body: continue - if body["reason"] != "breakpoint" and body["reason"] != "instruction breakpoint": + if ( + body["reason"] != "breakpoint" + and body["reason"] != "instruction breakpoint" + ): continue if "description" not in body: continue From 5401fbeacc249bb69c8c57ae4a98a29627f5e993 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 26 Aug 2024 10:59:58 -0700 Subject: [PATCH 31/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Resolved review comments. --- lldb/tools/lldb-dap/InstructionBreakpoint.cpp | 9 +++++---- lldb/tools/lldb-dap/InstructionBreakpoint.h | 4 ++-- lldb/tools/lldb-dap/lldb-dap.cpp | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lldb/tools/lldb-dap/InstructionBreakpoint.cpp b/lldb/tools/lldb-dap/InstructionBreakpoint.cpp index d4d8db5c51ec15..de4f6f5d86717f 100644 --- a/lldb/tools/lldb-dap/InstructionBreakpoint.cpp +++ b/lldb/tools/lldb-dap/InstructionBreakpoint.cpp @@ -14,14 +14,15 @@ namespace lldb_dap { // Instruction Breakpoint InstructionBreakpoint::InstructionBreakpoint(const llvm::json::Object &obj) - : Breakpoint(obj), instructionReference(LLDB_INVALID_ADDRESS), id(0), + : Breakpoint(obj), instructionAddressReference(LLDB_INVALID_ADDRESS), id(0), offset(GetSigned(obj, "offset", 0)) { - GetString(obj, "instructionReference").getAsInteger(0, instructionReference); - instructionReference += offset; + GetString(obj, "instructionReference") + .getAsInteger(0, instructionAddressReference); + instructionAddressReference += offset; } void InstructionBreakpoint::SetInstructionBreakpoint() { - bp = g_dap.target.BreakpointCreateByAddress(instructionReference); + bp = g_dap.target.BreakpointCreateByAddress(instructionAddressReference); id = bp.GetID(); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/InstructionBreakpoint.h b/lldb/tools/lldb-dap/InstructionBreakpoint.h index a9b94f648aeab7..cf1516f46e9551 100644 --- a/lldb/tools/lldb-dap/InstructionBreakpoint.h +++ b/lldb/tools/lldb-dap/InstructionBreakpoint.h @@ -18,12 +18,12 @@ namespace lldb_dap { // Instruction Breakpoint struct InstructionBreakpoint : public Breakpoint { - lldb::addr_t instructionReference; + lldb::addr_t instructionAddressReference; int32_t id; int32_t offset; InstructionBreakpoint() - : Breakpoint(), instructionReference(LLDB_INVALID_ADDRESS), id(0), + : Breakpoint(), instructionAddressReference(LLDB_INVALID_ADDRESS), id(0), offset(0) {} InstructionBreakpoint(const llvm::json::Object &obj); diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index f28ff5dcb45742..90d6f1650a5c68 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -4291,13 +4291,13 @@ void request_setInstructionBreakpoints(const llvm::json::Object &request) { // Read instruction breakpoint request. InstructionBreakpoint inst_bp(*bp_obj); // Store them into map for reference. - request_ibp[inst_bp.instructionReference] = std::move(inst_bp); + request_ibp[inst_bp.instructionAddressReference] = std::move(inst_bp); } } - // Iterate previouse active instruction breakpoint list. + // Iterate previous active instruction breakpoint list. for (auto &prev_ibp : g_dap.instruction_breakpoints) { - // Find previouse instruction breakpoint reference address in newly + // Find previous instruction breakpoint reference address in newly // received instruction breakpoint list. auto inst_reference = request_ibp.find(prev_ibp.first); // Request for remove and delete the breakpoint, if the prev instruction From 3523cef1181e7f99e3af2df3334de27659205ed1 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 26 Aug 2024 11:02:19 -0700 Subject: [PATCH 32/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Resolved review comments. --- lldb/tools/lldb-dap/DAP.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index ce0f7903d6f88e..6012ee52110b73 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -998,7 +998,6 @@ void DAP::SetThreadFormat(llvm::StringRef format) { InstructionBreakpoint * DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) { - for (auto &bp : instruction_breakpoints) { if (bp.second.id == bp_id) return &bp.second; From ab68b22d579807e695a47b1256dce275aa42ddad Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Ellendula Date: Mon, 26 Aug 2024 11:06:37 -0700 Subject: [PATCH 33/33] [lldb-dap] Enabling instruction breakpoint support to lldb-dap. Resolved review comments. --- lldb/tools/lldb-dap/lldb-dap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 90d6f1650a5c68..c5c4b09f15622b 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -4364,7 +4364,7 @@ void RegisterRequestCallbacks() { g_dap.RegisterRequestCallback("threads", request_threads); g_dap.RegisterRequestCallback("variables", request_variables); g_dap.RegisterRequestCallback("disassemble", request_disassemble); - // Instruction breapoint request + // Instruction breakpoint request g_dap.RegisterRequestCallback("setInstructionBreakpoints", request_setInstructionBreakpoints); // Custom requests