1122 lines · python
1"""Module for supporting unit testing of the lldb-server debug monitor exe.2"""3 4import binascii5import os6import os.path7import platform8import re9import socket10import subprocess11from lldbsuite.support import seven12from lldbsuite.test.lldbtest import *13from lldbsuite.test import configuration14from textwrap import dedent15import shutil16import select17import random18 19def _get_support_exe(basename):20 support_dir = lldb.SBHostOS.GetLLDBPath(lldb.ePathTypeSupportExecutableDir)21 22 return shutil.which(basename, path=support_dir.GetDirectory())23 24 25def get_lldb_server_exe():26 """Return the lldb-server exe path.27 28 Returns:29 A path to the lldb-server exe if it is found to exist; otherwise,30 returns None.31 """32 33 return _get_support_exe("lldb-server")34 35 36def get_debugserver_exe():37 """Return the debugserver exe path.38 39 Returns:40 A path to the debugserver exe if it is found to exist; otherwise,41 returns None.42 """43 if (44 configuration.arch45 and configuration.arch == "x86_64"46 and platform.machine().startswith("arm64")47 ):48 return "/Library/Apple/usr/libexec/oah/debugserver"49 50 return _get_support_exe("debugserver")51 52 53_LOG_LINE_REGEX = re.compile(54 r"^(lldb-server|debugserver)\s+<\s*(\d+)>\s+(read|send)\s+packet:\s+(.+)$"55)56 57 58def _is_packet_lldb_gdbserver_input(packet_type, llgs_input_is_read):59 """Return whether a given packet is input for lldb-gdbserver.60 61 Args:62 packet_type: a string indicating 'send' or 'receive', from a63 gdbremote packet protocol log.64 65 llgs_input_is_read: true if lldb-gdbserver input (content sent to66 lldb-gdbserver) is listed as 'read' or 'send' in the packet67 log entry.68 69 Returns:70 True if the packet should be considered input for lldb-gdbserver; False71 otherwise.72 """73 if packet_type == "read":74 # when llgs is the read side, then a read packet is meant for75 # input to llgs (when captured from the llgs/debugserver exe).76 return llgs_input_is_read77 elif packet_type == "send":78 # when llgs is the send side, then a send packet is meant to79 # be input to llgs (when captured from the lldb exe).80 return not llgs_input_is_read81 else:82 # don't understand what type of packet this is83 raise "Unknown packet type: {}".format(packet_type)84 85 86_STRIP_CHECKSUM_REGEX = re.compile(r"#[0-9a-fA-F]{2}$")87_STRIP_COMMAND_PREFIX_REGEX = re.compile(r"^\$")88_STRIP_COMMAND_PREFIX_M_REGEX = re.compile(r"^\$m")89 90 91def assert_packets_equal(asserter, actual_packet, expected_packet):92 # strip off the checksum digits of the packet. When we're in93 # no-ack mode, the # checksum is ignored, and should not be cause94 # for a mismatched packet.95 actual_stripped = _STRIP_CHECKSUM_REGEX.sub("", actual_packet)96 expected_stripped = _STRIP_CHECKSUM_REGEX.sub("", expected_packet)97 asserter.assertEqual(actual_stripped, expected_stripped)98 99 100def expect_lldb_gdbserver_replay(101 asserter, server, test_sequence, timeout_seconds, logger=None102):103 """Replay socket communication with lldb-gdbserver and verify responses.104 105 Args:106 asserter: the object providing assertEqual(first, second, msg=None), e.g. TestCase instance.107 108 test_sequence: a GdbRemoteTestSequence instance that describes109 the messages sent to the gdb remote and the responses110 expected from it.111 112 timeout_seconds: any response taking more than this number of113 seconds will cause an exception to be raised.114 115 logger: a Python logger instance.116 117 Returns:118 The context dictionary from running the given gdbremote119 protocol sequence. This will contain any of the capture120 elements specified to any GdbRemoteEntry instances in121 test_sequence.122 123 The context will also contain an entry, context["O_content"]124 which contains the text from the inferior received via $O125 packets. $O packets should not attempt to be matched126 directly since they are not entirely deterministic as to127 how many arrive and how much text is in each one.128 129 context["O_count"] will contain an integer of the number of130 O packets received.131 """132 133 # Ensure we have some work to do.134 if len(test_sequence.entries) < 1:135 return {}136 137 context = {"O_count": 0, "O_content": ""}138 139 # Grab the first sequence entry.140 sequence_entry = test_sequence.entries.pop(0)141 142 # While we have an active sequence entry, send messages143 # destined for the stub and collect/match/process responses144 # expected from the stub.145 while sequence_entry:146 if sequence_entry.is_send_to_remote():147 # This is an entry to send to the remote debug monitor.148 send_packet = sequence_entry.get_send_packet()149 if logger:150 if len(send_packet) == 1 and send_packet[0] == chr(3):151 packet_desc = "^C"152 else:153 packet_desc = send_packet154 logger.info("sending packet to remote: {}".format(packet_desc))155 server.send_raw(send_packet.encode())156 else:157 # This is an entry expecting to receive content from the remote158 # debug monitor.159 160 # We'll pull from (and wait on) the queue appropriate for the type of matcher.161 # We keep separate queues for process output (coming from non-deterministic162 # $O packet division) and for all other packets.163 try:164 if sequence_entry.is_output_matcher():165 # Grab next entry from the output queue.166 content = server.get_raw_output_packet()167 else:168 content = server.get_raw_normal_packet()169 content = seven.bitcast_to_string(content)170 except socket.timeout:171 asserter.fail(172 "timed out while waiting for '{}':\n{}".format(173 sequence_entry, server174 )175 )176 177 # Give the sequence entry the opportunity to match the content.178 # Output matchers might match or pass after more output accumulates.179 # Other packet types generally must match.180 asserter.assertIsNotNone(content)181 context = sequence_entry.assert_match(asserter, content, context=context)182 183 # Move on to next sequence entry as needed. Some sequence entries support executing multiple184 # times in different states (for looping over query/response185 # packets).186 if sequence_entry.is_consumed():187 if len(test_sequence.entries) > 0:188 sequence_entry = test_sequence.entries.pop(0)189 else:190 sequence_entry = None191 192 # Fill in the O_content entries.193 context["O_count"] = 1194 context["O_content"] = server.consume_accumulated_output()195 196 return context197 198 199def gdbremote_hex_encode_string(str):200 output = ""201 for c in str:202 output += "{0:02x}".format(ord(c))203 return output204 205 206def gdbremote_hex_decode_string(str):207 return str.decode("hex")208 209 210def gdbremote_packet_encode_string(str):211 checksum = 0212 for c in str:213 checksum += ord(c)214 return "$" + str + "#{0:02x}".format(checksum % 256)215 216 217def build_gdbremote_A_packet(args_list):218 """Given a list of args, create a properly-formed $A packet containing each arg."""219 payload = "A"220 221 # build the arg content222 arg_index = 0223 for arg in args_list:224 # Comma-separate the args.225 if arg_index > 0:226 payload += ","227 228 # Hex-encode the arg.229 hex_arg = gdbremote_hex_encode_string(arg)230 231 # Build the A entry.232 payload += "{},{},{}".format(len(hex_arg), arg_index, hex_arg)233 234 # Next arg index, please.235 arg_index += 1236 237 # return the packetized payload238 return gdbremote_packet_encode_string(payload)239 240 241def parse_reg_info_response(response_packet):242 if not response_packet:243 raise Exception("response_packet cannot be None")244 245 # Strip off prefix $ and suffix #xx if present.246 response_packet = _STRIP_COMMAND_PREFIX_REGEX.sub("", response_packet)247 response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet)248 249 # Build keyval pairs250 values = {}251 for kv in response_packet.split(";"):252 if len(kv) < 1:253 continue254 (key, val) = kv.split(":")255 values[key] = val256 257 return values258 259 260def parse_threadinfo_response(response_packet):261 if not response_packet:262 raise Exception("response_packet cannot be None")263 264 # Strip off prefix $ and suffix #xx if present.265 response_packet = _STRIP_COMMAND_PREFIX_M_REGEX.sub("", response_packet)266 response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet)267 268 for tid in response_packet.split(","):269 if not tid:270 continue271 if tid.startswith("p"):272 pid, _, tid = tid.partition(".")273 yield (int(pid[1:], 16), int(tid, 16))274 else:275 yield int(tid, 16)276 277 278def unpack_endian_binary_string(endian, value_string):279 """Unpack a gdb-remote binary (post-unescaped, i.e. not escaped) response to an unsigned int given endianness of the inferior."""280 if not endian:281 raise Exception("endian cannot be None")282 if not value_string or len(value_string) < 1:283 raise Exception("value_string cannot be None or empty")284 285 if endian == "little":286 value = 0287 i = 0288 while len(value_string) > 0:289 value += ord(value_string[0]) << i290 value_string = value_string[1:]291 i += 8292 return value293 elif endian == "big":294 value = 0295 while len(value_string) > 0:296 value = (value << 8) + ord(value_string[0])297 value_string = value_string[1:]298 return value299 else:300 # pdp is valid but need to add parse code once needed.301 raise Exception("unsupported endian:{}".format(endian))302 303 304def unpack_register_hex_unsigned(endian, value_string):305 """Unpack a gdb-remote $p-style response to an unsigned int given endianness of inferior."""306 if not endian:307 raise Exception("endian cannot be None")308 if not value_string or len(value_string) < 1:309 raise Exception("value_string cannot be None or empty")310 311 if endian == "little":312 value = 0313 i = 0314 while len(value_string) > 0:315 value += int(value_string[0:2], 16) << i316 value_string = value_string[2:]317 i += 8318 return value319 elif endian == "big":320 return int(value_string, 16)321 else:322 # pdp is valid but need to add parse code once needed.323 raise Exception("unsupported endian:{}".format(endian))324 325 326def pack_register_hex(endian, value, byte_size=None):327 """Unpack a gdb-remote $p-style response to an unsigned int given endianness of inferior."""328 if not endian:329 raise Exception("endian cannot be None")330 331 if endian == "little":332 # Create the litt-endian return value.333 retval = ""334 while value != 0:335 retval = retval + "{:02x}".format(value & 0xFF)336 value = value >> 8337 if byte_size:338 # Add zero-fill to the right/end (MSB side) of the value.339 retval += "00" * (byte_size - len(retval) // 2)340 return retval341 342 elif endian == "big":343 retval = ""344 while value != 0:345 retval = "{:02x}".format(value & 0xFF) + retval346 value = value >> 8347 if byte_size:348 # Add zero-fill to the left/front (MSB side) of the value.349 retval = ("00" * (byte_size - len(retval) // 2)) + retval350 return retval351 352 else:353 # pdp is valid but need to add parse code once needed.354 raise Exception("unsupported endian:{}".format(endian))355 356 357class GdbRemoteEntryBase(object):358 def is_output_matcher(self):359 return False360 361 362class GdbRemoteEntry(GdbRemoteEntryBase):363 def __init__(364 self, is_send_to_remote=True, exact_payload=None, regex=None, capture=None365 ):366 """Create an entry representing one piece of the I/O to/from a gdb remote debug monitor.367 368 Args:369 370 is_send_to_remote: True if this entry is a message to be371 sent to the gdbremote debug monitor; False if this372 entry represents text to be matched against the reply373 from the gdbremote debug monitor.374 375 exact_payload: if not None, then this packet is an exact376 send (when sending to the remote) or an exact match of377 the response from the gdbremote. The checksums are378 ignored on exact match requests since negotiation of379 no-ack makes the checksum content essentially380 undefined.381 382 regex: currently only valid for receives from gdbremote. When383 specified (and only if exact_payload is None), indicates the384 gdbremote response must match the given regex. Match groups in385 the regex can be used for the matching portion (see capture386 arg). It is perfectly valid to have just a regex arg without a387 capture arg. This arg only makes sense if exact_payload is not388 specified.389 390 capture: if specified, is a dictionary of regex match391 group indices (should start with 1) to variable names392 that will store the capture group indicated by the393 index. For example, {1:"thread_id"} will store capture394 group 1's content in the context dictionary where395 "thread_id" is the key and the match group value is396 the value. This arg only makes sense when regex is specified.397 """398 self._is_send_to_remote = is_send_to_remote399 self.exact_payload = exact_payload400 self.regex = regex401 self.capture = capture402 403 def is_send_to_remote(self):404 return self._is_send_to_remote405 406 def is_consumed(self):407 # For now, all packets are consumed after first use.408 return True409 410 def get_send_packet(self):411 if not self.is_send_to_remote():412 raise Exception(413 "get_send_packet() called on GdbRemoteEntry that is not a send-to-remote packet"414 )415 if not self.exact_payload:416 raise Exception(417 "get_send_packet() called on GdbRemoteEntry but it doesn't have an exact payload"418 )419 return self.exact_payload420 421 def _assert_exact_payload_match(self, asserter, actual_packet):422 assert_packets_equal(asserter, actual_packet, self.exact_payload)423 return None424 425 def _assert_regex_match(self, asserter, actual_packet, context):426 # Ensure the actual packet matches from the start of the actual packet.427 match = self.regex.match(actual_packet)428 if not match:429 asserter.fail(430 "regex '{}' failed to match against content '{}'".format(431 self.regex.pattern, actual_packet432 )433 )434 435 if self.capture:436 # Handle captures.437 for group_index, var_name in list(self.capture.items()):438 capture_text = match.group(group_index)439 # It is okay for capture text to be None - which it will be if it is a group that can match nothing.440 # The user must be okay with it since the regex itself matched441 # above.442 context[var_name] = capture_text443 444 return context445 446 def assert_match(self, asserter, actual_packet, context=None):447 # This only makes sense for matching lines coming from the448 # remote debug monitor.449 if self.is_send_to_remote():450 raise Exception(451 "Attempted to match a packet being sent to the remote debug monitor, doesn't make sense."452 )453 454 # Create a new context if needed.455 if not context:456 context = {}457 458 # If this is an exact payload, ensure they match exactly,459 # ignoring the packet checksum which is optional for no-ack460 # mode.461 if self.exact_payload:462 self._assert_exact_payload_match(asserter, actual_packet)463 return context464 elif self.regex:465 return self._assert_regex_match(asserter, actual_packet, context)466 else:467 raise Exception(468 "Don't know how to match a remote-sent packet when exact_payload isn't specified."469 )470 471 472class MultiResponseGdbRemoteEntry(GdbRemoteEntryBase):473 """Represents a query/response style packet.474 475 Assumes the first item is sent to the gdb remote.476 An end sequence regex indicates the end of the query/response477 packet sequence. All responses up through (but not including) the478 end response are stored in a context variable.479 480 Settings accepted from params:481 482 next_query or query: required. The typical query packet without the $ prefix or #xx suffix.483 If there is a special first packet to start the iteration query, see the484 first_query key.485 486 first_query: optional. If the first query requires a special query command, specify487 it with this key. Do not specify the $ prefix or #xx suffix.488 489 append_iteration_suffix: defaults to False. Specify True if the 0-based iteration490 index should be appended as a suffix to the command. e.g. qRegisterInfo with491 this key set true will generate query packets of qRegisterInfo0, qRegisterInfo1,492 etc.493 494 end_regex: required. Specifies a compiled regex object that will match the full text495 of any response that signals an end to the iteration. It must include the496 initial $ and ending #xx and must match the whole packet.497 498 save_key: required. Specifies the key within the context where an array will be stored.499 Each packet received from the gdb remote that does not match the end_regex will get500 appended to the array stored within the context at that key.501 502 runaway_response_count: optional. Defaults to 10000. If this many responses are retrieved,503 assume there is something wrong with either the response collection or the ending504 detection regex and throw an exception.505 """506 507 def __init__(self, params):508 self._next_query = params.get("next_query", params.get("query"))509 if not self._next_query:510 raise "either next_query or query key must be specified for MultiResponseGdbRemoteEntry"511 512 self._first_query = params.get("first_query", self._next_query)513 self._append_iteration_suffix = params.get("append_iteration_suffix", False)514 self._iteration = 0515 self._end_regex = params["end_regex"]516 self._save_key = params["save_key"]517 self._runaway_response_count = params.get("runaway_response_count", 10000)518 self._is_send_to_remote = True519 self._end_matched = False520 521 def is_send_to_remote(self):522 return self._is_send_to_remote523 524 def get_send_packet(self):525 if not self.is_send_to_remote():526 raise Exception(527 "get_send_packet() called on MultiResponseGdbRemoteEntry that is not in the send state"528 )529 if self._end_matched:530 raise Exception(531 "get_send_packet() called on MultiResponseGdbRemoteEntry but end of query/response sequence has already been seen."532 )533 534 # Choose the first or next query for the base payload.535 if self._iteration == 0 and self._first_query:536 payload = self._first_query537 else:538 payload = self._next_query539 540 # Append the suffix as needed.541 if self._append_iteration_suffix:542 payload += "%x" % self._iteration543 544 # Keep track of the iteration.545 self._iteration += 1546 547 # Now that we've given the query packet, flip the mode to548 # receive/match.549 self._is_send_to_remote = False550 551 # Return the result, converted to packet form.552 return gdbremote_packet_encode_string(payload)553 554 def is_consumed(self):555 return self._end_matched556 557 def assert_match(self, asserter, actual_packet, context=None):558 # This only makes sense for matching lines coming from the remote debug559 # monitor.560 if self.is_send_to_remote():561 raise Exception(562 "assert_match() called on MultiResponseGdbRemoteEntry but state is set to send a query packet."563 )564 565 if self._end_matched:566 raise Exception(567 "assert_match() called on MultiResponseGdbRemoteEntry but end of query/response sequence has already been seen."568 )569 570 # Set up a context as needed.571 if not context:572 context = {}573 574 # Check if the packet matches the end condition.575 match = self._end_regex.match(actual_packet)576 if match:577 # We're done iterating.578 self._end_matched = True579 return context580 581 # Not done iterating - save the packet.582 context[self._save_key] = context.get(self._save_key, [])583 context[self._save_key].append(actual_packet)584 585 # Check for a runaway response cycle.586 if len(context[self._save_key]) >= self._runaway_response_count:587 raise Exception(588 "runaway query/response cycle detected: %d responses captured so far. Last response: %s"589 % (len(context[self._save_key]), context[self._save_key][-1])590 )591 592 # Flip the mode to send for generating the query.593 self._is_send_to_remote = True594 return context595 596 597class MatchRemoteOutputEntry(GdbRemoteEntryBase):598 """Waits for output from the debug monitor to match a regex or time out.599 600 This entry type tries to match each time new gdb remote output is accumulated601 using a provided regex. If the output does not match the regex within the602 given timeframe, the command fails the playback session. If the regex does603 match, any capture fields are recorded in the context.604 605 Settings accepted from params:606 607 regex: required. Specifies a compiled regex object that must either succeed608 with re.match or re.search (see regex_mode below) within the given timeout609 (see timeout_seconds below) or cause the playback to fail.610 611 regex_mode: optional. Available values: "match" or "search". If "match", the entire612 stub output as collected so far must match the regex. If search, then the regex613 must match starting somewhere within the output text accumulated thus far.614 Default: "match" (i.e. the regex must match the entirety of the accumulated output615 buffer, so unexpected text will generally fail the match).616 617 capture: optional. If specified, is a dictionary of regex match group indices (should start618 with 1) to variable names that will store the capture group indicated by the619 index. For example, {1:"thread_id"} will store capture group 1's content in the620 context dictionary where "thread_id" is the key and the match group value is621 the value. This arg only makes sense when regex is specified.622 """623 624 def __init__(self, regex=None, regex_mode="match", capture=None):625 self._regex = regex626 self._regex_mode = regex_mode627 self._capture = capture628 self._matched = False629 630 if not self._regex:631 raise Exception("regex cannot be None")632 633 if not self._regex_mode in ["match", "search"]:634 raise Exception(635 'unsupported regex mode "{}": must be "match" or "search"'.format(636 self._regex_mode637 )638 )639 640 def is_output_matcher(self):641 return True642 643 def is_send_to_remote(self):644 # This is always a "wait for remote" command.645 return False646 647 def is_consumed(self):648 return self._matched649 650 def assert_match(self, asserter, accumulated_output, context):651 # Validate args.652 if not accumulated_output:653 raise Exception("accumulated_output cannot be none")654 if not context:655 raise Exception("context cannot be none")656 657 # Validate that we haven't already matched.658 if self._matched:659 raise Exception(660 "invalid state - already matched, attempting to match again"661 )662 663 # If we don't have any content yet, we don't match.664 if len(accumulated_output) < 1:665 return context666 667 # Check if we match668 if self._regex_mode == "match":669 match = self._regex.match(accumulated_output)670 elif self._regex_mode == "search":671 match = self._regex.search(accumulated_output)672 else:673 raise Exception("Unexpected regex mode: {}".format(self._regex_mode))674 675 # If we don't match, wait to try again after next $O content, or time676 # out.677 if not match:678 # print("re pattern \"{}\" did not match against \"{}\"".format(self._regex.pattern, accumulated_output))679 return context680 681 # We do match.682 self._matched = True683 # print("re pattern \"{}\" matched against \"{}\"".format(self._regex.pattern, accumulated_output))684 685 # Collect up any captures into the context.686 if self._capture:687 # Handle captures.688 for group_index, var_name in list(self._capture.items()):689 capture_text = match.group(group_index)690 if not capture_text:691 raise Exception("No content for group index {}".format(group_index))692 context[var_name] = capture_text693 694 return context695 696 697class GdbRemoteTestSequence(object):698 _LOG_LINE_REGEX = re.compile(r"^.*(read|send)\s+packet:\s+(.+)$")699 700 def __init__(self, logger):701 self.entries = []702 self.logger = logger703 704 def __len__(self):705 return len(self.entries)706 707 def add_log_lines(self, log_lines, remote_input_is_read):708 for line in log_lines:709 if isinstance(line, str):710 # Handle log line import711 # if self.logger:712 # self.logger.debug("processing log line: {}".format(line))713 match = self._LOG_LINE_REGEX.match(line)714 if match:715 playback_packet = match.group(2)716 direction = match.group(1)717 if _is_packet_lldb_gdbserver_input(direction, remote_input_is_read):718 # Handle as something to send to the remote debug monitor.719 # if self.logger:720 # self.logger.info("processed packet to send to remote: {}".format(playback_packet))721 self.entries.append(722 GdbRemoteEntry(723 is_send_to_remote=True, exact_payload=playback_packet724 )725 )726 else:727 # Log line represents content to be expected from the remote debug monitor.728 # if self.logger:729 # self.logger.info("receiving packet from llgs, should match: {}".format(playback_packet))730 self.entries.append(731 GdbRemoteEntry(732 is_send_to_remote=False, exact_payload=playback_packet733 )734 )735 else:736 raise Exception("failed to interpret log line: {}".format(line))737 elif isinstance(line, dict):738 entry_type = line.get("type", "regex_capture")739 if entry_type == "regex_capture":740 # Handle more explicit control over details via dictionary.741 direction = line.get("direction", None)742 regex = line.get("regex", None)743 capture = line.get("capture", None)744 745 # Compile the regex.746 if regex and (isinstance(regex, str)):747 regex = re.compile(regex, re.DOTALL)748 749 if _is_packet_lldb_gdbserver_input(direction, remote_input_is_read):750 # Handle as something to send to the remote debug monitor.751 # if self.logger:752 # self.logger.info("processed dict sequence to send to remote")753 self.entries.append(754 GdbRemoteEntry(755 is_send_to_remote=True, regex=regex, capture=capture756 )757 )758 else:759 # Log line represents content to be expected from the remote debug monitor.760 # if self.logger:761 # self.logger.info("processed dict sequence to match receiving from remote")762 self.entries.append(763 GdbRemoteEntry(764 is_send_to_remote=False, regex=regex, capture=capture765 )766 )767 elif entry_type == "multi_response":768 self.entries.append(MultiResponseGdbRemoteEntry(line))769 elif entry_type == "output_match":770 regex = line.get("regex", None)771 # Compile the regex.772 if regex and (isinstance(regex, str)):773 regex = re.compile(regex, re.DOTALL)774 775 regex_mode = line.get("regex_mode", "match")776 capture = line.get("capture", None)777 self.entries.append(778 MatchRemoteOutputEntry(779 regex=regex, regex_mode=regex_mode, capture=capture780 )781 )782 else:783 raise Exception('unknown entry type "%s"' % entry_type)784 785 786def process_is_running(pid, unknown_value=True):787 """If possible, validate that the given pid represents a running process on the local system.788 789 Args:790 791 pid: an OS-specific representation of a process id. Should be an integral value.792 793 unknown_value: value used when we cannot determine how to check running local794 processes on the OS.795 796 Returns:797 798 If we can figure out how to check running process ids on the given OS:799 return True if the process is running, or False otherwise.800 801 If we don't know how to check running process ids on the given OS:802 return the value provided by the unknown_value arg.803 """804 if not isinstance(pid, int):805 raise Exception(806 "pid must be an integral type (actual type: %s)" % str(type(pid))807 )808 809 process_ids = []810 811 if lldb.remote_platform:812 # Don't know how to get list of running process IDs on a remote813 # platform814 return unknown_value815 elif platform.system() in ["Darwin", "Linux", "FreeBSD", "NetBSD"]:816 # Build the list of running process ids817 output = subprocess.check_output(818 "ps ax | awk '{ print $1; }'", shell=True819 ).decode("utf-8")820 text_process_ids = output.split("\n")[1:]821 # Convert text pids to ints822 process_ids = [int(text_pid) for text_pid in text_process_ids if text_pid != ""]823 elif platform.system() == "Windows":824 output = subprocess.check_output(825 'for /f "tokens=2 delims=," %F in (\'tasklist /nh /fi "PID ne 0" /fo csv\') do @echo %~F',826 shell=True,827 ).decode("utf-8")828 text_process_ids = output.split("\n")[1:]829 process_ids = [int(text_pid) for text_pid in text_process_ids if text_pid != ""]830 # elif {your_platform_here}:831 # fill in process_ids as a list of int type process IDs running on832 # the local system.833 else:834 # Don't know how to get list of running process IDs on this835 # OS, so return the "don't know" value.836 return unknown_value837 838 # Check if the pid is in the process_ids839 return pid in process_ids840 841 842def _handle_output_packet_string(packet_contents):843 # Warning: in non-stop mode, we currently handle only the first output844 # packet since we'd need to inject vStdio packets845 if not packet_contents.startswith((b"$O", b"%Stdio:O")):846 return None847 elif packet_contents == b"$OK":848 return None849 else:850 return binascii.unhexlify(packet_contents.partition(b"O")[2])851 852 853class Server(object):854 _GDB_REMOTE_PACKET_REGEX = re.compile(rb"^([\$%][^\#]*)#[0-9a-fA-F]{2}")855 856 class ChecksumMismatch(Exception):857 pass858 859 def __init__(self, sock, proc=None):860 self._accumulated_output = b""861 self._receive_buffer = b""862 self._normal_queue = []863 self._output_queue = []864 self._sock = sock865 self._proc = proc866 self._validate_checksums = True867 868 def send_raw(self, frame):869 self._sock.sendall(frame)870 871 def send_ack(self):872 self.send_raw(b"+")873 874 def send_packet(self, packet):875 self.send_raw(b"$%s#%02x" % (packet, self._checksum(packet)))876 877 def set_validate_checksums(self, validate):878 self._validate_checksums = validate879 880 @staticmethod881 def _checksum(packet):882 checksum = 0883 for c in iter(packet):884 checksum += c885 return checksum % 256886 887 def _read(self, q):888 while not q:889 new_bytes = self._sock.recv(4096)890 self._process_new_bytes(new_bytes)891 return q.pop(0)892 893 def _process_new_bytes(self, new_bytes):894 # Add new bytes to our accumulated unprocessed packet bytes.895 self._receive_buffer += new_bytes896 897 # Parse fully-formed packets into individual packets.898 has_more = len(self._receive_buffer) > 0899 while has_more:900 if len(self._receive_buffer) <= 0:901 has_more = False902 # handle '+' ack903 elif self._receive_buffer[0:1] == b"+":904 self._normal_queue += [b"+"]905 self._receive_buffer = self._receive_buffer[1:]906 else:907 packet_match = self._GDB_REMOTE_PACKET_REGEX.match(self._receive_buffer)908 if packet_match:909 # Our receive buffer matches a packet at the910 # start of the receive buffer.911 new_output_content = _handle_output_packet_string(912 packet_match.group(1)913 )914 if new_output_content:915 # This was an $O packet with new content.916 self._accumulated_output += new_output_content917 self._output_queue += [self._accumulated_output]918 else:919 # Any packet other than $O.920 self._normal_queue += [packet_match.group(0)]921 922 # Remove the parsed packet from the receive923 # buffer.924 self._receive_buffer = self._receive_buffer[925 len(packet_match.group(0)) :926 ]927 else:928 # We don't have enough in the receive bufferto make a full929 # packet. Stop trying until we read more.930 has_more = False931 932 def get_raw_output_packet(self):933 return self._read(self._output_queue)934 935 def get_raw_normal_packet(self):936 return self._read(self._normal_queue)937 938 def _get_payload(self, frame):939 payload = frame[1:-3]940 if self._validate_checksums:941 checksum = int(frame[-2:], 16)942 if checksum != Server._checksum(payload):943 raise ChecksumMismatch944 return payload945 946 def get_normal_packet(self):947 frame = self.get_raw_normal_packet()948 if frame == b"+":949 return frame950 return self._get_payload(frame)951 952 def get_accumulated_output(self):953 return self._accumulated_output954 955 def consume_accumulated_output(self):956 output = self._accumulated_output957 self._accumulated_output = b""958 return output959 960 def __str__(self):961 return dedent(962 """\963 server '{}' on '{}'964 _receive_buffer: {}965 _normal_queue: {}966 _output_queue: {}967 _accumulated_output: {}968 """969 ).format(970 self._proc,971 self._sock,972 self._receive_buffer,973 self._normal_queue,974 self._output_queue,975 self._accumulated_output,976 )977 978 979# A class representing a pipe for communicating with debug server.980# This class includes menthods to open the pipe and read the port number from it.981if lldbplatformutil.getHostPlatform() == "windows":982 import ctypes983 import ctypes.wintypes984 from ctypes.wintypes import BOOL, DWORD, HANDLE, LPCWSTR, LPDWORD, LPVOID985 986 kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)987 988 PIPE_ACCESS_INBOUND = 1989 FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000990 FILE_FLAG_OVERLAPPED = 0x40000000991 PIPE_TYPE_BYTE = 0992 PIPE_REJECT_REMOTE_CLIENTS = 8993 INVALID_HANDLE_VALUE = -1994 ERROR_ACCESS_DENIED = 5995 ERROR_IO_PENDING = 997996 997 class OVERLAPPED(ctypes.Structure):998 _fields_ = [999 ("Internal", LPVOID),1000 ("InternalHigh", LPVOID),1001 ("Offset", DWORD),1002 ("OffsetHigh", DWORD),1003 ("hEvent", HANDLE),1004 ]1005 1006 def __init__(self):1007 super(OVERLAPPED, self).__init__(1008 Internal=0, InternalHigh=0, Offset=0, OffsetHigh=0, hEvent=None1009 )1010 1011 LPOVERLAPPED = ctypes.POINTER(OVERLAPPED)1012 1013 CreateNamedPipe = kernel32.CreateNamedPipeW1014 CreateNamedPipe.restype = HANDLE1015 CreateNamedPipe.argtypes = (1016 LPCWSTR,1017 DWORD,1018 DWORD,1019 DWORD,1020 DWORD,1021 DWORD,1022 DWORD,1023 LPVOID,1024 )1025 1026 ConnectNamedPipe = kernel32.ConnectNamedPipe1027 ConnectNamedPipe.restype = BOOL1028 ConnectNamedPipe.argtypes = (HANDLE, LPOVERLAPPED)1029 1030 CreateEvent = kernel32.CreateEventW1031 CreateEvent.restype = HANDLE1032 CreateEvent.argtypes = (LPVOID, BOOL, BOOL, LPCWSTR)1033 1034 GetOverlappedResultEx = kernel32.GetOverlappedResultEx1035 GetOverlappedResultEx.restype = BOOL1036 GetOverlappedResultEx.argtypes = (HANDLE, LPOVERLAPPED, LPDWORD, DWORD, BOOL)1037 1038 ReadFile = kernel32.ReadFile1039 ReadFile.restype = BOOL1040 ReadFile.argtypes = (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED)1041 1042 CloseHandle = kernel32.CloseHandle1043 CloseHandle.restype = BOOL1044 CloseHandle.argtypes = (HANDLE,)1045 1046 class Pipe(object):1047 def __init__(self, prefix):1048 while True:1049 self.name = "lldb-" + str(random.randrange(10**10))1050 full_name = "\\\\.\\pipe\\" + self.name1051 self._handle = CreateNamedPipe(1052 full_name,1053 PIPE_ACCESS_INBOUND1054 | FILE_FLAG_FIRST_PIPE_INSTANCE1055 | FILE_FLAG_OVERLAPPED,1056 PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,1057 1,1058 4096,1059 4096,1060 0,1061 None,1062 )1063 if self._handle != INVALID_HANDLE_VALUE:1064 break1065 if ctypes.get_last_error() != ERROR_ACCESS_DENIED:1066 raise ctypes.WinError(ctypes.get_last_error())1067 1068 self._overlapped = OVERLAPPED()1069 self._overlapped.hEvent = CreateEvent(None, True, False, None)1070 result = ConnectNamedPipe(self._handle, self._overlapped)1071 assert result == 01072 if ctypes.get_last_error() != ERROR_IO_PENDING:1073 raise ctypes.WinError(ctypes.get_last_error())1074 1075 def finish_connection(self, timeout):1076 if not GetOverlappedResultEx(1077 self._handle,1078 self._overlapped,1079 ctypes.byref(DWORD(0)),1080 timeout * 1000,1081 True,1082 ):1083 raise ctypes.WinError(ctypes.get_last_error())1084 1085 def read(self, size, timeout):1086 buf = ctypes.create_string_buffer(size)1087 if not ReadFile(1088 self._handle, ctypes.byref(buf), size, None, self._overlapped1089 ):1090 if ctypes.get_last_error() != ERROR_IO_PENDING:1091 raise ctypes.WinError(ctypes.get_last_error())1092 read = DWORD(0)1093 if not GetOverlappedResultEx(1094 self._handle, self._overlapped, ctypes.byref(read), timeout * 1000, True1095 ):1096 raise ctypes.WinError(ctypes.get_last_error())1097 return buf.raw[0 : read.value]1098 1099 def close(self):1100 CloseHandle(self._overlapped.hEvent)1101 CloseHandle(self._handle)1102 1103else:1104 1105 class Pipe(object):1106 def __init__(self, prefix):1107 self.name = os.path.join(prefix, "stub_port_number")1108 os.mkfifo(self.name)1109 self._fd = os.open(self.name, os.O_RDONLY | os.O_NONBLOCK)1110 1111 def finish_connection(self, timeout):1112 pass1113 1114 def read(self, size, timeout):1115 (readers, _, _) = select.select([self._fd], [], [], timeout)1116 if self._fd not in readers:1117 raise TimeoutError1118 return os.read(self._fd, size)1119 1120 def close(self):1121 os.close(self._fd)1122