1061da546Spatrick"""Module for supporting unit testing of the lldb-server debug monitor exe. 2061da546Spatrick""" 3061da546Spatrick 4061da546Spatrickfrom __future__ import division, print_function 5061da546Spatrick 6*be691f3bSpatrickimport binascii 7061da546Spatrickimport os 8061da546Spatrickimport os.path 9061da546Spatrickimport platform 10061da546Spatrickimport re 11061da546Spatrickimport six 12*be691f3bSpatrickimport socket 13061da546Spatrickimport subprocess 14*be691f3bSpatrickfrom lldbsuite.support import seven 15061da546Spatrickfrom lldbsuite.test.lldbtest import * 16*be691f3bSpatrickfrom lldbsuite.test import configuration 17*be691f3bSpatrickfrom textwrap import dedent 18*be691f3bSpatrickimport shutil 19061da546Spatrick 20*be691f3bSpatrickdef _get_support_exe(basename): 21*be691f3bSpatrick support_dir = lldb.SBHostOS.GetLLDBPath(lldb.ePathTypeSupportExecutableDir) 22061da546Spatrick 23*be691f3bSpatrick return shutil.which(basename, path=support_dir.GetDirectory()) 24061da546Spatrick 25061da546Spatrick 26061da546Spatrickdef get_lldb_server_exe(): 27061da546Spatrick """Return the lldb-server exe path. 28061da546Spatrick 29061da546Spatrick Returns: 30061da546Spatrick A path to the lldb-server exe if it is found to exist; otherwise, 31061da546Spatrick returns None. 32061da546Spatrick """ 33061da546Spatrick 34*be691f3bSpatrick return _get_support_exe("lldb-server") 35061da546Spatrick 36061da546Spatrick 37061da546Spatrickdef get_debugserver_exe(): 38061da546Spatrick """Return the debugserver exe path. 39061da546Spatrick 40061da546Spatrick Returns: 41061da546Spatrick A path to the debugserver exe if it is found to exist; otherwise, 42061da546Spatrick returns None. 43061da546Spatrick """ 44*be691f3bSpatrick if configuration.arch and configuration.arch == "x86_64" and \ 45*be691f3bSpatrick platform.machine().startswith("arm64"): 46*be691f3bSpatrick return '/Library/Apple/usr/libexec/oah/debugserver' 47061da546Spatrick 48*be691f3bSpatrick return _get_support_exe("debugserver") 49061da546Spatrick 50061da546Spatrick_LOG_LINE_REGEX = re.compile(r'^(lldb-server|debugserver)\s+<\s*(\d+)>' + 51061da546Spatrick '\s+(read|send)\s+packet:\s+(.+)$') 52061da546Spatrick 53061da546Spatrick 54061da546Spatrickdef _is_packet_lldb_gdbserver_input(packet_type, llgs_input_is_read): 55061da546Spatrick """Return whether a given packet is input for lldb-gdbserver. 56061da546Spatrick 57061da546Spatrick Args: 58061da546Spatrick packet_type: a string indicating 'send' or 'receive', from a 59061da546Spatrick gdbremote packet protocol log. 60061da546Spatrick 61061da546Spatrick llgs_input_is_read: true if lldb-gdbserver input (content sent to 62061da546Spatrick lldb-gdbserver) is listed as 'read' or 'send' in the packet 63061da546Spatrick log entry. 64061da546Spatrick 65061da546Spatrick Returns: 66061da546Spatrick True if the packet should be considered input for lldb-gdbserver; False 67061da546Spatrick otherwise. 68061da546Spatrick """ 69061da546Spatrick if packet_type == 'read': 70061da546Spatrick # when llgs is the read side, then a read packet is meant for 71061da546Spatrick # input to llgs (when captured from the llgs/debugserver exe). 72061da546Spatrick return llgs_input_is_read 73061da546Spatrick elif packet_type == 'send': 74061da546Spatrick # when llgs is the send side, then a send packet is meant to 75061da546Spatrick # be input to llgs (when captured from the lldb exe). 76061da546Spatrick return not llgs_input_is_read 77061da546Spatrick else: 78061da546Spatrick # don't understand what type of packet this is 79061da546Spatrick raise "Unknown packet type: {}".format(packet_type) 80061da546Spatrick 81061da546Spatrick 82061da546Spatrickdef handle_O_packet(context, packet_contents, logger): 83061da546Spatrick """Handle O packets.""" 84061da546Spatrick if (not packet_contents) or (len(packet_contents) < 1): 85061da546Spatrick return False 86061da546Spatrick elif packet_contents[0] != "O": 87061da546Spatrick return False 88061da546Spatrick elif packet_contents == "OK": 89061da546Spatrick return False 90061da546Spatrick 91061da546Spatrick new_text = gdbremote_hex_decode_string(packet_contents[1:]) 92061da546Spatrick context["O_content"] += new_text 93061da546Spatrick context["O_count"] += 1 94061da546Spatrick 95061da546Spatrick if logger: 96061da546Spatrick logger.debug( 97061da546Spatrick "text: new \"{}\", cumulative: \"{}\"".format( 98061da546Spatrick new_text, context["O_content"])) 99061da546Spatrick 100061da546Spatrick return True 101061da546Spatrick 102061da546Spatrick_STRIP_CHECKSUM_REGEX = re.compile(r'#[0-9a-fA-F]{2}$') 103061da546Spatrick_STRIP_COMMAND_PREFIX_REGEX = re.compile(r"^\$") 104061da546Spatrick_STRIP_COMMAND_PREFIX_M_REGEX = re.compile(r"^\$m") 105061da546Spatrick 106061da546Spatrick 107061da546Spatrickdef assert_packets_equal(asserter, actual_packet, expected_packet): 108061da546Spatrick # strip off the checksum digits of the packet. When we're in 109061da546Spatrick # no-ack mode, the # checksum is ignored, and should not be cause 110061da546Spatrick # for a mismatched packet. 111061da546Spatrick actual_stripped = _STRIP_CHECKSUM_REGEX.sub('', actual_packet) 112061da546Spatrick expected_stripped = _STRIP_CHECKSUM_REGEX.sub('', expected_packet) 113061da546Spatrick asserter.assertEqual(actual_stripped, expected_stripped) 114061da546Spatrick 115061da546Spatrick 116061da546Spatrickdef expect_lldb_gdbserver_replay( 117061da546Spatrick asserter, 118*be691f3bSpatrick server, 119061da546Spatrick test_sequence, 120061da546Spatrick timeout_seconds, 121061da546Spatrick logger=None): 122061da546Spatrick """Replay socket communication with lldb-gdbserver and verify responses. 123061da546Spatrick 124061da546Spatrick Args: 125061da546Spatrick asserter: the object providing assertEqual(first, second, msg=None), e.g. TestCase instance. 126061da546Spatrick 127061da546Spatrick test_sequence: a GdbRemoteTestSequence instance that describes 128061da546Spatrick the messages sent to the gdb remote and the responses 129061da546Spatrick expected from it. 130061da546Spatrick 131061da546Spatrick timeout_seconds: any response taking more than this number of 132061da546Spatrick seconds will cause an exception to be raised. 133061da546Spatrick 134061da546Spatrick logger: a Python logger instance. 135061da546Spatrick 136061da546Spatrick Returns: 137061da546Spatrick The context dictionary from running the given gdbremote 138061da546Spatrick protocol sequence. This will contain any of the capture 139061da546Spatrick elements specified to any GdbRemoteEntry instances in 140061da546Spatrick test_sequence. 141061da546Spatrick 142061da546Spatrick The context will also contain an entry, context["O_content"] 143061da546Spatrick which contains the text from the inferior received via $O 144061da546Spatrick packets. $O packets should not attempt to be matched 145061da546Spatrick directly since they are not entirely deterministic as to 146061da546Spatrick how many arrive and how much text is in each one. 147061da546Spatrick 148061da546Spatrick context["O_count"] will contain an integer of the number of 149061da546Spatrick O packets received. 150061da546Spatrick """ 151061da546Spatrick 152061da546Spatrick # Ensure we have some work to do. 153061da546Spatrick if len(test_sequence.entries) < 1: 154061da546Spatrick return {} 155061da546Spatrick 156061da546Spatrick context = {"O_count": 0, "O_content": ""} 157*be691f3bSpatrick 158061da546Spatrick # Grab the first sequence entry. 159061da546Spatrick sequence_entry = test_sequence.entries.pop(0) 160061da546Spatrick 161061da546Spatrick # While we have an active sequence entry, send messages 162061da546Spatrick # destined for the stub and collect/match/process responses 163061da546Spatrick # expected from the stub. 164061da546Spatrick while sequence_entry: 165061da546Spatrick if sequence_entry.is_send_to_remote(): 166061da546Spatrick # This is an entry to send to the remote debug monitor. 167061da546Spatrick send_packet = sequence_entry.get_send_packet() 168061da546Spatrick if logger: 169061da546Spatrick if len(send_packet) == 1 and send_packet[0] == chr(3): 170061da546Spatrick packet_desc = "^C" 171061da546Spatrick else: 172061da546Spatrick packet_desc = send_packet 173061da546Spatrick logger.info( 174061da546Spatrick "sending packet to remote: {}".format(packet_desc)) 175*be691f3bSpatrick server.send_raw(send_packet.encode()) 176061da546Spatrick else: 177061da546Spatrick # This is an entry expecting to receive content from the remote 178061da546Spatrick # debug monitor. 179061da546Spatrick 180061da546Spatrick # We'll pull from (and wait on) the queue appropriate for the type of matcher. 181061da546Spatrick # We keep separate queues for process output (coming from non-deterministic 182061da546Spatrick # $O packet division) and for all other packets. 183*be691f3bSpatrick try: 184061da546Spatrick if sequence_entry.is_output_matcher(): 185061da546Spatrick # Grab next entry from the output queue. 186*be691f3bSpatrick content = server.get_raw_output_packet() 187061da546Spatrick else: 188*be691f3bSpatrick content = server.get_raw_normal_packet() 189*be691f3bSpatrick content = seven.bitcast_to_string(content) 190*be691f3bSpatrick except socket.timeout: 191*be691f3bSpatrick asserter.fail( 192*be691f3bSpatrick "timed out while waiting for '{}':\n{}".format(sequence_entry, server)) 193061da546Spatrick 194061da546Spatrick # Give the sequence entry the opportunity to match the content. 195061da546Spatrick # Output matchers might match or pass after more output accumulates. 196061da546Spatrick # Other packet types generally must match. 197061da546Spatrick asserter.assertIsNotNone(content) 198061da546Spatrick context = sequence_entry.assert_match( 199061da546Spatrick asserter, content, context=context) 200061da546Spatrick 201061da546Spatrick # Move on to next sequence entry as needed. Some sequence entries support executing multiple 202061da546Spatrick # times in different states (for looping over query/response 203061da546Spatrick # packets). 204061da546Spatrick if sequence_entry.is_consumed(): 205061da546Spatrick if len(test_sequence.entries) > 0: 206061da546Spatrick sequence_entry = test_sequence.entries.pop(0) 207061da546Spatrick else: 208061da546Spatrick sequence_entry = None 209061da546Spatrick 210061da546Spatrick # Fill in the O_content entries. 211061da546Spatrick context["O_count"] = 1 212*be691f3bSpatrick context["O_content"] = server.consume_accumulated_output() 213061da546Spatrick 214061da546Spatrick return context 215061da546Spatrick 216061da546Spatrick 217061da546Spatrickdef gdbremote_hex_encode_string(str): 218061da546Spatrick output = '' 219061da546Spatrick for c in str: 220061da546Spatrick output += '{0:02x}'.format(ord(c)) 221061da546Spatrick return output 222061da546Spatrick 223061da546Spatrick 224061da546Spatrickdef gdbremote_hex_decode_string(str): 225061da546Spatrick return str.decode("hex") 226061da546Spatrick 227061da546Spatrick 228061da546Spatrickdef gdbremote_packet_encode_string(str): 229061da546Spatrick checksum = 0 230061da546Spatrick for c in str: 231061da546Spatrick checksum += ord(c) 232061da546Spatrick return '$' + str + '#{0:02x}'.format(checksum % 256) 233061da546Spatrick 234061da546Spatrick 235061da546Spatrickdef build_gdbremote_A_packet(args_list): 236061da546Spatrick """Given a list of args, create a properly-formed $A packet containing each arg. 237061da546Spatrick """ 238061da546Spatrick payload = "A" 239061da546Spatrick 240061da546Spatrick # build the arg content 241061da546Spatrick arg_index = 0 242061da546Spatrick for arg in args_list: 243061da546Spatrick # Comma-separate the args. 244061da546Spatrick if arg_index > 0: 245061da546Spatrick payload += ',' 246061da546Spatrick 247061da546Spatrick # Hex-encode the arg. 248061da546Spatrick hex_arg = gdbremote_hex_encode_string(arg) 249061da546Spatrick 250061da546Spatrick # Build the A entry. 251061da546Spatrick payload += "{},{},{}".format(len(hex_arg), arg_index, hex_arg) 252061da546Spatrick 253061da546Spatrick # Next arg index, please. 254061da546Spatrick arg_index += 1 255061da546Spatrick 256061da546Spatrick # return the packetized payload 257061da546Spatrick return gdbremote_packet_encode_string(payload) 258061da546Spatrick 259061da546Spatrick 260061da546Spatrickdef parse_reg_info_response(response_packet): 261061da546Spatrick if not response_packet: 262061da546Spatrick raise Exception("response_packet cannot be None") 263061da546Spatrick 264061da546Spatrick # Strip off prefix $ and suffix #xx if present. 265061da546Spatrick response_packet = _STRIP_COMMAND_PREFIX_REGEX.sub("", response_packet) 266061da546Spatrick response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet) 267061da546Spatrick 268061da546Spatrick # Build keyval pairs 269061da546Spatrick values = {} 270061da546Spatrick for kv in response_packet.split(";"): 271061da546Spatrick if len(kv) < 1: 272061da546Spatrick continue 273061da546Spatrick (key, val) = kv.split(':') 274061da546Spatrick values[key] = val 275061da546Spatrick 276061da546Spatrick return values 277061da546Spatrick 278061da546Spatrick 279061da546Spatrickdef parse_threadinfo_response(response_packet): 280061da546Spatrick if not response_packet: 281061da546Spatrick raise Exception("response_packet cannot be None") 282061da546Spatrick 283061da546Spatrick # Strip off prefix $ and suffix #xx if present. 284061da546Spatrick response_packet = _STRIP_COMMAND_PREFIX_M_REGEX.sub("", response_packet) 285061da546Spatrick response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet) 286061da546Spatrick 287061da546Spatrick # Return list of thread ids 288061da546Spatrick return [int(thread_id_hex, 16) for thread_id_hex in response_packet.split( 289061da546Spatrick ",") if len(thread_id_hex) > 0] 290061da546Spatrick 291061da546Spatrick 292061da546Spatrickdef unpack_endian_binary_string(endian, value_string): 293061da546Spatrick """Unpack a gdb-remote binary (post-unescaped, i.e. not escaped) response to an unsigned int given endianness of the inferior.""" 294061da546Spatrick if not endian: 295061da546Spatrick raise Exception("endian cannot be None") 296061da546Spatrick if not value_string or len(value_string) < 1: 297061da546Spatrick raise Exception("value_string cannot be None or empty") 298061da546Spatrick 299061da546Spatrick if endian == 'little': 300061da546Spatrick value = 0 301061da546Spatrick i = 0 302061da546Spatrick while len(value_string) > 0: 303061da546Spatrick value += (ord(value_string[0]) << i) 304061da546Spatrick value_string = value_string[1:] 305061da546Spatrick i += 8 306061da546Spatrick return value 307061da546Spatrick elif endian == 'big': 308061da546Spatrick value = 0 309061da546Spatrick while len(value_string) > 0: 310061da546Spatrick value = (value << 8) + ord(value_string[0]) 311061da546Spatrick value_string = value_string[1:] 312061da546Spatrick return value 313061da546Spatrick else: 314061da546Spatrick # pdp is valid but need to add parse code once needed. 315061da546Spatrick raise Exception("unsupported endian:{}".format(endian)) 316061da546Spatrick 317061da546Spatrick 318061da546Spatrickdef unpack_register_hex_unsigned(endian, value_string): 319061da546Spatrick """Unpack a gdb-remote $p-style response to an unsigned int given endianness of inferior.""" 320061da546Spatrick if not endian: 321061da546Spatrick raise Exception("endian cannot be None") 322061da546Spatrick if not value_string or len(value_string) < 1: 323061da546Spatrick raise Exception("value_string cannot be None or empty") 324061da546Spatrick 325061da546Spatrick if endian == 'little': 326061da546Spatrick value = 0 327061da546Spatrick i = 0 328061da546Spatrick while len(value_string) > 0: 329061da546Spatrick value += (int(value_string[0:2], 16) << i) 330061da546Spatrick value_string = value_string[2:] 331061da546Spatrick i += 8 332061da546Spatrick return value 333061da546Spatrick elif endian == 'big': 334061da546Spatrick return int(value_string, 16) 335061da546Spatrick else: 336061da546Spatrick # pdp is valid but need to add parse code once needed. 337061da546Spatrick raise Exception("unsupported endian:{}".format(endian)) 338061da546Spatrick 339061da546Spatrick 340061da546Spatrickdef pack_register_hex(endian, value, byte_size=None): 341061da546Spatrick """Unpack a gdb-remote $p-style response to an unsigned int given endianness of inferior.""" 342061da546Spatrick if not endian: 343061da546Spatrick raise Exception("endian cannot be None") 344061da546Spatrick 345061da546Spatrick if endian == 'little': 346061da546Spatrick # Create the litt-endian return value. 347061da546Spatrick retval = "" 348061da546Spatrick while value != 0: 349061da546Spatrick retval = retval + "{:02x}".format(value & 0xff) 350061da546Spatrick value = value >> 8 351061da546Spatrick if byte_size: 352061da546Spatrick # Add zero-fill to the right/end (MSB side) of the value. 353061da546Spatrick retval += "00" * (byte_size - len(retval) // 2) 354061da546Spatrick return retval 355061da546Spatrick 356061da546Spatrick elif endian == 'big': 357061da546Spatrick retval = "" 358061da546Spatrick while value != 0: 359061da546Spatrick retval = "{:02x}".format(value & 0xff) + retval 360061da546Spatrick value = value >> 8 361061da546Spatrick if byte_size: 362061da546Spatrick # Add zero-fill to the left/front (MSB side) of the value. 363061da546Spatrick retval = ("00" * (byte_size - len(retval) // 2)) + retval 364061da546Spatrick return retval 365061da546Spatrick 366061da546Spatrick else: 367061da546Spatrick # pdp is valid but need to add parse code once needed. 368061da546Spatrick raise Exception("unsupported endian:{}".format(endian)) 369061da546Spatrick 370061da546Spatrick 371061da546Spatrickclass GdbRemoteEntryBase(object): 372061da546Spatrick 373061da546Spatrick def is_output_matcher(self): 374061da546Spatrick return False 375061da546Spatrick 376061da546Spatrick 377061da546Spatrickclass GdbRemoteEntry(GdbRemoteEntryBase): 378061da546Spatrick 379061da546Spatrick def __init__( 380061da546Spatrick self, 381061da546Spatrick is_send_to_remote=True, 382061da546Spatrick exact_payload=None, 383061da546Spatrick regex=None, 384*be691f3bSpatrick capture=None): 385061da546Spatrick """Create an entry representing one piece of the I/O to/from a gdb remote debug monitor. 386061da546Spatrick 387061da546Spatrick Args: 388061da546Spatrick 389061da546Spatrick is_send_to_remote: True if this entry is a message to be 390061da546Spatrick sent to the gdbremote debug monitor; False if this 391061da546Spatrick entry represents text to be matched against the reply 392061da546Spatrick from the gdbremote debug monitor. 393061da546Spatrick 394061da546Spatrick exact_payload: if not None, then this packet is an exact 395061da546Spatrick send (when sending to the remote) or an exact match of 396061da546Spatrick the response from the gdbremote. The checksums are 397061da546Spatrick ignored on exact match requests since negotiation of 398061da546Spatrick no-ack makes the checksum content essentially 399061da546Spatrick undefined. 400061da546Spatrick 401*be691f3bSpatrick regex: currently only valid for receives from gdbremote. When 402*be691f3bSpatrick specified (and only if exact_payload is None), indicates the 403*be691f3bSpatrick gdbremote response must match the given regex. Match groups in 404*be691f3bSpatrick the regex can be used for the matching portion (see capture 405*be691f3bSpatrick arg). It is perfectly valid to have just a regex arg without a 406*be691f3bSpatrick capture arg. This arg only makes sense if exact_payload is not 407061da546Spatrick specified. 408061da546Spatrick 409061da546Spatrick capture: if specified, is a dictionary of regex match 410061da546Spatrick group indices (should start with 1) to variable names 411061da546Spatrick that will store the capture group indicated by the 412061da546Spatrick index. For example, {1:"thread_id"} will store capture 413061da546Spatrick group 1's content in the context dictionary where 414061da546Spatrick "thread_id" is the key and the match group value is 415*be691f3bSpatrick the value. This arg only makes sense when regex is specified. 416061da546Spatrick """ 417061da546Spatrick self._is_send_to_remote = is_send_to_remote 418061da546Spatrick self.exact_payload = exact_payload 419061da546Spatrick self.regex = regex 420061da546Spatrick self.capture = capture 421061da546Spatrick 422061da546Spatrick def is_send_to_remote(self): 423061da546Spatrick return self._is_send_to_remote 424061da546Spatrick 425061da546Spatrick def is_consumed(self): 426061da546Spatrick # For now, all packets are consumed after first use. 427061da546Spatrick return True 428061da546Spatrick 429061da546Spatrick def get_send_packet(self): 430061da546Spatrick if not self.is_send_to_remote(): 431061da546Spatrick raise Exception( 432061da546Spatrick "get_send_packet() called on GdbRemoteEntry that is not a send-to-remote packet") 433061da546Spatrick if not self.exact_payload: 434061da546Spatrick raise Exception( 435061da546Spatrick "get_send_packet() called on GdbRemoteEntry but it doesn't have an exact payload") 436061da546Spatrick return self.exact_payload 437061da546Spatrick 438061da546Spatrick def _assert_exact_payload_match(self, asserter, actual_packet): 439061da546Spatrick assert_packets_equal(asserter, actual_packet, self.exact_payload) 440061da546Spatrick return None 441061da546Spatrick 442061da546Spatrick def _assert_regex_match(self, asserter, actual_packet, context): 443061da546Spatrick # Ensure the actual packet matches from the start of the actual packet. 444061da546Spatrick match = self.regex.match(actual_packet) 445061da546Spatrick if not match: 446061da546Spatrick asserter.fail( 447061da546Spatrick "regex '{}' failed to match against content '{}'".format( 448061da546Spatrick self.regex.pattern, actual_packet)) 449061da546Spatrick 450061da546Spatrick if self.capture: 451061da546Spatrick # Handle captures. 452061da546Spatrick for group_index, var_name in list(self.capture.items()): 453061da546Spatrick capture_text = match.group(group_index) 454061da546Spatrick # It is okay for capture text to be None - which it will be if it is a group that can match nothing. 455061da546Spatrick # The user must be okay with it since the regex itself matched 456061da546Spatrick # above. 457061da546Spatrick context[var_name] = capture_text 458061da546Spatrick 459061da546Spatrick return context 460061da546Spatrick 461061da546Spatrick def assert_match(self, asserter, actual_packet, context=None): 462061da546Spatrick # This only makes sense for matching lines coming from the 463061da546Spatrick # remote debug monitor. 464061da546Spatrick if self.is_send_to_remote(): 465061da546Spatrick raise Exception( 466061da546Spatrick "Attempted to match a packet being sent to the remote debug monitor, doesn't make sense.") 467061da546Spatrick 468061da546Spatrick # Create a new context if needed. 469061da546Spatrick if not context: 470061da546Spatrick context = {} 471061da546Spatrick 472061da546Spatrick # If this is an exact payload, ensure they match exactly, 473061da546Spatrick # ignoring the packet checksum which is optional for no-ack 474061da546Spatrick # mode. 475061da546Spatrick if self.exact_payload: 476061da546Spatrick self._assert_exact_payload_match(asserter, actual_packet) 477061da546Spatrick return context 478061da546Spatrick elif self.regex: 479061da546Spatrick return self._assert_regex_match(asserter, actual_packet, context) 480061da546Spatrick else: 481061da546Spatrick raise Exception( 482061da546Spatrick "Don't know how to match a remote-sent packet when exact_payload isn't specified.") 483061da546Spatrick 484061da546Spatrick 485061da546Spatrickclass MultiResponseGdbRemoteEntry(GdbRemoteEntryBase): 486061da546Spatrick """Represents a query/response style packet. 487061da546Spatrick 488061da546Spatrick Assumes the first item is sent to the gdb remote. 489061da546Spatrick An end sequence regex indicates the end of the query/response 490061da546Spatrick packet sequence. All responses up through (but not including) the 491061da546Spatrick end response are stored in a context variable. 492061da546Spatrick 493061da546Spatrick Settings accepted from params: 494061da546Spatrick 495061da546Spatrick next_query or query: required. The typical query packet without the $ prefix or #xx suffix. 496061da546Spatrick If there is a special first packet to start the iteration query, see the 497061da546Spatrick first_query key. 498061da546Spatrick 499061da546Spatrick first_query: optional. If the first query requires a special query command, specify 500061da546Spatrick it with this key. Do not specify the $ prefix or #xx suffix. 501061da546Spatrick 502061da546Spatrick append_iteration_suffix: defaults to False. Specify True if the 0-based iteration 503061da546Spatrick index should be appended as a suffix to the command. e.g. qRegisterInfo with 504061da546Spatrick this key set true will generate query packets of qRegisterInfo0, qRegisterInfo1, 505061da546Spatrick etc. 506061da546Spatrick 507061da546Spatrick end_regex: required. Specifies a compiled regex object that will match the full text 508061da546Spatrick of any response that signals an end to the iteration. It must include the 509061da546Spatrick initial $ and ending #xx and must match the whole packet. 510061da546Spatrick 511061da546Spatrick save_key: required. Specifies the key within the context where an array will be stored. 512061da546Spatrick Each packet received from the gdb remote that does not match the end_regex will get 513061da546Spatrick appended to the array stored within the context at that key. 514061da546Spatrick 515061da546Spatrick runaway_response_count: optional. Defaults to 10000. If this many responses are retrieved, 516061da546Spatrick assume there is something wrong with either the response collection or the ending 517061da546Spatrick detection regex and throw an exception. 518061da546Spatrick """ 519061da546Spatrick 520061da546Spatrick def __init__(self, params): 521061da546Spatrick self._next_query = params.get("next_query", params.get("query")) 522061da546Spatrick if not self._next_query: 523061da546Spatrick raise "either next_query or query key must be specified for MultiResponseGdbRemoteEntry" 524061da546Spatrick 525061da546Spatrick self._first_query = params.get("first_query", self._next_query) 526061da546Spatrick self._append_iteration_suffix = params.get( 527061da546Spatrick "append_iteration_suffix", False) 528061da546Spatrick self._iteration = 0 529061da546Spatrick self._end_regex = params["end_regex"] 530061da546Spatrick self._save_key = params["save_key"] 531061da546Spatrick self._runaway_response_count = params.get( 532061da546Spatrick "runaway_response_count", 10000) 533061da546Spatrick self._is_send_to_remote = True 534061da546Spatrick self._end_matched = False 535061da546Spatrick 536061da546Spatrick def is_send_to_remote(self): 537061da546Spatrick return self._is_send_to_remote 538061da546Spatrick 539061da546Spatrick def get_send_packet(self): 540061da546Spatrick if not self.is_send_to_remote(): 541061da546Spatrick raise Exception( 542061da546Spatrick "get_send_packet() called on MultiResponseGdbRemoteEntry that is not in the send state") 543061da546Spatrick if self._end_matched: 544061da546Spatrick raise Exception( 545061da546Spatrick "get_send_packet() called on MultiResponseGdbRemoteEntry but end of query/response sequence has already been seen.") 546061da546Spatrick 547061da546Spatrick # Choose the first or next query for the base payload. 548061da546Spatrick if self._iteration == 0 and self._first_query: 549061da546Spatrick payload = self._first_query 550061da546Spatrick else: 551061da546Spatrick payload = self._next_query 552061da546Spatrick 553061da546Spatrick # Append the suffix as needed. 554061da546Spatrick if self._append_iteration_suffix: 555061da546Spatrick payload += "%x" % self._iteration 556061da546Spatrick 557061da546Spatrick # Keep track of the iteration. 558061da546Spatrick self._iteration += 1 559061da546Spatrick 560061da546Spatrick # Now that we've given the query packet, flip the mode to 561061da546Spatrick # receive/match. 562061da546Spatrick self._is_send_to_remote = False 563061da546Spatrick 564061da546Spatrick # Return the result, converted to packet form. 565061da546Spatrick return gdbremote_packet_encode_string(payload) 566061da546Spatrick 567061da546Spatrick def is_consumed(self): 568061da546Spatrick return self._end_matched 569061da546Spatrick 570061da546Spatrick def assert_match(self, asserter, actual_packet, context=None): 571061da546Spatrick # This only makes sense for matching lines coming from the remote debug 572061da546Spatrick # monitor. 573061da546Spatrick if self.is_send_to_remote(): 574061da546Spatrick raise Exception( 575061da546Spatrick "assert_match() called on MultiResponseGdbRemoteEntry but state is set to send a query packet.") 576061da546Spatrick 577061da546Spatrick if self._end_matched: 578061da546Spatrick raise Exception( 579061da546Spatrick "assert_match() called on MultiResponseGdbRemoteEntry but end of query/response sequence has already been seen.") 580061da546Spatrick 581061da546Spatrick # Set up a context as needed. 582061da546Spatrick if not context: 583061da546Spatrick context = {} 584061da546Spatrick 585061da546Spatrick # Check if the packet matches the end condition. 586061da546Spatrick match = self._end_regex.match(actual_packet) 587061da546Spatrick if match: 588061da546Spatrick # We're done iterating. 589061da546Spatrick self._end_matched = True 590061da546Spatrick return context 591061da546Spatrick 592061da546Spatrick # Not done iterating - save the packet. 593061da546Spatrick context[self._save_key] = context.get(self._save_key, []) 594061da546Spatrick context[self._save_key].append(actual_packet) 595061da546Spatrick 596061da546Spatrick # Check for a runaway response cycle. 597061da546Spatrick if len(context[self._save_key]) >= self._runaway_response_count: 598061da546Spatrick raise Exception( 599061da546Spatrick "runaway query/response cycle detected: %d responses captured so far. Last response: %s" % 600061da546Spatrick (len( 601061da546Spatrick context[ 602061da546Spatrick self._save_key]), context[ 603061da546Spatrick self._save_key][ 604061da546Spatrick -1])) 605061da546Spatrick 606061da546Spatrick # Flip the mode to send for generating the query. 607061da546Spatrick self._is_send_to_remote = True 608061da546Spatrick return context 609061da546Spatrick 610061da546Spatrick 611061da546Spatrickclass MatchRemoteOutputEntry(GdbRemoteEntryBase): 612061da546Spatrick """Waits for output from the debug monitor to match a regex or time out. 613061da546Spatrick 614061da546Spatrick This entry type tries to match each time new gdb remote output is accumulated 615061da546Spatrick using a provided regex. If the output does not match the regex within the 616061da546Spatrick given timeframe, the command fails the playback session. If the regex does 617061da546Spatrick match, any capture fields are recorded in the context. 618061da546Spatrick 619061da546Spatrick Settings accepted from params: 620061da546Spatrick 621061da546Spatrick regex: required. Specifies a compiled regex object that must either succeed 622061da546Spatrick with re.match or re.search (see regex_mode below) within the given timeout 623061da546Spatrick (see timeout_seconds below) or cause the playback to fail. 624061da546Spatrick 625061da546Spatrick regex_mode: optional. Available values: "match" or "search". If "match", the entire 626061da546Spatrick stub output as collected so far must match the regex. If search, then the regex 627061da546Spatrick must match starting somewhere within the output text accumulated thus far. 628061da546Spatrick Default: "match" (i.e. the regex must match the entirety of the accumulated output 629061da546Spatrick buffer, so unexpected text will generally fail the match). 630061da546Spatrick 631061da546Spatrick capture: optional. If specified, is a dictionary of regex match group indices (should start 632061da546Spatrick with 1) to variable names that will store the capture group indicated by the 633061da546Spatrick index. For example, {1:"thread_id"} will store capture group 1's content in the 634061da546Spatrick context dictionary where "thread_id" is the key and the match group value is 635*be691f3bSpatrick the value. This arg only makes sense when regex is specified. 636061da546Spatrick """ 637061da546Spatrick 638061da546Spatrick def __init__(self, regex=None, regex_mode="match", capture=None): 639061da546Spatrick self._regex = regex 640061da546Spatrick self._regex_mode = regex_mode 641061da546Spatrick self._capture = capture 642061da546Spatrick self._matched = False 643061da546Spatrick 644061da546Spatrick if not self._regex: 645061da546Spatrick raise Exception("regex cannot be None") 646061da546Spatrick 647061da546Spatrick if not self._regex_mode in ["match", "search"]: 648061da546Spatrick raise Exception( 649061da546Spatrick "unsupported regex mode \"{}\": must be \"match\" or \"search\"".format( 650061da546Spatrick self._regex_mode)) 651061da546Spatrick 652061da546Spatrick def is_output_matcher(self): 653061da546Spatrick return True 654061da546Spatrick 655061da546Spatrick def is_send_to_remote(self): 656061da546Spatrick # This is always a "wait for remote" command. 657061da546Spatrick return False 658061da546Spatrick 659061da546Spatrick def is_consumed(self): 660061da546Spatrick return self._matched 661061da546Spatrick 662061da546Spatrick def assert_match(self, asserter, accumulated_output, context): 663061da546Spatrick # Validate args. 664061da546Spatrick if not accumulated_output: 665061da546Spatrick raise Exception("accumulated_output cannot be none") 666061da546Spatrick if not context: 667061da546Spatrick raise Exception("context cannot be none") 668061da546Spatrick 669061da546Spatrick # Validate that we haven't already matched. 670061da546Spatrick if self._matched: 671061da546Spatrick raise Exception( 672061da546Spatrick "invalid state - already matched, attempting to match again") 673061da546Spatrick 674061da546Spatrick # If we don't have any content yet, we don't match. 675061da546Spatrick if len(accumulated_output) < 1: 676061da546Spatrick return context 677061da546Spatrick 678061da546Spatrick # Check if we match 679061da546Spatrick if self._regex_mode == "match": 680061da546Spatrick match = self._regex.match(accumulated_output) 681061da546Spatrick elif self._regex_mode == "search": 682061da546Spatrick match = self._regex.search(accumulated_output) 683061da546Spatrick else: 684061da546Spatrick raise Exception( 685061da546Spatrick "Unexpected regex mode: {}".format( 686061da546Spatrick self._regex_mode)) 687061da546Spatrick 688061da546Spatrick # If we don't match, wait to try again after next $O content, or time 689061da546Spatrick # out. 690061da546Spatrick if not match: 691061da546Spatrick # print("re pattern \"{}\" did not match against \"{}\"".format(self._regex.pattern, accumulated_output)) 692061da546Spatrick return context 693061da546Spatrick 694061da546Spatrick # We do match. 695061da546Spatrick self._matched = True 696061da546Spatrick # print("re pattern \"{}\" matched against \"{}\"".format(self._regex.pattern, accumulated_output)) 697061da546Spatrick 698061da546Spatrick # Collect up any captures into the context. 699061da546Spatrick if self._capture: 700061da546Spatrick # Handle captures. 701061da546Spatrick for group_index, var_name in list(self._capture.items()): 702061da546Spatrick capture_text = match.group(group_index) 703061da546Spatrick if not capture_text: 704061da546Spatrick raise Exception( 705061da546Spatrick "No content for group index {}".format(group_index)) 706061da546Spatrick context[var_name] = capture_text 707061da546Spatrick 708061da546Spatrick return context 709061da546Spatrick 710061da546Spatrick 711061da546Spatrickclass GdbRemoteTestSequence(object): 712061da546Spatrick 713061da546Spatrick _LOG_LINE_REGEX = re.compile(r'^.*(read|send)\s+packet:\s+(.+)$') 714061da546Spatrick 715061da546Spatrick def __init__(self, logger): 716061da546Spatrick self.entries = [] 717061da546Spatrick self.logger = logger 718061da546Spatrick 719*be691f3bSpatrick def __len__(self): 720*be691f3bSpatrick return len(self.entries) 721*be691f3bSpatrick 722061da546Spatrick def add_log_lines(self, log_lines, remote_input_is_read): 723061da546Spatrick for line in log_lines: 724061da546Spatrick if isinstance(line, str): 725061da546Spatrick # Handle log line import 726061da546Spatrick # if self.logger: 727061da546Spatrick # self.logger.debug("processing log line: {}".format(line)) 728061da546Spatrick match = self._LOG_LINE_REGEX.match(line) 729061da546Spatrick if match: 730061da546Spatrick playback_packet = match.group(2) 731061da546Spatrick direction = match.group(1) 732061da546Spatrick if _is_packet_lldb_gdbserver_input( 733061da546Spatrick direction, remote_input_is_read): 734061da546Spatrick # Handle as something to send to the remote debug monitor. 735061da546Spatrick # if self.logger: 736061da546Spatrick # self.logger.info("processed packet to send to remote: {}".format(playback_packet)) 737061da546Spatrick self.entries.append( 738061da546Spatrick GdbRemoteEntry( 739061da546Spatrick is_send_to_remote=True, 740061da546Spatrick exact_payload=playback_packet)) 741061da546Spatrick else: 742061da546Spatrick # Log line represents content to be expected from the remote debug monitor. 743061da546Spatrick # if self.logger: 744061da546Spatrick # self.logger.info("receiving packet from llgs, should match: {}".format(playback_packet)) 745061da546Spatrick self.entries.append( 746061da546Spatrick GdbRemoteEntry( 747061da546Spatrick is_send_to_remote=False, 748061da546Spatrick exact_payload=playback_packet)) 749061da546Spatrick else: 750061da546Spatrick raise Exception( 751061da546Spatrick "failed to interpret log line: {}".format(line)) 752061da546Spatrick elif isinstance(line, dict): 753061da546Spatrick entry_type = line.get("type", "regex_capture") 754061da546Spatrick if entry_type == "regex_capture": 755061da546Spatrick # Handle more explicit control over details via dictionary. 756061da546Spatrick direction = line.get("direction", None) 757061da546Spatrick regex = line.get("regex", None) 758061da546Spatrick capture = line.get("capture", None) 759061da546Spatrick 760061da546Spatrick # Compile the regex. 761061da546Spatrick if regex and (isinstance(regex, str)): 762061da546Spatrick regex = re.compile(regex) 763061da546Spatrick 764061da546Spatrick if _is_packet_lldb_gdbserver_input( 765061da546Spatrick direction, remote_input_is_read): 766061da546Spatrick # Handle as something to send to the remote debug monitor. 767061da546Spatrick # if self.logger: 768061da546Spatrick # self.logger.info("processed dict sequence to send to remote") 769061da546Spatrick self.entries.append( 770061da546Spatrick GdbRemoteEntry( 771061da546Spatrick is_send_to_remote=True, 772061da546Spatrick regex=regex, 773*be691f3bSpatrick capture=capture)) 774061da546Spatrick else: 775061da546Spatrick # Log line represents content to be expected from the remote debug monitor. 776061da546Spatrick # if self.logger: 777061da546Spatrick # self.logger.info("processed dict sequence to match receiving from remote") 778061da546Spatrick self.entries.append( 779061da546Spatrick GdbRemoteEntry( 780061da546Spatrick is_send_to_remote=False, 781061da546Spatrick regex=regex, 782*be691f3bSpatrick capture=capture)) 783061da546Spatrick elif entry_type == "multi_response": 784061da546Spatrick self.entries.append(MultiResponseGdbRemoteEntry(line)) 785061da546Spatrick elif entry_type == "output_match": 786061da546Spatrick 787061da546Spatrick regex = line.get("regex", None) 788061da546Spatrick # Compile the regex. 789061da546Spatrick if regex and (isinstance(regex, str)): 790061da546Spatrick regex = re.compile(regex, re.DOTALL) 791061da546Spatrick 792061da546Spatrick regex_mode = line.get("regex_mode", "match") 793061da546Spatrick capture = line.get("capture", None) 794061da546Spatrick self.entries.append( 795061da546Spatrick MatchRemoteOutputEntry( 796061da546Spatrick regex=regex, 797061da546Spatrick regex_mode=regex_mode, 798061da546Spatrick capture=capture)) 799061da546Spatrick else: 800061da546Spatrick raise Exception("unknown entry type \"%s\"" % entry_type) 801061da546Spatrick 802061da546Spatrick 803061da546Spatrickdef process_is_running(pid, unknown_value=True): 804061da546Spatrick """If possible, validate that the given pid represents a running process on the local system. 805061da546Spatrick 806061da546Spatrick Args: 807061da546Spatrick 808061da546Spatrick pid: an OS-specific representation of a process id. Should be an integral value. 809061da546Spatrick 810061da546Spatrick unknown_value: value used when we cannot determine how to check running local 811061da546Spatrick processes on the OS. 812061da546Spatrick 813061da546Spatrick Returns: 814061da546Spatrick 815061da546Spatrick If we can figure out how to check running process ids on the given OS: 816061da546Spatrick return True if the process is running, or False otherwise. 817061da546Spatrick 818061da546Spatrick If we don't know how to check running process ids on the given OS: 819061da546Spatrick return the value provided by the unknown_value arg. 820061da546Spatrick """ 821061da546Spatrick if not isinstance(pid, six.integer_types): 822061da546Spatrick raise Exception( 823061da546Spatrick "pid must be an integral type (actual type: %s)" % str( 824061da546Spatrick type(pid))) 825061da546Spatrick 826061da546Spatrick process_ids = [] 827061da546Spatrick 828061da546Spatrick if lldb.remote_platform: 829061da546Spatrick # Don't know how to get list of running process IDs on a remote 830061da546Spatrick # platform 831061da546Spatrick return unknown_value 832061da546Spatrick elif platform.system() in ['Darwin', 'Linux', 'FreeBSD', 'NetBSD']: 833061da546Spatrick # Build the list of running process ids 834061da546Spatrick output = subprocess.check_output( 835061da546Spatrick "ps ax | awk '{ print $1; }'", shell=True).decode("utf-8") 836061da546Spatrick text_process_ids = output.split('\n')[1:] 837061da546Spatrick # Convert text pids to ints 838061da546Spatrick process_ids = [int(text_pid) 839061da546Spatrick for text_pid in text_process_ids if text_pid != ''] 840061da546Spatrick elif platform.system() == 'Windows': 841061da546Spatrick output = subprocess.check_output( 842061da546Spatrick "for /f \"tokens=2 delims=,\" %F in ('tasklist /nh /fi \"PID ne 0\" /fo csv') do @echo %~F", shell=True).decode("utf-8") 843061da546Spatrick text_process_ids = output.split('\n')[1:] 844061da546Spatrick process_ids = [int(text_pid) 845061da546Spatrick for text_pid in text_process_ids if text_pid != ''] 846061da546Spatrick # elif {your_platform_here}: 847061da546Spatrick # fill in process_ids as a list of int type process IDs running on 848061da546Spatrick # the local system. 849061da546Spatrick else: 850061da546Spatrick # Don't know how to get list of running process IDs on this 851061da546Spatrick # OS, so return the "don't know" value. 852061da546Spatrick return unknown_value 853061da546Spatrick 854061da546Spatrick # Check if the pid is in the process_ids 855061da546Spatrick return pid in process_ids 856061da546Spatrick 857*be691f3bSpatrickdef _handle_output_packet_string(packet_contents): 858*be691f3bSpatrick if (not packet_contents) or (len(packet_contents) < 1): 859*be691f3bSpatrick return None 860*be691f3bSpatrick elif packet_contents[0:1] != b"O": 861*be691f3bSpatrick return None 862*be691f3bSpatrick elif packet_contents == b"OK": 863*be691f3bSpatrick return None 864061da546Spatrick else: 865*be691f3bSpatrick return binascii.unhexlify(packet_contents[1:]) 866*be691f3bSpatrick 867*be691f3bSpatrickclass Server(object): 868*be691f3bSpatrick 869*be691f3bSpatrick _GDB_REMOTE_PACKET_REGEX = re.compile(br'^\$([^\#]*)#[0-9a-fA-F]{2}') 870*be691f3bSpatrick 871*be691f3bSpatrick class ChecksumMismatch(Exception): 872*be691f3bSpatrick pass 873*be691f3bSpatrick 874*be691f3bSpatrick def __init__(self, sock, proc = None): 875*be691f3bSpatrick self._accumulated_output = b"" 876*be691f3bSpatrick self._receive_buffer = b"" 877*be691f3bSpatrick self._normal_queue = [] 878*be691f3bSpatrick self._output_queue = [] 879*be691f3bSpatrick self._sock = sock 880*be691f3bSpatrick self._proc = proc 881*be691f3bSpatrick 882*be691f3bSpatrick def send_raw(self, frame): 883*be691f3bSpatrick self._sock.sendall(frame) 884*be691f3bSpatrick 885*be691f3bSpatrick def send_ack(self): 886*be691f3bSpatrick self.send_raw(b"+") 887*be691f3bSpatrick 888*be691f3bSpatrick def send_packet(self, packet): 889*be691f3bSpatrick self.send_raw(b'$%s#%02x'%(packet, self._checksum(packet))) 890*be691f3bSpatrick 891*be691f3bSpatrick @staticmethod 892*be691f3bSpatrick def _checksum(packet): 893*be691f3bSpatrick checksum = 0 894*be691f3bSpatrick for c in six.iterbytes(packet): 895*be691f3bSpatrick checksum += c 896*be691f3bSpatrick return checksum % 256 897*be691f3bSpatrick 898*be691f3bSpatrick def _read(self, q): 899*be691f3bSpatrick while not q: 900*be691f3bSpatrick new_bytes = self._sock.recv(4096) 901*be691f3bSpatrick self._process_new_bytes(new_bytes) 902*be691f3bSpatrick return q.pop(0) 903*be691f3bSpatrick 904*be691f3bSpatrick def _process_new_bytes(self, new_bytes): 905*be691f3bSpatrick # Add new bytes to our accumulated unprocessed packet bytes. 906*be691f3bSpatrick self._receive_buffer += new_bytes 907*be691f3bSpatrick 908*be691f3bSpatrick # Parse fully-formed packets into individual packets. 909*be691f3bSpatrick has_more = len(self._receive_buffer) > 0 910*be691f3bSpatrick while has_more: 911*be691f3bSpatrick if len(self._receive_buffer) <= 0: 912*be691f3bSpatrick has_more = False 913*be691f3bSpatrick # handle '+' ack 914*be691f3bSpatrick elif self._receive_buffer[0:1] == b"+": 915*be691f3bSpatrick self._normal_queue += [b"+"] 916*be691f3bSpatrick self._receive_buffer = self._receive_buffer[1:] 917*be691f3bSpatrick else: 918*be691f3bSpatrick packet_match = self._GDB_REMOTE_PACKET_REGEX.match( 919*be691f3bSpatrick self._receive_buffer) 920*be691f3bSpatrick if packet_match: 921*be691f3bSpatrick # Our receive buffer matches a packet at the 922*be691f3bSpatrick # start of the receive buffer. 923*be691f3bSpatrick new_output_content = _handle_output_packet_string( 924*be691f3bSpatrick packet_match.group(1)) 925*be691f3bSpatrick if new_output_content: 926*be691f3bSpatrick # This was an $O packet with new content. 927*be691f3bSpatrick self._accumulated_output += new_output_content 928*be691f3bSpatrick self._output_queue += [self._accumulated_output] 929*be691f3bSpatrick else: 930*be691f3bSpatrick # Any packet other than $O. 931*be691f3bSpatrick self._normal_queue += [packet_match.group(0)] 932*be691f3bSpatrick 933*be691f3bSpatrick # Remove the parsed packet from the receive 934*be691f3bSpatrick # buffer. 935*be691f3bSpatrick self._receive_buffer = self._receive_buffer[ 936*be691f3bSpatrick len(packet_match.group(0)):] 937*be691f3bSpatrick else: 938*be691f3bSpatrick # We don't have enough in the receive bufferto make a full 939*be691f3bSpatrick # packet. Stop trying until we read more. 940*be691f3bSpatrick has_more = False 941*be691f3bSpatrick 942*be691f3bSpatrick def get_raw_output_packet(self): 943*be691f3bSpatrick return self._read(self._output_queue) 944*be691f3bSpatrick 945*be691f3bSpatrick def get_raw_normal_packet(self): 946*be691f3bSpatrick return self._read(self._normal_queue) 947*be691f3bSpatrick 948*be691f3bSpatrick @staticmethod 949*be691f3bSpatrick def _get_payload(frame): 950*be691f3bSpatrick payload = frame[1:-3] 951*be691f3bSpatrick checksum = int(frame[-2:], 16) 952*be691f3bSpatrick if checksum != Server._checksum(payload): 953*be691f3bSpatrick raise ChecksumMismatch 954*be691f3bSpatrick return payload 955*be691f3bSpatrick 956*be691f3bSpatrick def get_normal_packet(self): 957*be691f3bSpatrick frame = self.get_raw_normal_packet() 958*be691f3bSpatrick if frame == b"+": return frame 959*be691f3bSpatrick return self._get_payload(frame) 960*be691f3bSpatrick 961*be691f3bSpatrick def get_accumulated_output(self): 962*be691f3bSpatrick return self._accumulated_output 963*be691f3bSpatrick 964*be691f3bSpatrick def consume_accumulated_output(self): 965*be691f3bSpatrick output = self._accumulated_output 966*be691f3bSpatrick self._accumulated_output = b"" 967*be691f3bSpatrick return output 968*be691f3bSpatrick 969*be691f3bSpatrick def __str__(self): 970*be691f3bSpatrick return dedent("""\ 971*be691f3bSpatrick server '{}' on '{}' 972*be691f3bSpatrick _receive_buffer: {} 973*be691f3bSpatrick _normal_queue: {} 974*be691f3bSpatrick _output_queue: {} 975*be691f3bSpatrick _accumulated_output: {} 976*be691f3bSpatrick """).format(self._proc, self._sock, self._receive_buffer, 977*be691f3bSpatrick self._normal_queue, self._output_queue, 978*be691f3bSpatrick self._accumulated_output) 979