Repository: pyocd/pyocd
Issue: starting gdb server with port 0 doesn't print actual port in use

## Description
I want to start multiple gdb servers parallel. Now only way is to detect free port before calling `pyocd-gdbserver` because if I use `-p 0` it doesn't give me actual port number in use.

## How to reproduce
run gdb server
```
$ pyocd-gdbserver -p 0
...
INFO:root:GDB server started at port:0
```
pyocd is running now but it just print `..port:0` -which are not valid. See below:

```
$ lsof -iTCP -sTCP:LISTEN
COMMAND     PID     USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python    13091 jva    3u  IPv4 0x73fd86420b3627b3      0t0  TCP localhost:krb524 (LISTEN)
Python    13091 jva    4u  IPv4 0x73fd86420b36f7b3      0t0  TCP localhost:51418 (LISTEN)
```


Changed files:
pyocd/debug/semihost.py, pyocd/gdbserver/gdb_socket.py, pyocd/gdbserver/gdbserver.py, pyocd/gdbserver/gdb_websocket.py

diff --git a/pyocd/debug/semihost.py b/pyocd/debug/semihost.py
index d4cdeb56..c42533f4 100644
--- a/pyocd/debug/semihost.py
+++ b/pyocd/debug/semihost.py
@@ -303,8 +303,9 @@ class TelnetSemihostIOHandler(SemihostIOHandler):
             self._wss_server = port_or_url
             self._abstract_socket = GDBWebSocket(self._wss_server)
         else:
-            self._port = port_or_url
             self._abstract_socket = GDBSocket(self._port, 4096)
+            self._abstract_socket.init()
+            self._port = self._abstract_socket.port
             if serve_local_only:
                 self._abstract_socket.host = 'localhost'
         self._buffer = bytearray()
@@ -320,7 +321,7 @@ class TelnetSemihostIOHandler(SemihostIOHandler):
         self._thread.join()
 
     def _server(self):
-        logging.info("Telnet: server started on port %s", str(self._port))
+        logging.info("Telnet server started on port %d", self._port)
         self.connected = None
         try:
             while not self._shutdown_event.is_set():
@@ -355,7 +356,7 @@ class TelnetSemihostIOHandler(SemihostIOHandler):
                     except socket.timeout:
                         pass
         finally:
-            self._abstract_socket.close()
+            self._abstract_socket.cleanup()
         logging.info("Telnet: server stopped")
 
     def write(self, fd, ptr, length):
diff --git a/pyocd/gdbserver/gdb_socket.py b/pyocd/gdbserver/gdb_socket.py
index 10224209..75c444ee 100644
--- a/pyocd/gdbserver/gdb_socket.py
+++ b/pyocd/gdbserver/gdb_socket.py
@@ -20,24 +20,31 @@ import socket, select
 class GDBSocket(object):
     def __init__(self, port, packet_size):
         self.packet_size = packet_size
-        self.s = None
+        self.listener = None
         self.conn = None
         self.port = port
         self.host = ''
 
     def init(self):
-        if self.s is None:
-            self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            self.s.bind((self.host, self.port))
-            self.s.listen(5)
+        if self.listener is None:
+            self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            self.listener.bind((self.host, self.port))
+            # If we were asked for port 0, that's treated as "auto".
+            # Read back the port - allows our user to find (and print) it,
+            # and means that if we're closed then re-opened, as happens when
+            # persisting for multiple sessions, we reuse the same port, which
+            # is convenient.
+            if self.port == 0:
+                self.port = self.listener.getsockname()[1]
+            self.listener.listen(1)
 
     def connect(self):
         self.conn = None
         self.init()
-        rr, _, _ = select.select([self.s], [], [], 0.5)
+        rr, _, _ = select.select([self.listener], [], [], 0.5)
         if rr:
-            self.conn, _ = self.s.accept()
+            self.conn, _ = self.listener.accept()
 
         return self.conn
 
@@ -48,17 +55,18 @@ class GDBSocket(object):
         return self.conn.send(data)
 
     def close(self):
+        return_value = None
         if self.conn is not None:
-            self.conn.close()
+            return_value = self.conn.close()
             self.conn = None
 
-        return_value = None
-        if self.s is not None:
-            return_value = self.s.close()
-            self.s = None
-
         return return_value
 
+    def cleanup(self):
+        self.close()
+        if self.listener is not None:
+            self.listener.close()
+            self.listener = None
 
     def set_blocking(self, blocking):
         self.conn.setblocking(blocking)
diff --git a/pyocd/gdbserver/gdb_websocket.py b/pyocd/gdbserver/gdb_websocket.py
index 7591a997..3a3a20f0 100644
--- a/pyocd/gdbserver/gdb_websocket.py
+++ b/pyocd/gdbserver/gdb_websocket.py
@@ -43,6 +43,9 @@ class GDBWebSocket(object):
     def close(self):
         return self.wss.close()
 
+    def cleanup(self):
+        return self.close()
+
     def set_blocking(self, blocking):
         if blocking != 0:
             self.wss.settimeout(None)
diff --git a/pyocd/gdbserver/gdbserver.py b/pyocd/gdbserver/gdbserver.py
index 77372fa7..b920a9b2 100644
--- a/pyocd/gdbserver/gdbserver.py
+++ b/pyocd/gdbserver/gdbserver.py
@@ -271,9 +271,13 @@ class GDBServer(threading.Thread):
             self.port = 0
             self.wss_server = port_urlWSS
         else:
-            self.port = port_urlWSS + self.core
+            self.port = port_urlWSS
+            if self.port != 0:
+                self.port += self.core
             self.wss_server = None
-        self.telnet_port = session.options.get('telnet_port', 4444) + self.core
+        self.telnet_port = session.options.get('telnet_port', 4444)
+        if self.telnet_port != 0:
+            self.telnet_port += self.core
         self.vector_catch = session.options.get('vector_catch', Target.CATCH_HARD_FAULT)
         self.target.set_vector_catch(self.vector_catch)
         self.step_into_interrupt = session.options.get('step_into_interrupt', False)
@@ -308,6 +312,9 @@ class GDBServer(threading.Thread):
         self.first_run_after_reset_or_flash = True
         if self.wss_server == None:
             self.abstract_socket = GDBSocket(self.port, self.packet_size)
+            self.abstract_socket.init()
+            # Read back bound port in case auto-assigned (port 0)
+            self.port = self.abstract_socket.port
             if self.serve_local_only:
                 self.abstract_socket.host = 'localhost'
         else:
@@ -411,6 +418,7 @@ class GDBServer(threading.Thread):
         if self.telnet_console:
             self.telnet_console.stop()
             self.telnet_console = None
+        self.abstract_socket.cleanup()
 
     def _cleanup_for_next_connection(self):
         self.non_stop = False
@@ -419,7 +427,7 @@ class GDBServer(threading.Thread):
         self.current_thread_id = 0
 
     def run(self):
-        self.log.info('GDB server started at port:%d', self.port)
+        self.log.info('GDB server started on port %d', self.port)
 
         while True:
             try: