labath created this revision.
labath added reviewers: amccarth, stella.stamenova.
labath requested review of this revision.
Herald added a project: LLDB.

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.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D96260

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

Index: lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py
===================================================================
--- lldb/test/API/tools/lldb-server/commandline/TestGdbRemoteConnection.py
+++ 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 @@
         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 @@
 
         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 @@
         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)
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
  • [Lldb-commits] [PATCH] D9626... Pavel Labath via Phabricator via lldb-commits

Reply via email to