[Lldb-commits] [lldb] [lldb-dap] Added "port" property to vscode "attach" command. (PR #91170)

Santhosh Kumar Ellendula via lldb-commits lldb-commits at lists.llvm.org
Mon May 6 01:28:29 PDT 2024


https://github.com/santhoshe447 updated https://github.com/llvm/llvm-project/pull/91170

>From 960351c9abf51f42d92604ac6297aa5b76ddfba5 Mon Sep 17 00:00:00 2001
From: Santhosh Kumar Ellendula <sellendu at hu-sellendu-hyd.qualcomm.com>
Date: Fri, 17 Nov 2023 15:09:10 +0530
Subject: [PATCH 1/4] [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 <sellendu at hu-sellendu-lv.qualcomm.com>
Date: Fri, 3 May 2024 02:47:05 -0700
Subject: [PATCH 2/4] [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 <port>".
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 97a7cc7af4da483a2a4fc034b2513557fe40a562 Mon Sep 17 00:00:00 2001
From: Santhosh Kumar Ellendula <quic_sellendu at quicinc.com>
Date: Mon, 6 May 2024 13:39:49 +0530
Subject: [PATCH 3/4] Update TestDAP_attachByPortNum.py

Removed dead code
---
 .../lldb-dap/attach/TestDAP_attachByPortNum.py     | 14 +++++---------
 1 file changed, 5 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 0c0ea6ca436166..c42bdb582ad7e2 100644
--- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py
+++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py
@@ -15,19 +15,16 @@
 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,
+        self.process = subprocess.Popen([server_path + program], shell=True,
                                         stdin=subprocess.PIPE,
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE)
@@ -66,7 +63,6 @@ def test_by_port(self):
                             '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

>From 21e34a498626ba198cd06ab801a7c0cab68d16b8 Mon Sep 17 00:00:00 2001
From: Santhosh Kumar Ellendula <quic_sellendu at quicinc.com>
Date: Mon, 6 May 2024 13:41:25 +0530
Subject: [PATCH 4/4] Update lldbtest.py

Changed to getBuiltinServerTool from getBuiltServerTool
---
 lldb/packages/Python/lldbsuite/test/lldbtest.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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)



More information about the lldb-commits mailing list