180 lines · python
1import logging2import os3import os.path4import random5 6import lldb7from lldbsuite.test.lldbtest import *8from lldbsuite.test.gdbclientutils import *9import lldbgdbserverutils10from lldbsuite.support import seven11 12 13class GDBProxyTestBase(TestBase):14 """15 Base class for gdbserver proxy tests.16 17 This class will setup and start a mock GDB server for the test to use.18 It pases through requests to a regular lldb-server/debugserver and19 forwards replies back to the LLDB under test.20 """21 22 """The gdbserver that we implement."""23 server = None24 """The inner lldb-server/debugserver process that we proxy requests into."""25 monitor_server = None26 monitor_sock = None27 28 server_socket_class = TCPServerSocket29 30 DEFAULT_TIMEOUT = 20 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)31 32 _verbose_log_handler = None33 _log_formatter = logging.Formatter(fmt="%(asctime)-15s %(levelname)-8s %(message)s")34 35 def setUpBaseLogging(self):36 self.logger = logging.getLogger(__name__)37 38 self.logger.propagate = False39 self.logger.setLevel(logging.DEBUG)40 41 # log all warnings to stderr42 self._stderr_log_handler = logging.StreamHandler()43 self._stderr_log_handler.setLevel(44 logging.DEBUG if self.TraceOn() else logging.WARNING45 )46 self._stderr_log_handler.setFormatter(self._log_formatter)47 self.logger.addHandler(self._stderr_log_handler)48 49 def setUp(self):50 TestBase.setUp(self)51 52 self.setUpBaseLogging()53 54 if self.isVerboseLoggingRequested():55 # If requested, full logs go to a log file56 log_file_name = self.getLogBasenameForCurrentTest() + "-proxy.log"57 self._verbose_log_handler = logging.FileHandler(log_file_name)58 self._verbose_log_handler.setFormatter(self._log_formatter)59 self._verbose_log_handler.setLevel(logging.DEBUG)60 self.logger.addHandler(self._verbose_log_handler)61 62 if lldbplatformutil.getPlatform() == "macosx":63 self.debug_monitor_exe = lldbgdbserverutils.get_debugserver_exe()64 self.debug_monitor_extra_args = []65 else:66 self.debug_monitor_exe = lldbgdbserverutils.get_lldb_server_exe()67 self.debug_monitor_extra_args = ["gdbserver"]68 self.assertIsNotNone(self.debug_monitor_exe)69 70 self.server = MockGDBServer(self.server_socket_class())71 self.server.responder = self72 73 def tearDown(self):74 # TestBase.tearDown will kill the process, but we need to kill it early75 # so its client connection closes and we can stop the server before76 # finally calling the base tearDown.77 if self.process() is not None:78 self.process().Kill()79 self.server.stop()80 81 self.logger.removeHandler(self._verbose_log_handler)82 self._verbose_log_handler = None83 self.logger.removeHandler(self._stderr_log_handler)84 self._stderr_log_handler = None85 86 TestBase.tearDown(self)87 88 def isVerboseLoggingRequested(self):89 # We will report our detailed logs if the user requested that the "gdb-remote" channel is90 # logged.91 return any(("gdb-remote" in channel) for channel in lldbtest_config.channels)92 93 def connect(self, target):94 """95 Create a process by connecting to the mock GDB server.96 """97 self.prep_debug_monitor_and_inferior()98 self.server.start()99 100 listener = self.dbg.GetListener()101 error = lldb.SBError()102 process = target.ConnectRemote(103 listener, self.server.get_connect_url(), "gdb-remote", error104 )105 self.assertTrue(error.Success(), error.description)106 self.assertTrue(process, PROCESS_IS_VALID)107 return process108 109 def prep_debug_monitor_and_inferior(self):110 inferior_exe_path = self.getBuildArtifact("a.out")111 self.connect_to_debug_monitor([inferior_exe_path])112 self.assertIsNotNone(self.monitor_server)113 self.initial_handshake()114 115 def initial_handshake(self):116 self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))117 reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())118 self.assertEqual(reply, "+")119 self.monitor_server.send_packet(seven.bitcast_to_bytes("QStartNoAckMode"))120 reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())121 self.assertEqual(reply, "+")122 reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())123 self.assertEqual(reply, "OK")124 self.monitor_server.set_validate_checksums(False)125 self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))126 reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())127 self.assertEqual(reply, "+")128 129 def get_debug_monitor_command_line_args(self, connect_address, launch_args):130 return (131 self.debug_monitor_extra_args132 + ["--reverse-connect", connect_address]133 + launch_args134 )135 136 def launch_debug_monitor(self, launch_args):137 family, type, proto, _, addr = socket.getaddrinfo(138 "localhost", 0, proto=socket.IPPROTO_TCP139 )[0]140 sock = socket.socket(family, type, proto)141 sock.settimeout(self.DEFAULT_TIMEOUT)142 sock.bind(addr)143 sock.listen(1)144 addr = sock.getsockname()145 connect_address = "[{}]:{}".format(*addr)146 147 commandline_args = self.get_debug_monitor_command_line_args(148 connect_address, launch_args149 )150 151 # Start the server.152 self.logger.info(f"Spawning monitor {commandline_args}")153 monitor_process = self.spawnSubprocess(154 self.debug_monitor_exe, commandline_args, install_remote=False155 )156 self.assertIsNotNone(monitor_process)157 158 self.monitor_sock = sock.accept()[0]159 self.monitor_sock.settimeout(self.DEFAULT_TIMEOUT)160 return monitor_process161 162 def connect_to_debug_monitor(self, launch_args):163 monitor_process = self.launch_debug_monitor(launch_args)164 # Turn off checksum validation because debugserver does not produce165 # correct checksums.166 self.monitor_server = lldbgdbserverutils.Server(167 self.monitor_sock, monitor_process168 )169 170 def respond(self, packet):171 """Subclasses can override this to change how packets are handled."""172 return self.pass_through(packet)173 174 def pass_through(self, packet):175 self.logger.info(f"Sending packet {packet}")176 self.monitor_server.send_packet(seven.bitcast_to_bytes(packet))177 reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())178 self.logger.info(f"Received reply {reply}")179 return reply180