xref: /llvm-project/lldb/packages/Python/lldbsuite/test/tools/lldb-server/lldbgdbserverutils.py (revision 857700ff6fb9f9f653c3788445df06db07e7bb59)
1a61c247cSAntonio Afonso"""Module for supporting unit testing of the lldb-server debug monitor exe.
2a61c247cSAntonio Afonso"""
3a61c247cSAntonio Afonso
4a2f4f7daSPavel Labathimport binascii
5a61c247cSAntonio Afonsoimport os
6a61c247cSAntonio Afonsoimport os.path
7a61c247cSAntonio Afonsoimport platform
8a61c247cSAntonio Afonsoimport re
9a2f4f7daSPavel Labathimport socket
10a61c247cSAntonio Afonsoimport subprocess
11a2f4f7daSPavel Labathfrom lldbsuite.support import seven
12a61c247cSAntonio Afonsofrom lldbsuite.test.lldbtest import *
1399298c7fSFred Rissfrom lldbsuite.test import configuration
14a2f4f7daSPavel Labathfrom textwrap import dedent
153ca7b2d0SPavel Labathimport shutil
16a52be0ccSSanthosh Kumar Ellendulaimport select
17781ba3c6SMuhammad Omair Javaidimport random
182238dcc3SJonas Devlieghere
193ca7b2d0SPavel Labathdef _get_support_exe(basename):
203ca7b2d0SPavel Labath    support_dir = lldb.SBHostOS.GetLLDBPath(lldb.ePathTypeSupportExecutableDir)
21a61c247cSAntonio Afonso
223ca7b2d0SPavel Labath    return shutil.which(basename, path=support_dir.GetDirectory())
23a61c247cSAntonio Afonso
24a61c247cSAntonio Afonso
25a61c247cSAntonio Afonsodef get_lldb_server_exe():
26a61c247cSAntonio Afonso    """Return the lldb-server exe path.
27a61c247cSAntonio Afonso
28a61c247cSAntonio Afonso    Returns:
29a61c247cSAntonio Afonso        A path to the lldb-server exe if it is found to exist; otherwise,
30a61c247cSAntonio Afonso        returns None.
31a61c247cSAntonio Afonso    """
32599fdfc5SPavel Labath
333ca7b2d0SPavel Labath    return _get_support_exe("lldb-server")
34a61c247cSAntonio Afonso
35a61c247cSAntonio Afonso
36a61c247cSAntonio Afonsodef get_debugserver_exe():
37a61c247cSAntonio Afonso    """Return the debugserver exe path.
38a61c247cSAntonio Afonso
39a61c247cSAntonio Afonso    Returns:
40a61c247cSAntonio Afonso        A path to the debugserver exe if it is found to exist; otherwise,
41a61c247cSAntonio Afonso        returns None.
42a61c247cSAntonio Afonso    """
432238dcc3SJonas Devlieghere    if (
442238dcc3SJonas Devlieghere        configuration.arch
452238dcc3SJonas Devlieghere        and configuration.arch == "x86_64"
462238dcc3SJonas Devlieghere        and platform.machine().startswith("arm64")
472238dcc3SJonas Devlieghere    ):
482238dcc3SJonas Devlieghere        return "/Library/Apple/usr/libexec/oah/debugserver"
4999298c7fSFred Riss
503ca7b2d0SPavel Labath    return _get_support_exe("debugserver")
51a61c247cSAntonio Afonso
522238dcc3SJonas Devlieghere
532238dcc3SJonas Devlieghere_LOG_LINE_REGEX = re.compile(
54ba66dfb1SDmitry Vasilyev    r"^(lldb-server|debugserver)\s+<\s*(\d+)>\s+(read|send)\s+packet:\s+(.+)$"
552238dcc3SJonas Devlieghere)
56a61c247cSAntonio Afonso
57a61c247cSAntonio Afonso
58a61c247cSAntonio Afonsodef _is_packet_lldb_gdbserver_input(packet_type, llgs_input_is_read):
59a61c247cSAntonio Afonso    """Return whether a given packet is input for lldb-gdbserver.
60a61c247cSAntonio Afonso
61a61c247cSAntonio Afonso    Args:
62a61c247cSAntonio Afonso        packet_type: a string indicating 'send' or 'receive', from a
63a61c247cSAntonio Afonso            gdbremote packet protocol log.
64a61c247cSAntonio Afonso
65a61c247cSAntonio Afonso        llgs_input_is_read: true if lldb-gdbserver input (content sent to
66a61c247cSAntonio Afonso            lldb-gdbserver) is listed as 'read' or 'send' in the packet
67a61c247cSAntonio Afonso            log entry.
68a61c247cSAntonio Afonso
69a61c247cSAntonio Afonso    Returns:
70a61c247cSAntonio Afonso        True if the packet should be considered input for lldb-gdbserver; False
71a61c247cSAntonio Afonso        otherwise.
72a61c247cSAntonio Afonso    """
732238dcc3SJonas Devlieghere    if packet_type == "read":
74a61c247cSAntonio Afonso        # when llgs is the read side, then a read packet is meant for
75a61c247cSAntonio Afonso        # input to llgs (when captured from the llgs/debugserver exe).
76a61c247cSAntonio Afonso        return llgs_input_is_read
772238dcc3SJonas Devlieghere    elif packet_type == "send":
78a61c247cSAntonio Afonso        # when llgs is the send side, then a send packet is meant to
79a61c247cSAntonio Afonso        # be input to llgs (when captured from the lldb exe).
80a61c247cSAntonio Afonso        return not llgs_input_is_read
81a61c247cSAntonio Afonso    else:
82a61c247cSAntonio Afonso        # don't understand what type of packet this is
83a61c247cSAntonio Afonso        raise "Unknown packet type: {}".format(packet_type)
84a61c247cSAntonio Afonso
85a61c247cSAntonio Afonso
862238dcc3SJonas Devlieghere_STRIP_CHECKSUM_REGEX = re.compile(r"#[0-9a-fA-F]{2}$")
87a61c247cSAntonio Afonso_STRIP_COMMAND_PREFIX_REGEX = re.compile(r"^\$")
88a61c247cSAntonio Afonso_STRIP_COMMAND_PREFIX_M_REGEX = re.compile(r"^\$m")
89a61c247cSAntonio Afonso
90a61c247cSAntonio Afonso
91a61c247cSAntonio Afonsodef assert_packets_equal(asserter, actual_packet, expected_packet):
92a61c247cSAntonio Afonso    # strip off the checksum digits of the packet.  When we're in
93a61c247cSAntonio Afonso    # no-ack mode, the # checksum is ignored, and should not be cause
94a61c247cSAntonio Afonso    # for a mismatched packet.
952238dcc3SJonas Devlieghere    actual_stripped = _STRIP_CHECKSUM_REGEX.sub("", actual_packet)
962238dcc3SJonas Devlieghere    expected_stripped = _STRIP_CHECKSUM_REGEX.sub("", expected_packet)
97a61c247cSAntonio Afonso    asserter.assertEqual(actual_stripped, expected_stripped)
98a61c247cSAntonio Afonso
99a61c247cSAntonio Afonso
100a61c247cSAntonio Afonsodef expect_lldb_gdbserver_replay(
1012238dcc3SJonas Devlieghere    asserter, server, test_sequence, timeout_seconds, logger=None
1022238dcc3SJonas Devlieghere):
103a61c247cSAntonio Afonso    """Replay socket communication with lldb-gdbserver and verify responses.
104a61c247cSAntonio Afonso
105a61c247cSAntonio Afonso    Args:
106a61c247cSAntonio Afonso        asserter: the object providing assertEqual(first, second, msg=None), e.g. TestCase instance.
107a61c247cSAntonio Afonso
108a61c247cSAntonio Afonso        test_sequence: a GdbRemoteTestSequence instance that describes
109a61c247cSAntonio Afonso            the messages sent to the gdb remote and the responses
110a61c247cSAntonio Afonso            expected from it.
111a61c247cSAntonio Afonso
112a61c247cSAntonio Afonso        timeout_seconds: any response taking more than this number of
113a61c247cSAntonio Afonso           seconds will cause an exception to be raised.
114a61c247cSAntonio Afonso
115a61c247cSAntonio Afonso        logger: a Python logger instance.
116a61c247cSAntonio Afonso
117a61c247cSAntonio Afonso    Returns:
118a61c247cSAntonio Afonso        The context dictionary from running the given gdbremote
119a61c247cSAntonio Afonso        protocol sequence.  This will contain any of the capture
120a61c247cSAntonio Afonso        elements specified to any GdbRemoteEntry instances in
121a61c247cSAntonio Afonso        test_sequence.
122a61c247cSAntonio Afonso
123a61c247cSAntonio Afonso        The context will also contain an entry, context["O_content"]
124a61c247cSAntonio Afonso        which contains the text from the inferior received via $O
125a61c247cSAntonio Afonso        packets.  $O packets should not attempt to be matched
126a61c247cSAntonio Afonso        directly since they are not entirely deterministic as to
127a61c247cSAntonio Afonso        how many arrive and how much text is in each one.
128a61c247cSAntonio Afonso
129a61c247cSAntonio Afonso        context["O_count"] will contain an integer of the number of
130a61c247cSAntonio Afonso        O packets received.
131a61c247cSAntonio Afonso    """
132a61c247cSAntonio Afonso
133a61c247cSAntonio Afonso    # Ensure we have some work to do.
134a61c247cSAntonio Afonso    if len(test_sequence.entries) < 1:
135a61c247cSAntonio Afonso        return {}
136a61c247cSAntonio Afonso
137a61c247cSAntonio Afonso    context = {"O_count": 0, "O_content": ""}
138a2f4f7daSPavel Labath
139a61c247cSAntonio Afonso    # Grab the first sequence entry.
140a61c247cSAntonio Afonso    sequence_entry = test_sequence.entries.pop(0)
141a61c247cSAntonio Afonso
142a61c247cSAntonio Afonso    # While we have an active sequence entry, send messages
143a61c247cSAntonio Afonso    # destined for the stub and collect/match/process responses
144a61c247cSAntonio Afonso    # expected from the stub.
145a61c247cSAntonio Afonso    while sequence_entry:
146a61c247cSAntonio Afonso        if sequence_entry.is_send_to_remote():
147a61c247cSAntonio Afonso            # This is an entry to send to the remote debug monitor.
148a61c247cSAntonio Afonso            send_packet = sequence_entry.get_send_packet()
149a61c247cSAntonio Afonso            if logger:
150a61c247cSAntonio Afonso                if len(send_packet) == 1 and send_packet[0] == chr(3):
151a61c247cSAntonio Afonso                    packet_desc = "^C"
152a61c247cSAntonio Afonso                else:
153a61c247cSAntonio Afonso                    packet_desc = send_packet
1542238dcc3SJonas Devlieghere                logger.info("sending packet to remote: {}".format(packet_desc))
155a2f4f7daSPavel Labath            server.send_raw(send_packet.encode())
156a61c247cSAntonio Afonso        else:
157a61c247cSAntonio Afonso            # This is an entry expecting to receive content from the remote
158a61c247cSAntonio Afonso            # debug monitor.
159a61c247cSAntonio Afonso
160a61c247cSAntonio Afonso            # We'll pull from (and wait on) the queue appropriate for the type of matcher.
161a61c247cSAntonio Afonso            # We keep separate queues for process output (coming from non-deterministic
162a61c247cSAntonio Afonso            # $O packet division) and for all other packets.
163a2f4f7daSPavel Labath            try:
164a61c247cSAntonio Afonso                if sequence_entry.is_output_matcher():
165a61c247cSAntonio Afonso                    # Grab next entry from the output queue.
166a2f4f7daSPavel Labath                    content = server.get_raw_output_packet()
167a61c247cSAntonio Afonso                else:
168a2f4f7daSPavel Labath                    content = server.get_raw_normal_packet()
169a2f4f7daSPavel Labath                content = seven.bitcast_to_string(content)
170a2f4f7daSPavel Labath            except socket.timeout:
171a2f4f7daSPavel Labath                asserter.fail(
1722238dcc3SJonas Devlieghere                    "timed out while waiting for '{}':\n{}".format(
1732238dcc3SJonas Devlieghere                        sequence_entry, server
1742238dcc3SJonas Devlieghere                    )
1752238dcc3SJonas Devlieghere                )
176a61c247cSAntonio Afonso
177a61c247cSAntonio Afonso            # Give the sequence entry the opportunity to match the content.
178a61c247cSAntonio Afonso            # Output matchers might match or pass after more output accumulates.
179a61c247cSAntonio Afonso            # Other packet types generally must match.
180a61c247cSAntonio Afonso            asserter.assertIsNotNone(content)
1812238dcc3SJonas Devlieghere            context = sequence_entry.assert_match(asserter, content, context=context)
182a61c247cSAntonio Afonso
183a61c247cSAntonio Afonso        # Move on to next sequence entry as needed.  Some sequence entries support executing multiple
184a61c247cSAntonio Afonso        # times in different states (for looping over query/response
185a61c247cSAntonio Afonso        # packets).
186a61c247cSAntonio Afonso        if sequence_entry.is_consumed():
187a61c247cSAntonio Afonso            if len(test_sequence.entries) > 0:
188a61c247cSAntonio Afonso                sequence_entry = test_sequence.entries.pop(0)
189a61c247cSAntonio Afonso            else:
190a61c247cSAntonio Afonso                sequence_entry = None
191a61c247cSAntonio Afonso
192a61c247cSAntonio Afonso    # Fill in the O_content entries.
193a61c247cSAntonio Afonso    context["O_count"] = 1
194a2f4f7daSPavel Labath    context["O_content"] = server.consume_accumulated_output()
195a61c247cSAntonio Afonso
196a61c247cSAntonio Afonso    return context
197a61c247cSAntonio Afonso
198a61c247cSAntonio Afonso
199a61c247cSAntonio Afonsodef gdbremote_hex_encode_string(str):
2002238dcc3SJonas Devlieghere    output = ""
201a61c247cSAntonio Afonso    for c in str:
2022238dcc3SJonas Devlieghere        output += "{0:02x}".format(ord(c))
203a61c247cSAntonio Afonso    return output
204a61c247cSAntonio Afonso
205a61c247cSAntonio Afonso
206a61c247cSAntonio Afonsodef gdbremote_hex_decode_string(str):
207a61c247cSAntonio Afonso    return str.decode("hex")
208a61c247cSAntonio Afonso
209a61c247cSAntonio Afonso
210a61c247cSAntonio Afonsodef gdbremote_packet_encode_string(str):
211a61c247cSAntonio Afonso    checksum = 0
212a61c247cSAntonio Afonso    for c in str:
213a61c247cSAntonio Afonso        checksum += ord(c)
2142238dcc3SJonas Devlieghere    return "$" + str + "#{0:02x}".format(checksum % 256)
215a61c247cSAntonio Afonso
216a61c247cSAntonio Afonso
217a61c247cSAntonio Afonsodef build_gdbremote_A_packet(args_list):
2182238dcc3SJonas Devlieghere    """Given a list of args, create a properly-formed $A packet containing each arg."""
219a61c247cSAntonio Afonso    payload = "A"
220a61c247cSAntonio Afonso
221a61c247cSAntonio Afonso    # build the arg content
222a61c247cSAntonio Afonso    arg_index = 0
223a61c247cSAntonio Afonso    for arg in args_list:
224a61c247cSAntonio Afonso        # Comma-separate the args.
225a61c247cSAntonio Afonso        if arg_index > 0:
2262238dcc3SJonas Devlieghere            payload += ","
227a61c247cSAntonio Afonso
228a61c247cSAntonio Afonso        # Hex-encode the arg.
229a61c247cSAntonio Afonso        hex_arg = gdbremote_hex_encode_string(arg)
230a61c247cSAntonio Afonso
231a61c247cSAntonio Afonso        # Build the A entry.
232a61c247cSAntonio Afonso        payload += "{},{},{}".format(len(hex_arg), arg_index, hex_arg)
233a61c247cSAntonio Afonso
234a61c247cSAntonio Afonso        # Next arg index, please.
235a61c247cSAntonio Afonso        arg_index += 1
236a61c247cSAntonio Afonso
237a61c247cSAntonio Afonso    # return the packetized payload
238a61c247cSAntonio Afonso    return gdbremote_packet_encode_string(payload)
239a61c247cSAntonio Afonso
240a61c247cSAntonio Afonso
241a61c247cSAntonio Afonsodef parse_reg_info_response(response_packet):
242a61c247cSAntonio Afonso    if not response_packet:
243a61c247cSAntonio Afonso        raise Exception("response_packet cannot be None")
244a61c247cSAntonio Afonso
245a61c247cSAntonio Afonso    # Strip off prefix $ and suffix #xx if present.
246a61c247cSAntonio Afonso    response_packet = _STRIP_COMMAND_PREFIX_REGEX.sub("", response_packet)
247a61c247cSAntonio Afonso    response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet)
248a61c247cSAntonio Afonso
249a61c247cSAntonio Afonso    # Build keyval pairs
250a61c247cSAntonio Afonso    values = {}
251a61c247cSAntonio Afonso    for kv in response_packet.split(";"):
252a61c247cSAntonio Afonso        if len(kv) < 1:
253a61c247cSAntonio Afonso            continue
2542238dcc3SJonas Devlieghere        (key, val) = kv.split(":")
255a61c247cSAntonio Afonso        values[key] = val
256a61c247cSAntonio Afonso
257a61c247cSAntonio Afonso    return values
258a61c247cSAntonio Afonso
259a61c247cSAntonio Afonso
260a61c247cSAntonio Afonsodef parse_threadinfo_response(response_packet):
261a61c247cSAntonio Afonso    if not response_packet:
262a61c247cSAntonio Afonso        raise Exception("response_packet cannot be None")
263a61c247cSAntonio Afonso
264a61c247cSAntonio Afonso    # Strip off prefix $ and suffix #xx if present.
265a61c247cSAntonio Afonso    response_packet = _STRIP_COMMAND_PREFIX_M_REGEX.sub("", response_packet)
266a61c247cSAntonio Afonso    response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet)
267a61c247cSAntonio Afonso
26875757c86SMichał Górny    for tid in response_packet.split(","):
26975757c86SMichał Górny        if not tid:
27075757c86SMichał Górny            continue
27175757c86SMichał Górny        if tid.startswith("p"):
27275757c86SMichał Górny            pid, _, tid = tid.partition(".")
27375757c86SMichał Górny            yield (int(pid[1:], 16), int(tid, 16))
27475757c86SMichał Górny        else:
27575757c86SMichał Górny            yield int(tid, 16)
276a61c247cSAntonio Afonso
277a61c247cSAntonio Afonso
278a61c247cSAntonio Afonsodef unpack_endian_binary_string(endian, value_string):
279a61c247cSAntonio Afonso    """Unpack a gdb-remote binary (post-unescaped, i.e. not escaped) response to an unsigned int given endianness of the inferior."""
280a61c247cSAntonio Afonso    if not endian:
281a61c247cSAntonio Afonso        raise Exception("endian cannot be None")
282a61c247cSAntonio Afonso    if not value_string or len(value_string) < 1:
283a61c247cSAntonio Afonso        raise Exception("value_string cannot be None or empty")
284a61c247cSAntonio Afonso
2852238dcc3SJonas Devlieghere    if endian == "little":
286a61c247cSAntonio Afonso        value = 0
287a61c247cSAntonio Afonso        i = 0
288a61c247cSAntonio Afonso        while len(value_string) > 0:
2892238dcc3SJonas Devlieghere            value += ord(value_string[0]) << i
290a61c247cSAntonio Afonso            value_string = value_string[1:]
291a61c247cSAntonio Afonso            i += 8
292a61c247cSAntonio Afonso        return value
2932238dcc3SJonas Devlieghere    elif endian == "big":
294a61c247cSAntonio Afonso        value = 0
295a61c247cSAntonio Afonso        while len(value_string) > 0:
296a61c247cSAntonio Afonso            value = (value << 8) + ord(value_string[0])
297a61c247cSAntonio Afonso            value_string = value_string[1:]
298a61c247cSAntonio Afonso        return value
299a61c247cSAntonio Afonso    else:
300a61c247cSAntonio Afonso        # pdp is valid but need to add parse code once needed.
301a61c247cSAntonio Afonso        raise Exception("unsupported endian:{}".format(endian))
302a61c247cSAntonio Afonso
303a61c247cSAntonio Afonso
304a61c247cSAntonio Afonsodef unpack_register_hex_unsigned(endian, value_string):
305a61c247cSAntonio Afonso    """Unpack a gdb-remote $p-style response to an unsigned int given endianness of inferior."""
306a61c247cSAntonio Afonso    if not endian:
307a61c247cSAntonio Afonso        raise Exception("endian cannot be None")
308a61c247cSAntonio Afonso    if not value_string or len(value_string) < 1:
309a61c247cSAntonio Afonso        raise Exception("value_string cannot be None or empty")
310a61c247cSAntonio Afonso
3112238dcc3SJonas Devlieghere    if endian == "little":
312a61c247cSAntonio Afonso        value = 0
313a61c247cSAntonio Afonso        i = 0
314a61c247cSAntonio Afonso        while len(value_string) > 0:
3152238dcc3SJonas Devlieghere            value += int(value_string[0:2], 16) << i
316a61c247cSAntonio Afonso            value_string = value_string[2:]
317a61c247cSAntonio Afonso            i += 8
318a61c247cSAntonio Afonso        return value
3192238dcc3SJonas Devlieghere    elif endian == "big":
320a61c247cSAntonio Afonso        return int(value_string, 16)
321a61c247cSAntonio Afonso    else:
322a61c247cSAntonio Afonso        # pdp is valid but need to add parse code once needed.
323a61c247cSAntonio Afonso        raise Exception("unsupported endian:{}".format(endian))
324a61c247cSAntonio Afonso
325a61c247cSAntonio Afonso
326a61c247cSAntonio Afonsodef pack_register_hex(endian, value, byte_size=None):
327a61c247cSAntonio Afonso    """Unpack a gdb-remote $p-style response to an unsigned int given endianness of inferior."""
328a61c247cSAntonio Afonso    if not endian:
329a61c247cSAntonio Afonso        raise Exception("endian cannot be None")
330a61c247cSAntonio Afonso
3312238dcc3SJonas Devlieghere    if endian == "little":
332a61c247cSAntonio Afonso        # Create the litt-endian return value.
333a61c247cSAntonio Afonso        retval = ""
334a61c247cSAntonio Afonso        while value != 0:
3352238dcc3SJonas Devlieghere            retval = retval + "{:02x}".format(value & 0xFF)
336a61c247cSAntonio Afonso            value = value >> 8
337a61c247cSAntonio Afonso        if byte_size:
338a61c247cSAntonio Afonso            # Add zero-fill to the right/end (MSB side) of the value.
339a61c247cSAntonio Afonso            retval += "00" * (byte_size - len(retval) // 2)
340a61c247cSAntonio Afonso        return retval
341a61c247cSAntonio Afonso
3422238dcc3SJonas Devlieghere    elif endian == "big":
343a61c247cSAntonio Afonso        retval = ""
344a61c247cSAntonio Afonso        while value != 0:
3452238dcc3SJonas Devlieghere            retval = "{:02x}".format(value & 0xFF) + retval
346a61c247cSAntonio Afonso            value = value >> 8
347a61c247cSAntonio Afonso        if byte_size:
348a61c247cSAntonio Afonso            # Add zero-fill to the left/front (MSB side) of the value.
349a61c247cSAntonio Afonso            retval = ("00" * (byte_size - len(retval) // 2)) + retval
350a61c247cSAntonio Afonso        return retval
351a61c247cSAntonio Afonso
352a61c247cSAntonio Afonso    else:
353a61c247cSAntonio Afonso        # pdp is valid but need to add parse code once needed.
354a61c247cSAntonio Afonso        raise Exception("unsupported endian:{}".format(endian))
355a61c247cSAntonio Afonso
356a61c247cSAntonio Afonso
357a61c247cSAntonio Afonsoclass GdbRemoteEntryBase(object):
358a61c247cSAntonio Afonso    def is_output_matcher(self):
359a61c247cSAntonio Afonso        return False
360a61c247cSAntonio Afonso
361a61c247cSAntonio Afonso
362a61c247cSAntonio Afonsoclass GdbRemoteEntry(GdbRemoteEntryBase):
363a61c247cSAntonio Afonso    def __init__(
3642238dcc3SJonas Devlieghere        self, is_send_to_remote=True, exact_payload=None, regex=None, capture=None
3652238dcc3SJonas Devlieghere    ):
366a61c247cSAntonio Afonso        """Create an entry representing one piece of the I/O to/from a gdb remote debug monitor.
367a61c247cSAntonio Afonso
368a61c247cSAntonio Afonso        Args:
369a61c247cSAntonio Afonso
370a61c247cSAntonio Afonso            is_send_to_remote: True if this entry is a message to be
371a61c247cSAntonio Afonso                sent to the gdbremote debug monitor; False if this
372a61c247cSAntonio Afonso                entry represents text to be matched against the reply
373a61c247cSAntonio Afonso                from the gdbremote debug monitor.
374a61c247cSAntonio Afonso
375a61c247cSAntonio Afonso            exact_payload: if not None, then this packet is an exact
376a61c247cSAntonio Afonso                send (when sending to the remote) or an exact match of
377a61c247cSAntonio Afonso                the response from the gdbremote. The checksums are
378a61c247cSAntonio Afonso                ignored on exact match requests since negotiation of
379a61c247cSAntonio Afonso                no-ack makes the checksum content essentially
380a61c247cSAntonio Afonso                undefined.
381a61c247cSAntonio Afonso
382ded66049SPavel Labath            regex: currently only valid for receives from gdbremote.  When
383ded66049SPavel Labath                specified (and only if exact_payload is None), indicates the
384ded66049SPavel Labath                gdbremote response must match the given regex. Match groups in
385ded66049SPavel Labath                the regex can be used for the matching portion (see capture
386ded66049SPavel Labath                arg). It is perfectly valid to have just a regex arg without a
387ded66049SPavel Labath                capture arg. This arg only makes sense if exact_payload is not
388a61c247cSAntonio Afonso                specified.
389a61c247cSAntonio Afonso
390a61c247cSAntonio Afonso            capture: if specified, is a dictionary of regex match
391a61c247cSAntonio Afonso                group indices (should start with 1) to variable names
392a61c247cSAntonio Afonso                that will store the capture group indicated by the
393a61c247cSAntonio Afonso                index. For example, {1:"thread_id"} will store capture
394a61c247cSAntonio Afonso                group 1's content in the context dictionary where
395a61c247cSAntonio Afonso                "thread_id" is the key and the match group value is
396ded66049SPavel Labath                the value. This arg only makes sense when regex is specified.
397a61c247cSAntonio Afonso        """
398a61c247cSAntonio Afonso        self._is_send_to_remote = is_send_to_remote
399a61c247cSAntonio Afonso        self.exact_payload = exact_payload
400a61c247cSAntonio Afonso        self.regex = regex
401a61c247cSAntonio Afonso        self.capture = capture
402a61c247cSAntonio Afonso
403a61c247cSAntonio Afonso    def is_send_to_remote(self):
404a61c247cSAntonio Afonso        return self._is_send_to_remote
405a61c247cSAntonio Afonso
406a61c247cSAntonio Afonso    def is_consumed(self):
407a61c247cSAntonio Afonso        # For now, all packets are consumed after first use.
408a61c247cSAntonio Afonso        return True
409a61c247cSAntonio Afonso
410a61c247cSAntonio Afonso    def get_send_packet(self):
411a61c247cSAntonio Afonso        if not self.is_send_to_remote():
412a61c247cSAntonio Afonso            raise Exception(
4132238dcc3SJonas Devlieghere                "get_send_packet() called on GdbRemoteEntry that is not a send-to-remote packet"
4142238dcc3SJonas Devlieghere            )
415a61c247cSAntonio Afonso        if not self.exact_payload:
416a61c247cSAntonio Afonso            raise Exception(
4172238dcc3SJonas Devlieghere                "get_send_packet() called on GdbRemoteEntry but it doesn't have an exact payload"
4182238dcc3SJonas Devlieghere            )
419a61c247cSAntonio Afonso        return self.exact_payload
420a61c247cSAntonio Afonso
421a61c247cSAntonio Afonso    def _assert_exact_payload_match(self, asserter, actual_packet):
422a61c247cSAntonio Afonso        assert_packets_equal(asserter, actual_packet, self.exact_payload)
423a61c247cSAntonio Afonso        return None
424a61c247cSAntonio Afonso
425a61c247cSAntonio Afonso    def _assert_regex_match(self, asserter, actual_packet, context):
426a61c247cSAntonio Afonso        # Ensure the actual packet matches from the start of the actual packet.
427a61c247cSAntonio Afonso        match = self.regex.match(actual_packet)
428a61c247cSAntonio Afonso        if not match:
429a61c247cSAntonio Afonso            asserter.fail(
430a61c247cSAntonio Afonso                "regex '{}' failed to match against content '{}'".format(
4312238dcc3SJonas Devlieghere                    self.regex.pattern, actual_packet
4322238dcc3SJonas Devlieghere                )
4332238dcc3SJonas Devlieghere            )
434a61c247cSAntonio Afonso
435a61c247cSAntonio Afonso        if self.capture:
436a61c247cSAntonio Afonso            # Handle captures.
437a61c247cSAntonio Afonso            for group_index, var_name in list(self.capture.items()):
438a61c247cSAntonio Afonso                capture_text = match.group(group_index)
439a61c247cSAntonio Afonso                # It is okay for capture text to be None - which it will be if it is a group that can match nothing.
440a61c247cSAntonio Afonso                # The user must be okay with it since the regex itself matched
441a61c247cSAntonio Afonso                # above.
442a61c247cSAntonio Afonso                context[var_name] = capture_text
443a61c247cSAntonio Afonso
444a61c247cSAntonio Afonso        return context
445a61c247cSAntonio Afonso
446a61c247cSAntonio Afonso    def assert_match(self, asserter, actual_packet, context=None):
447a61c247cSAntonio Afonso        # This only makes sense for matching lines coming from the
448a61c247cSAntonio Afonso        # remote debug monitor.
449a61c247cSAntonio Afonso        if self.is_send_to_remote():
450a61c247cSAntonio Afonso            raise Exception(
4512238dcc3SJonas Devlieghere                "Attempted to match a packet being sent to the remote debug monitor, doesn't make sense."
4522238dcc3SJonas Devlieghere            )
453a61c247cSAntonio Afonso
454a61c247cSAntonio Afonso        # Create a new context if needed.
455a61c247cSAntonio Afonso        if not context:
456a61c247cSAntonio Afonso            context = {}
457a61c247cSAntonio Afonso
458a61c247cSAntonio Afonso        # If this is an exact payload, ensure they match exactly,
459a61c247cSAntonio Afonso        # ignoring the packet checksum which is optional for no-ack
460a61c247cSAntonio Afonso        # mode.
461a61c247cSAntonio Afonso        if self.exact_payload:
462a61c247cSAntonio Afonso            self._assert_exact_payload_match(asserter, actual_packet)
463a61c247cSAntonio Afonso            return context
464a61c247cSAntonio Afonso        elif self.regex:
465a61c247cSAntonio Afonso            return self._assert_regex_match(asserter, actual_packet, context)
466a61c247cSAntonio Afonso        else:
467a61c247cSAntonio Afonso            raise Exception(
4682238dcc3SJonas Devlieghere                "Don't know how to match a remote-sent packet when exact_payload isn't specified."
4692238dcc3SJonas Devlieghere            )
470a61c247cSAntonio Afonso
471a61c247cSAntonio Afonso
472a61c247cSAntonio Afonsoclass MultiResponseGdbRemoteEntry(GdbRemoteEntryBase):
473a61c247cSAntonio Afonso    """Represents a query/response style packet.
474a61c247cSAntonio Afonso
475a61c247cSAntonio Afonso    Assumes the first item is sent to the gdb remote.
476a61c247cSAntonio Afonso    An end sequence regex indicates the end of the query/response
477a61c247cSAntonio Afonso    packet sequence.  All responses up through (but not including) the
478a61c247cSAntonio Afonso    end response are stored in a context variable.
479a61c247cSAntonio Afonso
480a61c247cSAntonio Afonso    Settings accepted from params:
481a61c247cSAntonio Afonso
482a61c247cSAntonio Afonso        next_query or query: required.  The typical query packet without the $ prefix or #xx suffix.
483a61c247cSAntonio Afonso            If there is a special first packet to start the iteration query, see the
484a61c247cSAntonio Afonso            first_query key.
485a61c247cSAntonio Afonso
486a61c247cSAntonio Afonso        first_query: optional. If the first query requires a special query command, specify
487a61c247cSAntonio Afonso            it with this key.  Do not specify the $ prefix or #xx suffix.
488a61c247cSAntonio Afonso
489a61c247cSAntonio Afonso        append_iteration_suffix: defaults to False.  Specify True if the 0-based iteration
490a61c247cSAntonio Afonso            index should be appended as a suffix to the command.  e.g. qRegisterInfo with
491a61c247cSAntonio Afonso            this key set true will generate query packets of qRegisterInfo0, qRegisterInfo1,
492a61c247cSAntonio Afonso            etc.
493a61c247cSAntonio Afonso
494a61c247cSAntonio Afonso        end_regex: required. Specifies a compiled regex object that will match the full text
495a61c247cSAntonio Afonso            of any response that signals an end to the iteration.  It must include the
496a61c247cSAntonio Afonso            initial $ and ending #xx and must match the whole packet.
497a61c247cSAntonio Afonso
498a61c247cSAntonio Afonso        save_key: required.  Specifies the key within the context where an array will be stored.
499a61c247cSAntonio Afonso            Each packet received from the gdb remote that does not match the end_regex will get
500a61c247cSAntonio Afonso            appended to the array stored within the context at that key.
501a61c247cSAntonio Afonso
502a61c247cSAntonio Afonso        runaway_response_count: optional. Defaults to 10000. If this many responses are retrieved,
503a61c247cSAntonio Afonso            assume there is something wrong with either the response collection or the ending
504a61c247cSAntonio Afonso            detection regex and throw an exception.
505a61c247cSAntonio Afonso    """
506a61c247cSAntonio Afonso
507a61c247cSAntonio Afonso    def __init__(self, params):
508a61c247cSAntonio Afonso        self._next_query = params.get("next_query", params.get("query"))
509a61c247cSAntonio Afonso        if not self._next_query:
510a61c247cSAntonio Afonso            raise "either next_query or query key must be specified for MultiResponseGdbRemoteEntry"
511a61c247cSAntonio Afonso
512a61c247cSAntonio Afonso        self._first_query = params.get("first_query", self._next_query)
5132238dcc3SJonas Devlieghere        self._append_iteration_suffix = params.get("append_iteration_suffix", False)
514a61c247cSAntonio Afonso        self._iteration = 0
515a61c247cSAntonio Afonso        self._end_regex = params["end_regex"]
516a61c247cSAntonio Afonso        self._save_key = params["save_key"]
5172238dcc3SJonas Devlieghere        self._runaway_response_count = params.get("runaway_response_count", 10000)
518a61c247cSAntonio Afonso        self._is_send_to_remote = True
519a61c247cSAntonio Afonso        self._end_matched = False
520a61c247cSAntonio Afonso
521a61c247cSAntonio Afonso    def is_send_to_remote(self):
522a61c247cSAntonio Afonso        return self._is_send_to_remote
523a61c247cSAntonio Afonso
524a61c247cSAntonio Afonso    def get_send_packet(self):
525a61c247cSAntonio Afonso        if not self.is_send_to_remote():
526a61c247cSAntonio Afonso            raise Exception(
5272238dcc3SJonas Devlieghere                "get_send_packet() called on MultiResponseGdbRemoteEntry that is not in the send state"
5282238dcc3SJonas Devlieghere            )
529a61c247cSAntonio Afonso        if self._end_matched:
530a61c247cSAntonio Afonso            raise Exception(
5312238dcc3SJonas Devlieghere                "get_send_packet() called on MultiResponseGdbRemoteEntry but end of query/response sequence has already been seen."
5322238dcc3SJonas Devlieghere            )
533a61c247cSAntonio Afonso
534a61c247cSAntonio Afonso        # Choose the first or next query for the base payload.
535a61c247cSAntonio Afonso        if self._iteration == 0 and self._first_query:
536a61c247cSAntonio Afonso            payload = self._first_query
537a61c247cSAntonio Afonso        else:
538a61c247cSAntonio Afonso            payload = self._next_query
539a61c247cSAntonio Afonso
540a61c247cSAntonio Afonso        # Append the suffix as needed.
541a61c247cSAntonio Afonso        if self._append_iteration_suffix:
542a61c247cSAntonio Afonso            payload += "%x" % self._iteration
543a61c247cSAntonio Afonso
544a61c247cSAntonio Afonso        # Keep track of the iteration.
545a61c247cSAntonio Afonso        self._iteration += 1
546a61c247cSAntonio Afonso
547a61c247cSAntonio Afonso        # Now that we've given the query packet, flip the mode to
548a61c247cSAntonio Afonso        # receive/match.
549a61c247cSAntonio Afonso        self._is_send_to_remote = False
550a61c247cSAntonio Afonso
551a61c247cSAntonio Afonso        # Return the result, converted to packet form.
552a61c247cSAntonio Afonso        return gdbremote_packet_encode_string(payload)
553a61c247cSAntonio Afonso
554a61c247cSAntonio Afonso    def is_consumed(self):
555a61c247cSAntonio Afonso        return self._end_matched
556a61c247cSAntonio Afonso
557a61c247cSAntonio Afonso    def assert_match(self, asserter, actual_packet, context=None):
558a61c247cSAntonio Afonso        # This only makes sense for matching lines coming from the remote debug
559a61c247cSAntonio Afonso        # monitor.
560a61c247cSAntonio Afonso        if self.is_send_to_remote():
561a61c247cSAntonio Afonso            raise Exception(
5622238dcc3SJonas Devlieghere                "assert_match() called on MultiResponseGdbRemoteEntry but state is set to send a query packet."
5632238dcc3SJonas Devlieghere            )
564a61c247cSAntonio Afonso
565a61c247cSAntonio Afonso        if self._end_matched:
566a61c247cSAntonio Afonso            raise Exception(
5672238dcc3SJonas Devlieghere                "assert_match() called on MultiResponseGdbRemoteEntry but end of query/response sequence has already been seen."
5682238dcc3SJonas Devlieghere            )
569a61c247cSAntonio Afonso
570a61c247cSAntonio Afonso        # Set up a context as needed.
571a61c247cSAntonio Afonso        if not context:
572a61c247cSAntonio Afonso            context = {}
573a61c247cSAntonio Afonso
574a61c247cSAntonio Afonso        # Check if the packet matches the end condition.
575a61c247cSAntonio Afonso        match = self._end_regex.match(actual_packet)
576a61c247cSAntonio Afonso        if match:
577a61c247cSAntonio Afonso            # We're done iterating.
578a61c247cSAntonio Afonso            self._end_matched = True
579a61c247cSAntonio Afonso            return context
580a61c247cSAntonio Afonso
581a61c247cSAntonio Afonso        # Not done iterating - save the packet.
582a61c247cSAntonio Afonso        context[self._save_key] = context.get(self._save_key, [])
583a61c247cSAntonio Afonso        context[self._save_key].append(actual_packet)
584a61c247cSAntonio Afonso
585a61c247cSAntonio Afonso        # Check for a runaway response cycle.
586a61c247cSAntonio Afonso        if len(context[self._save_key]) >= self._runaway_response_count:
587a61c247cSAntonio Afonso            raise Exception(
5882238dcc3SJonas Devlieghere                "runaway query/response cycle detected: %d responses captured so far. Last response: %s"
5892238dcc3SJonas Devlieghere                % (len(context[self._save_key]), context[self._save_key][-1])
5902238dcc3SJonas Devlieghere            )
591a61c247cSAntonio Afonso
592a61c247cSAntonio Afonso        # Flip the mode to send for generating the query.
593a61c247cSAntonio Afonso        self._is_send_to_remote = True
594a61c247cSAntonio Afonso        return context
595a61c247cSAntonio Afonso
596a61c247cSAntonio Afonso
597a61c247cSAntonio Afonsoclass MatchRemoteOutputEntry(GdbRemoteEntryBase):
598a61c247cSAntonio Afonso    """Waits for output from the debug monitor to match a regex or time out.
599a61c247cSAntonio Afonso
600a61c247cSAntonio Afonso    This entry type tries to match each time new gdb remote output is accumulated
601a61c247cSAntonio Afonso    using a provided regex.  If the output does not match the regex within the
602a61c247cSAntonio Afonso    given timeframe, the command fails the playback session.  If the regex does
603a61c247cSAntonio Afonso    match, any capture fields are recorded in the context.
604a61c247cSAntonio Afonso
605a61c247cSAntonio Afonso    Settings accepted from params:
606a61c247cSAntonio Afonso
607a61c247cSAntonio Afonso        regex: required. Specifies a compiled regex object that must either succeed
608a61c247cSAntonio Afonso            with re.match or re.search (see regex_mode below) within the given timeout
609a61c247cSAntonio Afonso            (see timeout_seconds below) or cause the playback to fail.
610a61c247cSAntonio Afonso
611a61c247cSAntonio Afonso        regex_mode: optional. Available values: "match" or "search". If "match", the entire
612a61c247cSAntonio Afonso            stub output as collected so far must match the regex.  If search, then the regex
613a61c247cSAntonio Afonso            must match starting somewhere within the output text accumulated thus far.
614a61c247cSAntonio Afonso            Default: "match" (i.e. the regex must match the entirety of the accumulated output
615a61c247cSAntonio Afonso            buffer, so unexpected text will generally fail the match).
616a61c247cSAntonio Afonso
617a61c247cSAntonio Afonso        capture: optional.  If specified, is a dictionary of regex match group indices (should start
618a61c247cSAntonio Afonso            with 1) to variable names that will store the capture group indicated by the
619a61c247cSAntonio Afonso            index. For example, {1:"thread_id"} will store capture group 1's content in the
620a61c247cSAntonio Afonso            context dictionary where "thread_id" is the key and the match group value is
621ded66049SPavel Labath            the value. This arg only makes sense when regex is specified.
622a61c247cSAntonio Afonso    """
623a61c247cSAntonio Afonso
624a61c247cSAntonio Afonso    def __init__(self, regex=None, regex_mode="match", capture=None):
625a61c247cSAntonio Afonso        self._regex = regex
626a61c247cSAntonio Afonso        self._regex_mode = regex_mode
627a61c247cSAntonio Afonso        self._capture = capture
628a61c247cSAntonio Afonso        self._matched = False
629a61c247cSAntonio Afonso
630a61c247cSAntonio Afonso        if not self._regex:
631a61c247cSAntonio Afonso            raise Exception("regex cannot be None")
632a61c247cSAntonio Afonso
633a61c247cSAntonio Afonso        if not self._regex_mode in ["match", "search"]:
634a61c247cSAntonio Afonso            raise Exception(
6352238dcc3SJonas Devlieghere                'unsupported regex mode "{}": must be "match" or "search"'.format(
6362238dcc3SJonas Devlieghere                    self._regex_mode
6372238dcc3SJonas Devlieghere                )
6382238dcc3SJonas Devlieghere            )
639a61c247cSAntonio Afonso
640a61c247cSAntonio Afonso    def is_output_matcher(self):
641a61c247cSAntonio Afonso        return True
642a61c247cSAntonio Afonso
643a61c247cSAntonio Afonso    def is_send_to_remote(self):
644a61c247cSAntonio Afonso        # This is always a "wait for remote" command.
645a61c247cSAntonio Afonso        return False
646a61c247cSAntonio Afonso
647a61c247cSAntonio Afonso    def is_consumed(self):
648a61c247cSAntonio Afonso        return self._matched
649a61c247cSAntonio Afonso
650a61c247cSAntonio Afonso    def assert_match(self, asserter, accumulated_output, context):
651a61c247cSAntonio Afonso        # Validate args.
652a61c247cSAntonio Afonso        if not accumulated_output:
653a61c247cSAntonio Afonso            raise Exception("accumulated_output cannot be none")
654a61c247cSAntonio Afonso        if not context:
655a61c247cSAntonio Afonso            raise Exception("context cannot be none")
656a61c247cSAntonio Afonso
657a61c247cSAntonio Afonso        # Validate that we haven't already matched.
658a61c247cSAntonio Afonso        if self._matched:
659a61c247cSAntonio Afonso            raise Exception(
6602238dcc3SJonas Devlieghere                "invalid state - already matched, attempting to match again"
6612238dcc3SJonas Devlieghere            )
662a61c247cSAntonio Afonso
663a61c247cSAntonio Afonso        # If we don't have any content yet, we don't match.
664a61c247cSAntonio Afonso        if len(accumulated_output) < 1:
665a61c247cSAntonio Afonso            return context
666a61c247cSAntonio Afonso
667a61c247cSAntonio Afonso        # Check if we match
668a61c247cSAntonio Afonso        if self._regex_mode == "match":
669a61c247cSAntonio Afonso            match = self._regex.match(accumulated_output)
670a61c247cSAntonio Afonso        elif self._regex_mode == "search":
671a61c247cSAntonio Afonso            match = self._regex.search(accumulated_output)
672a61c247cSAntonio Afonso        else:
6732238dcc3SJonas Devlieghere            raise Exception("Unexpected regex mode: {}".format(self._regex_mode))
674a61c247cSAntonio Afonso
675a61c247cSAntonio Afonso        # If we don't match, wait to try again after next $O content, or time
676a61c247cSAntonio Afonso        # out.
677a61c247cSAntonio Afonso        if not match:
678a61c247cSAntonio Afonso            # print("re pattern \"{}\" did not match against \"{}\"".format(self._regex.pattern, accumulated_output))
679a61c247cSAntonio Afonso            return context
680a61c247cSAntonio Afonso
681a61c247cSAntonio Afonso        # We do match.
682a61c247cSAntonio Afonso        self._matched = True
683a61c247cSAntonio Afonso        # print("re pattern \"{}\" matched against \"{}\"".format(self._regex.pattern, accumulated_output))
684a61c247cSAntonio Afonso
685a61c247cSAntonio Afonso        # Collect up any captures into the context.
686a61c247cSAntonio Afonso        if self._capture:
687a61c247cSAntonio Afonso            # Handle captures.
688a61c247cSAntonio Afonso            for group_index, var_name in list(self._capture.items()):
689a61c247cSAntonio Afonso                capture_text = match.group(group_index)
690a61c247cSAntonio Afonso                if not capture_text:
6912238dcc3SJonas Devlieghere                    raise Exception("No content for group index {}".format(group_index))
692a61c247cSAntonio Afonso                context[var_name] = capture_text
693a61c247cSAntonio Afonso
694a61c247cSAntonio Afonso        return context
695a61c247cSAntonio Afonso
696a61c247cSAntonio Afonso
697a61c247cSAntonio Afonsoclass GdbRemoteTestSequence(object):
6982238dcc3SJonas Devlieghere    _LOG_LINE_REGEX = re.compile(r"^.*(read|send)\s+packet:\s+(.+)$")
699a61c247cSAntonio Afonso
700a61c247cSAntonio Afonso    def __init__(self, logger):
701a61c247cSAntonio Afonso        self.entries = []
702a61c247cSAntonio Afonso        self.logger = logger
703a61c247cSAntonio Afonso
704c888694aSMuhammad Omair Javaid    def __len__(self):
705c888694aSMuhammad Omair Javaid        return len(self.entries)
706c888694aSMuhammad Omair Javaid
707a61c247cSAntonio Afonso    def add_log_lines(self, log_lines, remote_input_is_read):
708a61c247cSAntonio Afonso        for line in log_lines:
709a61c247cSAntonio Afonso            if isinstance(line, str):
710a61c247cSAntonio Afonso                # Handle log line import
711a61c247cSAntonio Afonso                # if self.logger:
712a61c247cSAntonio Afonso                #     self.logger.debug("processing log line: {}".format(line))
713a61c247cSAntonio Afonso                match = self._LOG_LINE_REGEX.match(line)
714a61c247cSAntonio Afonso                if match:
715a61c247cSAntonio Afonso                    playback_packet = match.group(2)
716a61c247cSAntonio Afonso                    direction = match.group(1)
7172238dcc3SJonas Devlieghere                    if _is_packet_lldb_gdbserver_input(direction, remote_input_is_read):
718a61c247cSAntonio Afonso                        # Handle as something to send to the remote debug monitor.
719a61c247cSAntonio Afonso                        # if self.logger:
720a61c247cSAntonio Afonso                        #     self.logger.info("processed packet to send to remote: {}".format(playback_packet))
721a61c247cSAntonio Afonso                        self.entries.append(
722a61c247cSAntonio Afonso                            GdbRemoteEntry(
7232238dcc3SJonas Devlieghere                                is_send_to_remote=True, exact_payload=playback_packet
7242238dcc3SJonas Devlieghere                            )
7252238dcc3SJonas Devlieghere                        )
726a61c247cSAntonio Afonso                    else:
727a61c247cSAntonio Afonso                        # Log line represents content to be expected from the remote debug monitor.
728a61c247cSAntonio Afonso                        # if self.logger:
729a61c247cSAntonio Afonso                        #     self.logger.info("receiving packet from llgs, should match: {}".format(playback_packet))
730a61c247cSAntonio Afonso                        self.entries.append(
731a61c247cSAntonio Afonso                            GdbRemoteEntry(
7322238dcc3SJonas Devlieghere                                is_send_to_remote=False, exact_payload=playback_packet
7332238dcc3SJonas Devlieghere                            )
7342238dcc3SJonas Devlieghere                        )
735a61c247cSAntonio Afonso                else:
7362238dcc3SJonas Devlieghere                    raise Exception("failed to interpret log line: {}".format(line))
737a61c247cSAntonio Afonso            elif isinstance(line, dict):
738a61c247cSAntonio Afonso                entry_type = line.get("type", "regex_capture")
739a61c247cSAntonio Afonso                if entry_type == "regex_capture":
740a61c247cSAntonio Afonso                    # Handle more explicit control over details via dictionary.
741a61c247cSAntonio Afonso                    direction = line.get("direction", None)
742a61c247cSAntonio Afonso                    regex = line.get("regex", None)
743a61c247cSAntonio Afonso                    capture = line.get("capture", None)
744a61c247cSAntonio Afonso
745a61c247cSAntonio Afonso                    # Compile the regex.
746a61c247cSAntonio Afonso                    if regex and (isinstance(regex, str)):
747ff94f602SJan Kratochvil                        regex = re.compile(regex, re.DOTALL)
748a61c247cSAntonio Afonso
7492238dcc3SJonas Devlieghere                    if _is_packet_lldb_gdbserver_input(direction, remote_input_is_read):
750a61c247cSAntonio Afonso                        # Handle as something to send to the remote debug monitor.
751a61c247cSAntonio Afonso                        # if self.logger:
752a61c247cSAntonio Afonso                        #     self.logger.info("processed dict sequence to send to remote")
753a61c247cSAntonio Afonso                        self.entries.append(
754a61c247cSAntonio Afonso                            GdbRemoteEntry(
7552238dcc3SJonas Devlieghere                                is_send_to_remote=True, regex=regex, capture=capture
7562238dcc3SJonas Devlieghere                            )
7572238dcc3SJonas Devlieghere                        )
758a61c247cSAntonio Afonso                    else:
759a61c247cSAntonio Afonso                        # Log line represents content to be expected from the remote debug monitor.
760a61c247cSAntonio Afonso                        # if self.logger:
761a61c247cSAntonio Afonso                        #     self.logger.info("processed dict sequence to match receiving from remote")
762a61c247cSAntonio Afonso                        self.entries.append(
763a61c247cSAntonio Afonso                            GdbRemoteEntry(
7642238dcc3SJonas Devlieghere                                is_send_to_remote=False, regex=regex, capture=capture
7652238dcc3SJonas Devlieghere                            )
7662238dcc3SJonas Devlieghere                        )
767a61c247cSAntonio Afonso                elif entry_type == "multi_response":
768a61c247cSAntonio Afonso                    self.entries.append(MultiResponseGdbRemoteEntry(line))
769a61c247cSAntonio Afonso                elif entry_type == "output_match":
770a61c247cSAntonio Afonso                    regex = line.get("regex", None)
771a61c247cSAntonio Afonso                    # Compile the regex.
772a61c247cSAntonio Afonso                    if regex and (isinstance(regex, str)):
773a61c247cSAntonio Afonso                        regex = re.compile(regex, re.DOTALL)
774a61c247cSAntonio Afonso
775a61c247cSAntonio Afonso                    regex_mode = line.get("regex_mode", "match")
776a61c247cSAntonio Afonso                    capture = line.get("capture", None)
777a61c247cSAntonio Afonso                    self.entries.append(
778a61c247cSAntonio Afonso                        MatchRemoteOutputEntry(
7792238dcc3SJonas Devlieghere                            regex=regex, regex_mode=regex_mode, capture=capture
7802238dcc3SJonas Devlieghere                        )
7812238dcc3SJonas Devlieghere                    )
782a61c247cSAntonio Afonso                else:
7832238dcc3SJonas Devlieghere                    raise Exception('unknown entry type "%s"' % entry_type)
784a61c247cSAntonio Afonso
785a61c247cSAntonio Afonso
786a61c247cSAntonio Afonsodef process_is_running(pid, unknown_value=True):
787a61c247cSAntonio Afonso    """If possible, validate that the given pid represents a running process on the local system.
788a61c247cSAntonio Afonso
789a61c247cSAntonio Afonso    Args:
790a61c247cSAntonio Afonso
791a61c247cSAntonio Afonso        pid: an OS-specific representation of a process id.  Should be an integral value.
792a61c247cSAntonio Afonso
793a61c247cSAntonio Afonso        unknown_value: value used when we cannot determine how to check running local
794a61c247cSAntonio Afonso        processes on the OS.
795a61c247cSAntonio Afonso
796a61c247cSAntonio Afonso    Returns:
797a61c247cSAntonio Afonso
798a61c247cSAntonio Afonso        If we can figure out how to check running process ids on the given OS:
799a61c247cSAntonio Afonso        return True if the process is running, or False otherwise.
800a61c247cSAntonio Afonso
801a61c247cSAntonio Afonso        If we don't know how to check running process ids on the given OS:
802a61c247cSAntonio Afonso        return the value provided by the unknown_value arg.
803a61c247cSAntonio Afonso    """
80456f9cfe3SDave Lee    if not isinstance(pid, int):
805a61c247cSAntonio Afonso        raise Exception(
8062238dcc3SJonas Devlieghere            "pid must be an integral type (actual type: %s)" % str(type(pid))
8072238dcc3SJonas Devlieghere        )
808a61c247cSAntonio Afonso
809a61c247cSAntonio Afonso    process_ids = []
810a61c247cSAntonio Afonso
811a61c247cSAntonio Afonso    if lldb.remote_platform:
812a61c247cSAntonio Afonso        # Don't know how to get list of running process IDs on a remote
813a61c247cSAntonio Afonso        # platform
814a61c247cSAntonio Afonso        return unknown_value
8152238dcc3SJonas Devlieghere    elif platform.system() in ["Darwin", "Linux", "FreeBSD", "NetBSD"]:
816a61c247cSAntonio Afonso        # Build the list of running process ids
817a61c247cSAntonio Afonso        output = subprocess.check_output(
8182238dcc3SJonas Devlieghere            "ps ax | awk '{ print $1; }'", shell=True
8192238dcc3SJonas Devlieghere        ).decode("utf-8")
8202238dcc3SJonas Devlieghere        text_process_ids = output.split("\n")[1:]
821a61c247cSAntonio Afonso        # Convert text pids to ints
8222238dcc3SJonas Devlieghere        process_ids = [int(text_pid) for text_pid in text_process_ids if text_pid != ""]
8232238dcc3SJonas Devlieghere    elif platform.system() == "Windows":
8242a39024aSAaron Smith        output = subprocess.check_output(
8252238dcc3SJonas Devlieghere            'for /f "tokens=2 delims=," %F in (\'tasklist /nh /fi "PID ne 0" /fo csv\') do @echo %~F',
8262238dcc3SJonas Devlieghere            shell=True,
8272238dcc3SJonas Devlieghere        ).decode("utf-8")
8282238dcc3SJonas Devlieghere        text_process_ids = output.split("\n")[1:]
8292238dcc3SJonas Devlieghere        process_ids = [int(text_pid) for text_pid in text_process_ids if text_pid != ""]
830a61c247cSAntonio Afonso    # elif {your_platform_here}:
831a61c247cSAntonio Afonso    #   fill in process_ids as a list of int type process IDs running on
832a61c247cSAntonio Afonso    #   the local system.
833a61c247cSAntonio Afonso    else:
834a61c247cSAntonio Afonso        # Don't know how to get list of running process IDs on this
835a61c247cSAntonio Afonso        # OS, so return the "don't know" value.
836a61c247cSAntonio Afonso        return unknown_value
837a61c247cSAntonio Afonso
838a61c247cSAntonio Afonso    # Check if the pid is in the process_ids
839a61c247cSAntonio Afonso    return pid in process_ids
840a61c247cSAntonio Afonso
8411903f358SMichał Górny
842a2f4f7daSPavel Labathdef _handle_output_packet_string(packet_contents):
8431903f358SMichał Górny    # Warning: in non-stop mode, we currently handle only the first output
8441903f358SMichał Górny    # packet since we'd need to inject vStdio packets
8451903f358SMichał Górny    if not packet_contents.startswith((b"$O", b"%Stdio:O")):
846a2f4f7daSPavel Labath        return None
8471903f358SMichał Górny    elif packet_contents == b"$OK":
848a2f4f7daSPavel Labath        return None
849a61c247cSAntonio Afonso    else:
8501903f358SMichał Górny        return binascii.unhexlify(packet_contents.partition(b"O")[2])
8511903f358SMichał Górny
852a2f4f7daSPavel Labath
853a2f4f7daSPavel Labathclass Server(object):
8542238dcc3SJonas Devlieghere    _GDB_REMOTE_PACKET_REGEX = re.compile(rb"^([\$%][^\#]*)#[0-9a-fA-F]{2}")
855a2f4f7daSPavel Labath
856a2f4f7daSPavel Labath    class ChecksumMismatch(Exception):
857a2f4f7daSPavel Labath        pass
858a2f4f7daSPavel Labath
859a2f4f7daSPavel Labath    def __init__(self, sock, proc=None):
860a2f4f7daSPavel Labath        self._accumulated_output = b""
861a2f4f7daSPavel Labath        self._receive_buffer = b""
862a2f4f7daSPavel Labath        self._normal_queue = []
863a2f4f7daSPavel Labath        self._output_queue = []
864a2f4f7daSPavel Labath        self._sock = sock
865a2f4f7daSPavel Labath        self._proc = proc
866a2f4f7daSPavel Labath
867a2f4f7daSPavel Labath    def send_raw(self, frame):
868a2f4f7daSPavel Labath        self._sock.sendall(frame)
869a2f4f7daSPavel Labath
870872b1da6SPavel Labath    def send_ack(self):
871872b1da6SPavel Labath        self.send_raw(b"+")
872872b1da6SPavel Labath
873872b1da6SPavel Labath    def send_packet(self, packet):
8742238dcc3SJonas Devlieghere        self.send_raw(b"$%s#%02x" % (packet, self._checksum(packet)))
875872b1da6SPavel Labath
876872b1da6SPavel Labath    @staticmethod
877872b1da6SPavel Labath    def _checksum(packet):
878872b1da6SPavel Labath        checksum = 0
87956f9cfe3SDave Lee        for c in iter(packet):
880872b1da6SPavel Labath            checksum += c
881872b1da6SPavel Labath        return checksum % 256
882872b1da6SPavel Labath
883a2f4f7daSPavel Labath    def _read(self, q):
884a2f4f7daSPavel Labath        while not q:
885a2f4f7daSPavel Labath            new_bytes = self._sock.recv(4096)
886a2f4f7daSPavel Labath            self._process_new_bytes(new_bytes)
887a2f4f7daSPavel Labath        return q.pop(0)
888a2f4f7daSPavel Labath
889a2f4f7daSPavel Labath    def _process_new_bytes(self, new_bytes):
890a2f4f7daSPavel Labath        # Add new bytes to our accumulated unprocessed packet bytes.
891a2f4f7daSPavel Labath        self._receive_buffer += new_bytes
892a2f4f7daSPavel Labath
893a2f4f7daSPavel Labath        # Parse fully-formed packets into individual packets.
894a2f4f7daSPavel Labath        has_more = len(self._receive_buffer) > 0
895a2f4f7daSPavel Labath        while has_more:
896a2f4f7daSPavel Labath            if len(self._receive_buffer) <= 0:
897a2f4f7daSPavel Labath                has_more = False
898a2f4f7daSPavel Labath            # handle '+' ack
899a2f4f7daSPavel Labath            elif self._receive_buffer[0:1] == b"+":
900a2f4f7daSPavel Labath                self._normal_queue += [b"+"]
901a2f4f7daSPavel Labath                self._receive_buffer = self._receive_buffer[1:]
902a2f4f7daSPavel Labath            else:
9032238dcc3SJonas Devlieghere                packet_match = self._GDB_REMOTE_PACKET_REGEX.match(self._receive_buffer)
904a2f4f7daSPavel Labath                if packet_match:
905a2f4f7daSPavel Labath                    # Our receive buffer matches a packet at the
906a2f4f7daSPavel Labath                    # start of the receive buffer.
907a2f4f7daSPavel Labath                    new_output_content = _handle_output_packet_string(
9082238dcc3SJonas Devlieghere                        packet_match.group(1)
9092238dcc3SJonas Devlieghere                    )
910a2f4f7daSPavel Labath                    if new_output_content:
911a2f4f7daSPavel Labath                        # This was an $O packet with new content.
912a2f4f7daSPavel Labath                        self._accumulated_output += new_output_content
913a2f4f7daSPavel Labath                        self._output_queue += [self._accumulated_output]
914a2f4f7daSPavel Labath                    else:
915a2f4f7daSPavel Labath                        # Any packet other than $O.
916a2f4f7daSPavel Labath                        self._normal_queue += [packet_match.group(0)]
917a2f4f7daSPavel Labath
918a2f4f7daSPavel Labath                    # Remove the parsed packet from the receive
919a2f4f7daSPavel Labath                    # buffer.
920a2f4f7daSPavel Labath                    self._receive_buffer = self._receive_buffer[
9212238dcc3SJonas Devlieghere                        len(packet_match.group(0)) :
9222238dcc3SJonas Devlieghere                    ]
923a2f4f7daSPavel Labath                else:
924a2f4f7daSPavel Labath                    # We don't have enough in the receive bufferto make a full
925a2f4f7daSPavel Labath                    # packet. Stop trying until we read more.
926a2f4f7daSPavel Labath                    has_more = False
927a2f4f7daSPavel Labath
928a2f4f7daSPavel Labath    def get_raw_output_packet(self):
929a2f4f7daSPavel Labath        return self._read(self._output_queue)
930a2f4f7daSPavel Labath
931a2f4f7daSPavel Labath    def get_raw_normal_packet(self):
932a2f4f7daSPavel Labath        return self._read(self._normal_queue)
933a2f4f7daSPavel Labath
934872b1da6SPavel Labath    @staticmethod
935872b1da6SPavel Labath    def _get_payload(frame):
936872b1da6SPavel Labath        payload = frame[1:-3]
937872b1da6SPavel Labath        checksum = int(frame[-2:], 16)
938872b1da6SPavel Labath        if checksum != Server._checksum(payload):
939872b1da6SPavel Labath            raise ChecksumMismatch
940872b1da6SPavel Labath        return payload
941872b1da6SPavel Labath
942872b1da6SPavel Labath    def get_normal_packet(self):
943872b1da6SPavel Labath        frame = self.get_raw_normal_packet()
9442238dcc3SJonas Devlieghere        if frame == b"+":
9452238dcc3SJonas Devlieghere            return frame
946872b1da6SPavel Labath        return self._get_payload(frame)
947872b1da6SPavel Labath
948a2f4f7daSPavel Labath    def get_accumulated_output(self):
949a2f4f7daSPavel Labath        return self._accumulated_output
950a2f4f7daSPavel Labath
951a2f4f7daSPavel Labath    def consume_accumulated_output(self):
952a2f4f7daSPavel Labath        output = self._accumulated_output
953a2f4f7daSPavel Labath        self._accumulated_output = b""
954a2f4f7daSPavel Labath        return output
955a2f4f7daSPavel Labath
956a2f4f7daSPavel Labath    def __str__(self):
9572238dcc3SJonas Devlieghere        return dedent(
9582238dcc3SJonas Devlieghere            """\
959a2f4f7daSPavel Labath            server '{}' on '{}'
960a2f4f7daSPavel Labath            _receive_buffer: {}
961a2f4f7daSPavel Labath            _normal_queue: {}
962a2f4f7daSPavel Labath            _output_queue: {}
963a2f4f7daSPavel Labath            _accumulated_output: {}
9642238dcc3SJonas Devlieghere            """
9652238dcc3SJonas Devlieghere        ).format(
9662238dcc3SJonas Devlieghere            self._proc,
9672238dcc3SJonas Devlieghere            self._sock,
9682238dcc3SJonas Devlieghere            self._receive_buffer,
9692238dcc3SJonas Devlieghere            self._normal_queue,
9702238dcc3SJonas Devlieghere            self._output_queue,
9712238dcc3SJonas Devlieghere            self._accumulated_output,
9722238dcc3SJonas Devlieghere        )
973a52be0ccSSanthosh Kumar Ellendula
974a52be0ccSSanthosh Kumar Ellendula
975a52be0ccSSanthosh Kumar Ellendula# A class representing a pipe for communicating with debug server.
976a52be0ccSSanthosh Kumar Ellendula# This class includes menthods to open the pipe and read the port number from it.
977a52be0ccSSanthosh Kumar Ellendulaif lldbplatformutil.getHostPlatform() == "windows":
978a52be0ccSSanthosh Kumar Ellendula    import ctypes
979a52be0ccSSanthosh Kumar Ellendula    import ctypes.wintypes
980a52be0ccSSanthosh Kumar Ellendula    from ctypes.wintypes import BOOL, DWORD, HANDLE, LPCWSTR, LPDWORD, LPVOID
981a52be0ccSSanthosh Kumar Ellendula
982a52be0ccSSanthosh Kumar Ellendula    kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
983a52be0ccSSanthosh Kumar Ellendula
984a52be0ccSSanthosh Kumar Ellendula    PIPE_ACCESS_INBOUND = 1
985a52be0ccSSanthosh Kumar Ellendula    FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000
986a52be0ccSSanthosh Kumar Ellendula    FILE_FLAG_OVERLAPPED = 0x40000000
987a52be0ccSSanthosh Kumar Ellendula    PIPE_TYPE_BYTE = 0
988a52be0ccSSanthosh Kumar Ellendula    PIPE_REJECT_REMOTE_CLIENTS = 8
989a52be0ccSSanthosh Kumar Ellendula    INVALID_HANDLE_VALUE = -1
990a52be0ccSSanthosh Kumar Ellendula    ERROR_ACCESS_DENIED = 5
991a52be0ccSSanthosh Kumar Ellendula    ERROR_IO_PENDING = 997
992a52be0ccSSanthosh Kumar Ellendula
993a52be0ccSSanthosh Kumar Ellendula    class OVERLAPPED(ctypes.Structure):
994a52be0ccSSanthosh Kumar Ellendula        _fields_ = [
995a52be0ccSSanthosh Kumar Ellendula            ("Internal", LPVOID),
996a52be0ccSSanthosh Kumar Ellendula            ("InternalHigh", LPVOID),
997a52be0ccSSanthosh Kumar Ellendula            ("Offset", DWORD),
998a52be0ccSSanthosh Kumar Ellendula            ("OffsetHigh", DWORD),
999a52be0ccSSanthosh Kumar Ellendula            ("hEvent", HANDLE),
1000a52be0ccSSanthosh Kumar Ellendula        ]
1001a52be0ccSSanthosh Kumar Ellendula
1002a52be0ccSSanthosh Kumar Ellendula        def __init__(self):
1003a52be0ccSSanthosh Kumar Ellendula            super(OVERLAPPED, self).__init__(
1004a52be0ccSSanthosh Kumar Ellendula                Internal=0, InternalHigh=0, Offset=0, OffsetHigh=0, hEvent=None
1005a52be0ccSSanthosh Kumar Ellendula            )
1006a52be0ccSSanthosh Kumar Ellendula
1007a52be0ccSSanthosh Kumar Ellendula    LPOVERLAPPED = ctypes.POINTER(OVERLAPPED)
1008a52be0ccSSanthosh Kumar Ellendula
1009a52be0ccSSanthosh Kumar Ellendula    CreateNamedPipe = kernel32.CreateNamedPipeW
1010a52be0ccSSanthosh Kumar Ellendula    CreateNamedPipe.restype = HANDLE
1011a52be0ccSSanthosh Kumar Ellendula    CreateNamedPipe.argtypes = (
1012a52be0ccSSanthosh Kumar Ellendula        LPCWSTR,
1013a52be0ccSSanthosh Kumar Ellendula        DWORD,
1014a52be0ccSSanthosh Kumar Ellendula        DWORD,
1015a52be0ccSSanthosh Kumar Ellendula        DWORD,
1016a52be0ccSSanthosh Kumar Ellendula        DWORD,
1017a52be0ccSSanthosh Kumar Ellendula        DWORD,
1018a52be0ccSSanthosh Kumar Ellendula        DWORD,
1019a52be0ccSSanthosh Kumar Ellendula        LPVOID,
1020a52be0ccSSanthosh Kumar Ellendula    )
1021a52be0ccSSanthosh Kumar Ellendula
1022a52be0ccSSanthosh Kumar Ellendula    ConnectNamedPipe = kernel32.ConnectNamedPipe
1023a52be0ccSSanthosh Kumar Ellendula    ConnectNamedPipe.restype = BOOL
1024a52be0ccSSanthosh Kumar Ellendula    ConnectNamedPipe.argtypes = (HANDLE, LPOVERLAPPED)
1025a52be0ccSSanthosh Kumar Ellendula
1026a52be0ccSSanthosh Kumar Ellendula    CreateEvent = kernel32.CreateEventW
1027a52be0ccSSanthosh Kumar Ellendula    CreateEvent.restype = HANDLE
1028a52be0ccSSanthosh Kumar Ellendula    CreateEvent.argtypes = (LPVOID, BOOL, BOOL, LPCWSTR)
1029a52be0ccSSanthosh Kumar Ellendula
1030a52be0ccSSanthosh Kumar Ellendula    GetOverlappedResultEx = kernel32.GetOverlappedResultEx
1031a52be0ccSSanthosh Kumar Ellendula    GetOverlappedResultEx.restype = BOOL
1032a52be0ccSSanthosh Kumar Ellendula    GetOverlappedResultEx.argtypes = (HANDLE, LPOVERLAPPED, LPDWORD, DWORD, BOOL)
1033a52be0ccSSanthosh Kumar Ellendula
1034a52be0ccSSanthosh Kumar Ellendula    ReadFile = kernel32.ReadFile
1035a52be0ccSSanthosh Kumar Ellendula    ReadFile.restype = BOOL
1036a52be0ccSSanthosh Kumar Ellendula    ReadFile.argtypes = (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED)
1037a52be0ccSSanthosh Kumar Ellendula
1038a52be0ccSSanthosh Kumar Ellendula    CloseHandle = kernel32.CloseHandle
1039a52be0ccSSanthosh Kumar Ellendula    CloseHandle.restype = BOOL
1040a52be0ccSSanthosh Kumar Ellendula    CloseHandle.argtypes = (HANDLE,)
1041a52be0ccSSanthosh Kumar Ellendula
1042a52be0ccSSanthosh Kumar Ellendula    class Pipe(object):
1043a52be0ccSSanthosh Kumar Ellendula        def __init__(self, prefix):
1044a52be0ccSSanthosh Kumar Ellendula            while True:
1045*857700ffSKendal Harland                self.name = "lldb-" + str(random.randrange(10**10))
1046a52be0ccSSanthosh Kumar Ellendula                full_name = "\\\\.\\pipe\\" + self.name
1047a52be0ccSSanthosh Kumar Ellendula                self._handle = CreateNamedPipe(
1048a52be0ccSSanthosh Kumar Ellendula                    full_name,
1049a52be0ccSSanthosh Kumar Ellendula                    PIPE_ACCESS_INBOUND
1050a52be0ccSSanthosh Kumar Ellendula                    | FILE_FLAG_FIRST_PIPE_INSTANCE
1051a52be0ccSSanthosh Kumar Ellendula                    | FILE_FLAG_OVERLAPPED,
1052a52be0ccSSanthosh Kumar Ellendula                    PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,
1053a52be0ccSSanthosh Kumar Ellendula                    1,
1054a52be0ccSSanthosh Kumar Ellendula                    4096,
1055a52be0ccSSanthosh Kumar Ellendula                    4096,
1056a52be0ccSSanthosh Kumar Ellendula                    0,
1057a52be0ccSSanthosh Kumar Ellendula                    None,
1058a52be0ccSSanthosh Kumar Ellendula                )
1059a52be0ccSSanthosh Kumar Ellendula                if self._handle != INVALID_HANDLE_VALUE:
1060a52be0ccSSanthosh Kumar Ellendula                    break
1061a52be0ccSSanthosh Kumar Ellendula                if ctypes.get_last_error() != ERROR_ACCESS_DENIED:
1062a52be0ccSSanthosh Kumar Ellendula                    raise ctypes.WinError(ctypes.get_last_error())
1063a52be0ccSSanthosh Kumar Ellendula
1064a52be0ccSSanthosh Kumar Ellendula            self._overlapped = OVERLAPPED()
1065a52be0ccSSanthosh Kumar Ellendula            self._overlapped.hEvent = CreateEvent(None, True, False, None)
1066a52be0ccSSanthosh Kumar Ellendula            result = ConnectNamedPipe(self._handle, self._overlapped)
1067a52be0ccSSanthosh Kumar Ellendula            assert result == 0
1068a52be0ccSSanthosh Kumar Ellendula            if ctypes.get_last_error() != ERROR_IO_PENDING:
1069a52be0ccSSanthosh Kumar Ellendula                raise ctypes.WinError(ctypes.get_last_error())
1070a52be0ccSSanthosh Kumar Ellendula
1071a52be0ccSSanthosh Kumar Ellendula        def finish_connection(self, timeout):
1072a52be0ccSSanthosh Kumar Ellendula            if not GetOverlappedResultEx(
1073a52be0ccSSanthosh Kumar Ellendula                self._handle,
1074a52be0ccSSanthosh Kumar Ellendula                self._overlapped,
1075a52be0ccSSanthosh Kumar Ellendula                ctypes.byref(DWORD(0)),
1076a52be0ccSSanthosh Kumar Ellendula                timeout * 1000,
1077a52be0ccSSanthosh Kumar Ellendula                True,
1078a52be0ccSSanthosh Kumar Ellendula            ):
1079a52be0ccSSanthosh Kumar Ellendula                raise ctypes.WinError(ctypes.get_last_error())
1080a52be0ccSSanthosh Kumar Ellendula
1081a52be0ccSSanthosh Kumar Ellendula        def read(self, size, timeout):
1082a52be0ccSSanthosh Kumar Ellendula            buf = ctypes.create_string_buffer(size)
1083a52be0ccSSanthosh Kumar Ellendula            if not ReadFile(
1084a52be0ccSSanthosh Kumar Ellendula                self._handle, ctypes.byref(buf), size, None, self._overlapped
1085a52be0ccSSanthosh Kumar Ellendula            ):
1086a52be0ccSSanthosh Kumar Ellendula                if ctypes.get_last_error() != ERROR_IO_PENDING:
1087a52be0ccSSanthosh Kumar Ellendula                    raise ctypes.WinError(ctypes.get_last_error())
1088a52be0ccSSanthosh Kumar Ellendula            read = DWORD(0)
1089a52be0ccSSanthosh Kumar Ellendula            if not GetOverlappedResultEx(
1090a52be0ccSSanthosh Kumar Ellendula                self._handle, self._overlapped, ctypes.byref(read), timeout * 1000, True
1091a52be0ccSSanthosh Kumar Ellendula            ):
1092a52be0ccSSanthosh Kumar Ellendula                raise ctypes.WinError(ctypes.get_last_error())
1093a52be0ccSSanthosh Kumar Ellendula            return buf.raw[0 : read.value]
1094a52be0ccSSanthosh Kumar Ellendula
1095a52be0ccSSanthosh Kumar Ellendula        def close(self):
1096a52be0ccSSanthosh Kumar Ellendula            CloseHandle(self._overlapped.hEvent)
1097a52be0ccSSanthosh Kumar Ellendula            CloseHandle(self._handle)
1098a52be0ccSSanthosh Kumar Ellendula
1099a52be0ccSSanthosh Kumar Ellendulaelse:
1100a52be0ccSSanthosh Kumar Ellendula
1101a52be0ccSSanthosh Kumar Ellendula    class Pipe(object):
1102a52be0ccSSanthosh Kumar Ellendula        def __init__(self, prefix):
1103a52be0ccSSanthosh Kumar Ellendula            self.name = os.path.join(prefix, "stub_port_number")
1104a52be0ccSSanthosh Kumar Ellendula            os.mkfifo(self.name)
1105a52be0ccSSanthosh Kumar Ellendula            self._fd = os.open(self.name, os.O_RDONLY | os.O_NONBLOCK)
1106a52be0ccSSanthosh Kumar Ellendula
1107a52be0ccSSanthosh Kumar Ellendula        def finish_connection(self, timeout):
1108a52be0ccSSanthosh Kumar Ellendula            pass
1109a52be0ccSSanthosh Kumar Ellendula
1110a52be0ccSSanthosh Kumar Ellendula        def read(self, size, timeout):
1111a52be0ccSSanthosh Kumar Ellendula            (readers, _, _) = select.select([self._fd], [], [], timeout)
1112a52be0ccSSanthosh Kumar Ellendula            if self._fd not in readers:
1113a52be0ccSSanthosh Kumar Ellendula                raise TimeoutError
1114a52be0ccSSanthosh Kumar Ellendula            return os.read(self._fd, size)
1115a52be0ccSSanthosh Kumar Ellendula
1116a52be0ccSSanthosh Kumar Ellendula        def close(self):
1117a52be0ccSSanthosh Kumar Ellendula            os.close(self._fd)
1118