[Lldb-commits] [lldb] 85f025e - [lldb/test] Test lldb-server named pipe functionality on windows

Pavel Labath via lldb-commits lldb-commits at lists.llvm.org
Tue Feb 16 06:47:55 PST 2021


Author: Pavel Labath
Date: 2021-02-16T15:47:39+01:00
New Revision: 85f025e5b33d148808177427eebca4cc14f93079

URL: https://github.com/llvm/llvm-project/commit/85f025e5b33d148808177427eebca4cc14f93079
DIFF: https://github.com/llvm/llvm-project/commit/85f025e5b33d148808177427eebca4cc14f93079.diff

LOG: [lldb/test] Test lldb-server named pipe functionality on windows

lldb-server can use a named pipe to communicate the port number it is
listening on. This windows bits of this are already implemented, but we
did not have a test for that, most likely because python does not have
native pipe functionality.

This patch implements the windows bits necessary to test this. I'm using
the ctypes package to call the native APIs directly to avoid a
dependency to non-standard python packages. This introduces some amount
of boilerplate, but our named pipe use case is fairly limited, so we
should not end up needing to wrap large chunks of windows APIs.

Surprisingly to changes to lldb-server were needed to make the test
pass.

Differential Revision: https://reviews.llvm.org/D96260

Added: 
    

Modified: 
    lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py

Removed: 
    


################################################################################
diff  --git a/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py b/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py
index 5a7220f76fe0..c9799d1976cb 100644
--- a/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py
+++ b/lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py
@@ -5,6 +5,122 @@
 import socket
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
+import lldbsuite.test.lldbplatformutil
+import random
+
+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)
 
 
 class TestGdbRemoteConnection(gdbremote_testcase.GdbRemoteTestCaseBase):
@@ -19,7 +135,6 @@ def test_reverse_connect_llgs(self):
         self.do_handshake(self.sock)
 
     @skipIfRemote
-    @skipIfWindows
     def test_named_pipe_llgs(self):
         family, type, proto, _, addr = socket.getaddrinfo(
             self.stub_hostname, 0, proto=socket.IPPROTO_TCP)[0]
@@ -28,16 +143,9 @@ def test_named_pipe_llgs(self):
 
         self.addTearDownHook(lambda: self.sock.close())
 
-        named_pipe_path = self.getBuildArtifact("stub_port_number")
+        pipe = Pipe(self.getBuildDir())
 
-        # Create the named pipe.
-        os.mkfifo(named_pipe_path)
-
-        # Open the read side of the pipe in non-blocking mode.  This will
-        # return right away, ready or not.
-        named_pipe_fd = os.open(named_pipe_path, os.O_RDONLY | os.O_NONBLOCK)
-
-        self.addTearDownHook(lambda: os.close(named_pipe_fd))
+        self.addTearDownHook(lambda: pipe.close())
 
         args = self.debug_monitor_extra_args
         if lldb.remote_platform:
@@ -45,19 +153,15 @@ def test_named_pipe_llgs(self):
         else:
             args += ["localhost:0"]
 
-        args += ["--named-pipe", named_pipe_path]
+        args += ["--named-pipe", pipe.name]
 
         server = self.spawnSubprocess(
             self.debug_monitor_exe,
             args,
             install_remote=False)
 
-        (ready_readers, _, _) = select.select(
-            [named_pipe_fd], [], [], self.DEFAULT_TIMEOUT)
-        self.assertIsNotNone(
-            ready_readers,
-            "write side of pipe has not written anything - stub isn't writing to pipe.")
-        port = os.read(named_pipe_fd, 10)
+        pipe.finish_connection(self.DEFAULT_TIMEOUT)
+        port = pipe.read(10, self.DEFAULT_TIMEOUT)
         # Trim null byte, convert to int
         addr = (addr[0], int(port[:-1]))
         self.sock.connect(addr)


        


More information about the lldb-commits mailing list