xref: /llvm-project/lldb/examples/python/gdbremote.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1#!/usr/bin/env python
2
3# ----------------------------------------------------------------------
4# This module will enable GDB remote packet logging when the
5# 'start_gdb_log' command is called with a filename to log to. When the
6# 'stop_gdb_log' command is called, it will disable the logging and
7# print out statistics about how long commands took to execute and also
8# will primnt ou
9# Be sure to add the python path that points to the LLDB shared library.
10#
11# To use this in the embedded python interpreter using "lldb" just
12# import it with the full path using the "command script import"
13# command. This can be done from the LLDB command line:
14#   (lldb) command script import /path/to/gdbremote.py
15# Or it can be added to your ~/.lldbinit file so this module is always
16# available.
17# ----------------------------------------------------------------------
18
19import binascii
20import subprocess
21import json
22import math
23import optparse
24import os
25import re
26import shlex
27import string
28import sys
29import tempfile
30import xml.etree.ElementTree as ET
31
32# ----------------------------------------------------------------------
33# Global variables
34# ----------------------------------------------------------------------
35g_log_file = ""
36g_byte_order = "little"
37g_number_regex = re.compile("^(0x[0-9a-fA-F]+|[0-9]+)")
38g_thread_id_regex = re.compile("^(-1|[0-9a-fA-F]+|0)")
39
40
41class TerminalColors:
42    """Simple terminal colors class"""
43
44    def __init__(self, enabled=True):
45        # TODO: discover terminal type from "file" and disable if
46        # it can't handle the color codes
47        self.enabled = enabled
48
49    def reset(self):
50        """Reset all terminal colors and formatting."""
51        if self.enabled:
52            return "\x1b[0m"
53        return ""
54
55    def bold(self, on=True):
56        """Enable or disable bold depending on the "on" parameter."""
57        if self.enabled:
58            if on:
59                return "\x1b[1m"
60            else:
61                return "\x1b[22m"
62        return ""
63
64    def italics(self, on=True):
65        """Enable or disable italics depending on the "on" parameter."""
66        if self.enabled:
67            if on:
68                return "\x1b[3m"
69            else:
70                return "\x1b[23m"
71        return ""
72
73    def underline(self, on=True):
74        """Enable or disable underline depending on the "on" parameter."""
75        if self.enabled:
76            if on:
77                return "\x1b[4m"
78            else:
79                return "\x1b[24m"
80        return ""
81
82    def inverse(self, on=True):
83        """Enable or disable inverse depending on the "on" parameter."""
84        if self.enabled:
85            if on:
86                return "\x1b[7m"
87            else:
88                return "\x1b[27m"
89        return ""
90
91    def strike(self, on=True):
92        """Enable or disable strike through depending on the "on" parameter."""
93        if self.enabled:
94            if on:
95                return "\x1b[9m"
96            else:
97                return "\x1b[29m"
98        return ""
99
100    def black(self, fg=True):
101        """Set the foreground or background color to black.
102        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
103        """
104        if self.enabled:
105            if fg:
106                return "\x1b[30m"
107            else:
108                return "\x1b[40m"
109        return ""
110
111    def red(self, fg=True):
112        """Set the foreground or background color to red.
113        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
114        """
115        if self.enabled:
116            if fg:
117                return "\x1b[31m"
118            else:
119                return "\x1b[41m"
120        return ""
121
122    def green(self, fg=True):
123        """Set the foreground or background color to green.
124        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
125        """
126        if self.enabled:
127            if fg:
128                return "\x1b[32m"
129            else:
130                return "\x1b[42m"
131        return ""
132
133    def yellow(self, fg=True):
134        """Set the foreground or background color to yellow.
135        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
136        """
137        if self.enabled:
138            if fg:
139                return "\x1b[33m"
140            else:
141                return "\x1b[43m"
142        return ""
143
144    def blue(self, fg=True):
145        """Set the foreground or background color to blue.
146        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
147        """
148        if self.enabled:
149            if fg:
150                return "\x1b[34m"
151            else:
152                return "\x1b[44m"
153        return ""
154
155    def magenta(self, fg=True):
156        """Set the foreground or background color to magenta.
157        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
158        """
159        if self.enabled:
160            if fg:
161                return "\x1b[35m"
162            else:
163                return "\x1b[45m"
164        return ""
165
166    def cyan(self, fg=True):
167        """Set the foreground or background color to cyan.
168        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
169        """
170        if self.enabled:
171            if fg:
172                return "\x1b[36m"
173            else:
174                return "\x1b[46m"
175        return ""
176
177    def white(self, fg=True):
178        """Set the foreground or background color to white.
179        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
180        """
181        if self.enabled:
182            if fg:
183                return "\x1b[37m"
184            else:
185                return "\x1b[47m"
186        return ""
187
188    def default(self, fg=True):
189        """Set the foreground or background color to the default.
190        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.
191        """
192        if self.enabled:
193            if fg:
194                return "\x1b[39m"
195            else:
196                return "\x1b[49m"
197        return ""
198
199
200def start_gdb_log(debugger, command, result, dict):
201    """Start logging GDB remote packets by enabling logging with timestamps and
202    thread safe logging. Follow a call to this function with a call to "stop_gdb_log"
203    in order to dump out the commands."""
204    global g_log_file
205    command_args = shlex.split(command)
206    usage = "usage: start_gdb_log [options] [<LOGFILEPATH>]"
207    description = """The command enables GDB remote packet logging with timestamps. The packets will be logged to <LOGFILEPATH> if supplied, or a temporary file will be used. Logging stops when stop_gdb_log is called and the packet times will
208    be aggregated and displayed."""
209    parser = optparse.OptionParser(
210        description=description, prog="start_gdb_log", usage=usage
211    )
212    parser.add_option(
213        "-v",
214        "--verbose",
215        action="store_true",
216        dest="verbose",
217        help="display verbose debug info",
218        default=False,
219    )
220    try:
221        (options, args) = parser.parse_args(command_args)
222    except:
223        return
224
225    if g_log_file:
226        result.PutCString(
227            'error: logging is already in progress with file "%s"' % g_log_file
228        )
229    else:
230        args_len = len(args)
231        if args_len == 0:
232            g_log_file = tempfile.mktemp()
233        elif len(args) == 1:
234            g_log_file = args[0]
235
236        if g_log_file:
237            debugger.HandleCommand(
238                'log enable --threadsafe --timestamp --file "%s" gdb-remote packets'
239                % g_log_file
240            )
241            result.PutCString(
242                "GDB packet logging enable with log file '%s'\nUse the 'stop_gdb_log' command to stop logging and show packet statistics."
243                % g_log_file
244            )
245            return
246
247        result.PutCString("error: invalid log file path")
248    result.PutCString(usage)
249
250
251def stop_gdb_log(debugger, command, result, dict):
252    """Stop logging GDB remote packets to the file that was specified in a call
253    to "start_gdb_log" and normalize the timestamps to be relative to the first
254    timestamp in the log file. Also print out statistics for how long each
255    command took to allow performance bottlenecks to be determined."""
256    global g_log_file
257    # Any commands whose names might be followed by more valid C identifier
258    # characters must be listed here
259    command_args = shlex.split(command)
260    usage = "usage: stop_gdb_log [options]"
261    description = """The command stops a previously enabled GDB remote packet logging command. Packet logging must have been previously enabled with a call to start_gdb_log."""
262    parser = optparse.OptionParser(
263        description=description, prog="stop_gdb_log", usage=usage
264    )
265    parser.add_option(
266        "-v",
267        "--verbose",
268        action="store_true",
269        dest="verbose",
270        help="display verbose debug info",
271        default=False,
272    )
273    parser.add_option(
274        "--plot",
275        action="store_true",
276        dest="plot",
277        help="plot packet latencies by packet type",
278        default=False,
279    )
280    parser.add_option(
281        "-q",
282        "--quiet",
283        action="store_true",
284        dest="quiet",
285        help="display verbose debug info",
286        default=False,
287    )
288    parser.add_option(
289        "-C",
290        "--color",
291        action="store_true",
292        dest="color",
293        help="add terminal colors",
294        default=False,
295    )
296    parser.add_option(
297        "-c",
298        "--sort-by-count",
299        action="store_true",
300        dest="sort_count",
301        help="display verbose debug info",
302        default=False,
303    )
304    parser.add_option(
305        "-s",
306        "--symbolicate",
307        action="store_true",
308        dest="symbolicate",
309        help='symbolicate addresses in log using current "lldb.target"',
310        default=False,
311    )
312    try:
313        (options, args) = parser.parse_args(command_args)
314    except:
315        return
316    options.colors = TerminalColors(options.color)
317    options.symbolicator = None
318    if options.symbolicate:
319        if lldb.target:
320            import lldb.utils.symbolication
321
322            options.symbolicator = lldb.utils.symbolication.Symbolicator()
323            options.symbolicator.target = lldb.target
324        else:
325            print("error: can't symbolicate without a target")
326
327    if not g_log_file:
328        result.PutCString(
329            'error: logging must have been previously enabled with a call to "stop_gdb_log"'
330        )
331    elif os.path.exists(g_log_file):
332        if len(args) == 0:
333            debugger.HandleCommand("log disable gdb-remote packets")
334            result.PutCString(
335                "GDB packet logging disabled. Logged packets are in '%s'" % g_log_file
336            )
337            parse_gdb_log_file(g_log_file, options)
338        else:
339            result.PutCString(usage)
340    else:
341        print('error: the GDB packet log file "%s" does not exist' % g_log_file)
342
343
344def is_hex_byte(str):
345    if len(str) == 2:
346        return str[0] in string.hexdigits and str[1] in string.hexdigits
347    return False
348
349
350def get_hex_string_if_all_printable(str):
351    try:
352        s = binascii.unhexlify(str).decode()
353        if all(c in string.printable for c in s):
354            return s
355    except (TypeError, binascii.Error, UnicodeDecodeError):
356        pass
357    return None
358
359
360# global register info list
361g_register_infos = list()
362g_max_register_info_name_len = 0
363
364
365class RegisterInfo:
366    """Class that represents register information"""
367
368    def __init__(self, kvp):
369        self.info = dict()
370        for kv in kvp:
371            key = kv[0]
372            value = kv[1]
373            self.info[key] = value
374
375    def name(self):
376        """Get the name of the register."""
377        if self.info and "name" in self.info:
378            return self.info["name"]
379        return None
380
381    def bit_size(self):
382        """Get the size in bits of the register."""
383        if self.info and "bitsize" in self.info:
384            return int(self.info["bitsize"])
385        return 0
386
387    def byte_size(self):
388        """Get the size in bytes of the register."""
389        return self.bit_size() / 8
390
391    def get_value_from_hex_string(self, hex_str):
392        """Dump the register value given a native byte order encoded hex ASCII byte string."""
393        encoding = self.info["encoding"]
394        bit_size = self.bit_size()
395        packet = Packet(hex_str)
396        if encoding == "uint":
397            uval = packet.get_hex_uint(g_byte_order)
398            if bit_size == 8:
399                return "0x%2.2x" % (uval)
400            elif bit_size == 16:
401                return "0x%4.4x" % (uval)
402            elif bit_size == 32:
403                return "0x%8.8x" % (uval)
404            elif bit_size == 64:
405                return "0x%16.16x" % (uval)
406        bytes = list()
407        uval = packet.get_hex_uint8()
408        while uval is not None:
409            bytes.append(uval)
410            uval = packet.get_hex_uint8()
411        value_str = "0x"
412        if g_byte_order == "little":
413            bytes.reverse()
414        for byte in bytes:
415            value_str += "%2.2x" % byte
416        return "%s" % (value_str)
417
418    def __str__(self):
419        """Dump the register info key/value pairs"""
420        s = ""
421        for key in self.info.keys():
422            if s:
423                s += ", "
424            s += "%s=%s " % (key, self.info[key])
425        return s
426
427
428class Packet:
429    """Class that represents a packet that contains string data"""
430
431    def __init__(self, packet_str):
432        self.str = packet_str
433
434    def peek_char(self):
435        ch = 0
436        if self.str:
437            ch = self.str[0]
438        return ch
439
440    def get_char(self):
441        ch = 0
442        if self.str:
443            ch = self.str[0]
444            self.str = self.str[1:]
445        return ch
446
447    def skip_exact_string(self, s):
448        if self.str and self.str.startswith(s):
449            self.str = self.str[len(s) :]
450            return True
451        else:
452            return False
453
454    def get_thread_id(self, fail_value=-1):
455        match = g_number_regex.match(self.str)
456        if match:
457            number_str = match.group(1)
458            self.str = self.str[len(number_str) :]
459            return int(number_str, 0)
460        else:
461            return fail_value
462
463    def get_hex_uint8(self):
464        if (
465            self.str
466            and len(self.str) >= 2
467            and self.str[0] in string.hexdigits
468            and self.str[1] in string.hexdigits
469        ):
470            uval = int(self.str[0:2], 16)
471            self.str = self.str[2:]
472            return uval
473        return None
474
475    def get_hex_uint16(self, byte_order):
476        uval = 0
477        if byte_order == "big":
478            uval |= self.get_hex_uint8() << 8
479            uval |= self.get_hex_uint8()
480        else:
481            uval |= self.get_hex_uint8()
482            uval |= self.get_hex_uint8() << 8
483        return uval
484
485    def get_hex_uint32(self, byte_order):
486        uval = 0
487        if byte_order == "big":
488            uval |= self.get_hex_uint8() << 24
489            uval |= self.get_hex_uint8() << 16
490            uval |= self.get_hex_uint8() << 8
491            uval |= self.get_hex_uint8()
492        else:
493            uval |= self.get_hex_uint8()
494            uval |= self.get_hex_uint8() << 8
495            uval |= self.get_hex_uint8() << 16
496            uval |= self.get_hex_uint8() << 24
497        return uval
498
499    def get_hex_uint64(self, byte_order):
500        uval = 0
501        if byte_order == "big":
502            uval |= self.get_hex_uint8() << 56
503            uval |= self.get_hex_uint8() << 48
504            uval |= self.get_hex_uint8() << 40
505            uval |= self.get_hex_uint8() << 32
506            uval |= self.get_hex_uint8() << 24
507            uval |= self.get_hex_uint8() << 16
508            uval |= self.get_hex_uint8() << 8
509            uval |= self.get_hex_uint8()
510        else:
511            uval |= self.get_hex_uint8()
512            uval |= self.get_hex_uint8() << 8
513            uval |= self.get_hex_uint8() << 16
514            uval |= self.get_hex_uint8() << 24
515            uval |= self.get_hex_uint8() << 32
516            uval |= self.get_hex_uint8() << 40
517            uval |= self.get_hex_uint8() << 48
518            uval |= self.get_hex_uint8() << 56
519        return uval
520
521    def get_number(self, fail_value=-1):
522        """Get a number from the packet. The number must be in big endian format and should be parsed
523        according to its prefix (starts with "0x" means hex, starts with "0" means octal, starts with
524        [1-9] means decimal, etc)"""
525        match = g_number_regex.match(self.str)
526        if match:
527            number_str = match.group(1)
528            self.str = self.str[len(number_str) :]
529            return int(number_str, 0)
530        else:
531            return fail_value
532
533    def get_hex_ascii_str(self, n=0):
534        hex_chars = self.get_hex_chars(n)
535        if hex_chars:
536            return binascii.unhexlify(hex_chars)
537        else:
538            return None
539
540    def get_hex_chars(self, n=0):
541        str_len = len(self.str)
542        if n == 0:
543            # n was zero, so we need to determine all hex chars and
544            # stop when we hit the end of the string of a non-hex character
545            while n < str_len and self.str[n] in string.hexdigits:
546                n = n + 1
547        else:
548            if n > str_len:
549                return None  # Not enough chars
550            # Verify all chars are hex if a length was specified
551            for i in range(n):
552                if self.str[i] not in string.hexdigits:
553                    return None  # Not all hex digits
554        if n == 0:
555            return None
556        hex_str = self.str[0:n]
557        self.str = self.str[n:]
558        return hex_str
559
560    def get_hex_uint(self, byte_order, n=0):
561        if byte_order == "big":
562            hex_str = self.get_hex_chars(n)
563            if hex_str is None:
564                return None
565            return int(hex_str, 16)
566        else:
567            uval = self.get_hex_uint8()
568            if uval is None:
569                return None
570            uval_result = 0
571            shift = 0
572            while uval is not None:
573                uval_result |= uval << shift
574                shift += 8
575                uval = self.get_hex_uint8()
576            return uval_result
577
578    def get_key_value_pairs(self):
579        kvp = list()
580        if ";" in self.str:
581            key_value_pairs = self.str.split(";")
582            for key_value_pair in key_value_pairs:
583                if len(key_value_pair):
584                    kvp.append(key_value_pair.split(":", 1))
585        return kvp
586
587    def split(self, ch):
588        return self.str.split(ch)
589
590    def split_hex(self, ch, byte_order):
591        hex_values = list()
592        strings = self.str.split(ch)
593        for str in strings:
594            hex_values.append(Packet(str).get_hex_uint(byte_order))
595        return hex_values
596
597    def __str__(self):
598        return self.str
599
600    def __len__(self):
601        return len(self.str)
602
603
604g_thread_suffix_regex = re.compile(";thread:([0-9a-fA-F]+);")
605
606
607def get_thread_from_thread_suffix(str):
608    if str:
609        match = g_thread_suffix_regex.match(str)
610        if match:
611            return int(match.group(1), 16)
612    return None
613
614
615def cmd_qThreadStopInfo(options, cmd, args):
616    packet = Packet(args)
617    tid = packet.get_hex_uint("big")
618    print("get_thread_stop_info  (tid = 0x%x)" % (tid))
619
620
621def cmd_stop_reply(options, cmd, args):
622    print("get_last_stop_info()")
623    return False
624
625
626def rsp_stop_reply(options, cmd, cmd_args, rsp):
627    global g_byte_order
628    packet = Packet(rsp)
629    stop_type = packet.get_char()
630    if stop_type == "T" or stop_type == "S":
631        signo = packet.get_hex_uint8()
632        key_value_pairs = packet.get_key_value_pairs()
633        for key_value_pair in key_value_pairs:
634            key = key_value_pair[0]
635            if is_hex_byte(key):
636                reg_num = Packet(key).get_hex_uint8()
637                if reg_num < len(g_register_infos):
638                    reg_info = g_register_infos[reg_num]
639                    key_value_pair[0] = reg_info.name()
640                    key_value_pair[1] = reg_info.get_value_from_hex_string(
641                        key_value_pair[1]
642                    )
643            elif key == "jthreads" or key == "jstopinfo":
644                key_value_pair[1] = binascii.unhexlify(key_value_pair[1])
645        key_value_pairs.insert(0, ["signal", signo])
646        print("stop_reply():")
647        dump_key_value_pairs(key_value_pairs)
648    elif stop_type == "W":
649        exit_status = packet.get_hex_uint8()
650        print("stop_reply(): exit (status=%i)" % exit_status)
651    elif stop_type == "O":
652        print('stop_reply(): stdout = "%s"' % packet.str)
653
654
655def cmd_unknown_packet(options, cmd, args):
656    if args:
657        print("cmd: %s, args: %s", cmd, args)
658    else:
659        print("cmd: %s", cmd)
660    return False
661
662
663def cmd_qSymbol(options, cmd, args):
664    if args == ":":
665        print("ready to serve symbols")
666    else:
667        packet = Packet(args)
668        symbol_addr = packet.get_hex_uint("big")
669        if symbol_addr is None:
670            if packet.skip_exact_string(":"):
671                symbol_name = packet.get_hex_ascii_str()
672                print('lookup_symbol("%s") -> symbol not available yet' % (symbol_name))
673            else:
674                print("error: bad command format")
675        else:
676            if packet.skip_exact_string(":"):
677                symbol_name = packet.get_hex_ascii_str()
678                print('lookup_symbol("%s") -> 0x%x' % (symbol_name, symbol_addr))
679            else:
680                print("error: bad command format")
681
682
683def cmd_QSetWithHexString(options, cmd, args):
684    print('%s("%s")' % (cmd[:-1], binascii.unhexlify(args)))
685
686
687def cmd_QSetWithString(options, cmd, args):
688    print('%s("%s")' % (cmd[:-1], args))
689
690
691def cmd_QSetWithUnsigned(options, cmd, args):
692    print("%s(%i)" % (cmd[:-1], int(args)))
693
694
695def rsp_qSymbol(options, cmd, cmd_args, rsp):
696    if len(rsp) == 0:
697        print("Unsupported")
698    else:
699        if rsp == "OK":
700            print("No more symbols to lookup")
701        else:
702            packet = Packet(rsp)
703            if packet.skip_exact_string("qSymbol:"):
704                symbol_name = packet.get_hex_ascii_str()
705                print('lookup_symbol("%s")' % (symbol_name))
706            else:
707                print(
708                    'error: response string should start with "qSymbol:": respnse is "%s"'
709                    % (rsp)
710                )
711
712
713def cmd_qXfer(options, cmd, args):
714    # $qXfer:features:read:target.xml:0,1ffff#14
715    print("read target special data %s" % (args))
716    return True
717
718
719def rsp_qXfer(options, cmd, cmd_args, rsp):
720    data = cmd_args.split(":")
721    if data[0] == "features":
722        if data[1] == "read":
723            filename, extension = os.path.splitext(data[2])
724            if extension == ".xml":
725                response = Packet(rsp)
726                xml_string = response.get_hex_ascii_str()
727                if xml_string:
728                    ch = xml_string[0]
729                    if ch == "l":
730                        xml_string = xml_string[1:]
731                        xml_root = ET.fromstring(xml_string)
732                        for reg_element in xml_root.findall("./feature/reg"):
733                            if not "value_regnums" in reg_element.attrib:
734                                reg_info = RegisterInfo([])
735                                if "name" in reg_element.attrib:
736                                    reg_info.info["name"] = reg_element.attrib["name"]
737                                else:
738                                    reg_info.info["name"] = "unspecified"
739                                if "encoding" in reg_element.attrib:
740                                    reg_info.info["encoding"] = reg_element.attrib[
741                                        "encoding"
742                                    ]
743                                else:
744                                    reg_info.info["encoding"] = "uint"
745                                if "offset" in reg_element.attrib:
746                                    reg_info.info["offset"] = reg_element.attrib[
747                                        "offset"
748                                    ]
749                                if "bitsize" in reg_element.attrib:
750                                    reg_info.info["bitsize"] = reg_element.attrib[
751                                        "bitsize"
752                                    ]
753                                g_register_infos.append(reg_info)
754                        print('XML for "%s":' % (data[2]))
755                        ET.dump(xml_root)
756
757
758def cmd_A(options, cmd, args):
759    print("launch process:")
760    packet = Packet(args)
761    while True:
762        arg_len = packet.get_number()
763        if arg_len == -1:
764            break
765        if not packet.skip_exact_string(","):
766            break
767        arg_idx = packet.get_number()
768        if arg_idx == -1:
769            break
770        if not packet.skip_exact_string(","):
771            break
772        arg_value = packet.get_hex_ascii_str(arg_len)
773        print('argv[%u] = "%s"' % (arg_idx, arg_value))
774
775
776def cmd_qC(options, cmd, args):
777    print("query_current_thread_id()")
778
779
780def rsp_qC(options, cmd, cmd_args, rsp):
781    packet = Packet(rsp)
782    if packet.skip_exact_string("QC"):
783        tid = packet.get_thread_id()
784        print("current_thread_id = %#x" % (tid))
785    else:
786        print("current_thread_id = old thread ID")
787
788
789def cmd_query_packet(options, cmd, args):
790    if args:
791        print("%s%s" % (cmd, args))
792    else:
793        print("%s" % (cmd))
794    return False
795
796
797def rsp_ok_error(rsp):
798    print("rsp: ", rsp)
799
800
801def rsp_ok_means_supported(options, cmd, cmd_args, rsp):
802    if rsp == "OK":
803        print("%s%s is supported" % (cmd, cmd_args))
804    elif rsp == "":
805        print("%s%s is not supported" % (cmd, cmd_args))
806    else:
807        print("%s%s -> %s" % (cmd, cmd_args, rsp))
808
809
810def rsp_ok_means_success(options, cmd, cmd_args, rsp):
811    if rsp == "OK":
812        print("success")
813    elif rsp == "":
814        print("%s%s is not supported" % (cmd, cmd_args))
815    else:
816        print("%s%s -> %s" % (cmd, cmd_args, rsp))
817
818
819def dump_key_value_pairs(key_value_pairs):
820    max_key_len = 0
821    for key_value_pair in key_value_pairs:
822        key_len = len(key_value_pair[0])
823        if max_key_len < key_len:
824            max_key_len = key_len
825    for key_value_pair in key_value_pairs:
826        key = key_value_pair[0]
827        value = key_value_pair[1]
828        unhex_value = get_hex_string_if_all_printable(value)
829        if unhex_value:
830            print("%*s = %s (%s)" % (max_key_len, key, value, unhex_value))
831        else:
832            print("%*s = %s" % (max_key_len, key, value))
833
834
835def rsp_dump_key_value_pairs(options, cmd, cmd_args, rsp):
836    if rsp:
837        print("%s response:" % (cmd))
838        packet = Packet(rsp)
839        key_value_pairs = packet.get_key_value_pairs()
840        dump_key_value_pairs(key_value_pairs)
841    else:
842        print("not supported")
843
844
845def cmd_c(options, cmd, args):
846    print("continue()")
847    return False
848
849
850def cmd_s(options, cmd, args):
851    print("step()")
852    return False
853
854
855def cmd_qSpeedTest(options, cmd, args):
856    print(("qSpeedTest: cmd='%s', args='%s'" % (cmd, args)))
857
858
859def rsp_qSpeedTest(options, cmd, cmd_args, rsp):
860    print(("qSpeedTest: rsp='%s' cmd='%s', args='%s'" % (rsp, cmd, args)))
861
862
863def cmd_vCont(options, cmd, args):
864    if args == "?":
865        print("%s: get supported extended continue modes" % (cmd))
866    else:
867        got_other_threads = 0
868        s = ""
869        for thread_action in args[1:].split(";"):
870            (short_action, thread) = thread_action.split(":", 1)
871            tid = int(thread, 16)
872            if short_action == "c":
873                action = "continue"
874            elif short_action == "s":
875                action = "step"
876            elif short_action[0] == "C":
877                action = "continue with signal 0x%s" % (short_action[1:])
878            elif short_action == "S":
879                action = "step with signal 0x%s" % (short_action[1:])
880            else:
881                action = short_action
882            if s:
883                s += ", "
884            if tid == -1:
885                got_other_threads = 1
886                s += "other-threads:"
887            else:
888                s += "thread 0x%4.4x: %s" % (tid, action)
889        if got_other_threads:
890            print("extended_continue (%s)" % (s))
891        else:
892            print("extended_continue (%s, other-threads: suspend)" % (s))
893    return False
894
895
896def rsp_vCont(options, cmd, cmd_args, rsp):
897    if cmd_args == "?":
898        # Skip the leading 'vCont;'
899        rsp = rsp[6:]
900        modes = rsp.split(";")
901        s = "%s: supported extended continue modes include: " % (cmd)
902
903        for i, mode in enumerate(modes):
904            if i:
905                s += ", "
906            if mode == "c":
907                s += "continue"
908            elif mode == "C":
909                s += "continue with signal"
910            elif mode == "s":
911                s += "step"
912            elif mode == "S":
913                s += "step with signal"
914            elif mode == "t":
915                s += "stop"
916            # else:
917            #     s += 'unrecognized vCont mode: ', str(mode)
918        print(s)
919    elif rsp:
920        if rsp[0] == "T" or rsp[0] == "S" or rsp[0] == "W" or rsp[0] == "X":
921            rsp_stop_reply(options, cmd, cmd_args, rsp)
922            return
923        if rsp[0] == "O":
924            print("stdout: %s" % (rsp))
925            return
926    else:
927        print(
928            "not supported (cmd = '%s', args = '%s', rsp = '%s')" % (cmd, cmd_args, rsp)
929        )
930
931
932def cmd_vAttach(options, cmd, args):
933    (extra_command, args) = args.split(";")
934    if extra_command:
935        print("%s%s(%s)" % (cmd, extra_command, args))
936    else:
937        print("attach(pid = %u)" % int(args, 16))
938    return False
939
940
941def cmd_qRegisterInfo(options, cmd, args):
942    print("query_register_info(reg_num=%i)" % (int(args, 16)))
943    return False
944
945
946def rsp_qRegisterInfo(options, cmd, cmd_args, rsp):
947    global g_max_register_info_name_len
948    print("query_register_info(reg_num=%i):" % (int(cmd_args, 16)), end=" ")
949    if len(rsp) == 3 and rsp[0] == "E":
950        g_max_register_info_name_len = 0
951        for reg_info in g_register_infos:
952            name_len = len(reg_info.name())
953            if g_max_register_info_name_len < name_len:
954                g_max_register_info_name_len = name_len
955        print(" DONE")
956    else:
957        packet = Packet(rsp)
958        reg_info = RegisterInfo(packet.get_key_value_pairs())
959        g_register_infos.append(reg_info)
960        print(reg_info)
961    return False
962
963
964def cmd_qThreadInfo(options, cmd, args):
965    if cmd == "qfThreadInfo":
966        query_type = "first"
967    else:
968        query_type = "subsequent"
969    print("get_current_thread_list(type=%s)" % (query_type))
970    return False
971
972
973def rsp_qThreadInfo(options, cmd, cmd_args, rsp):
974    packet = Packet(rsp)
975    response_type = packet.get_char()
976    if response_type == "m":
977        tids = packet.split_hex(";", "big")
978        for i, tid in enumerate(tids):
979            if i:
980                print(",", end=" ")
981            print("0x%x" % (tid), end=" ")
982        print()
983    elif response_type == "l":
984        print("END")
985
986
987def rsp_hex_big_endian(options, cmd, cmd_args, rsp):
988    if rsp == "":
989        print("%s%s is not supported" % (cmd, cmd_args))
990    else:
991        packet = Packet(rsp)
992        uval = packet.get_hex_uint("big")
993        print("%s: 0x%x" % (cmd, uval))
994
995
996def cmd_read_mem_bin(options, cmd, args):
997    # x0x7fff5fc39200,0x200
998    packet = Packet(args)
999    addr = packet.get_hex_uint("big")
1000    comma = packet.get_char()
1001    size = packet.get_hex_uint("big")
1002    print("binary_read_memory (addr = 0x%16.16x, size = %u)" % (addr, size))
1003    return False
1004
1005
1006def rsp_mem_bin_bytes(options, cmd, cmd_args, rsp):
1007    packet = Packet(cmd_args)
1008    addr = packet.get_hex_uint("big")
1009    comma = packet.get_char()
1010    size = packet.get_hex_uint("big")
1011    print("memory:")
1012    if size > 0:
1013        dump_hex_memory_buffer(addr, rsp)
1014
1015
1016def cmd_read_memory(options, cmd, args):
1017    packet = Packet(args)
1018    addr = packet.get_hex_uint("big")
1019    comma = packet.get_char()
1020    size = packet.get_hex_uint("big")
1021    print("read_memory (addr = 0x%16.16x, size = %u)" % (addr, size))
1022    return False
1023
1024
1025def dump_hex_memory_buffer(addr, hex_byte_str):
1026    packet = Packet(hex_byte_str)
1027    idx = 0
1028    ascii = ""
1029    uval = packet.get_hex_uint8()
1030    while uval is not None:
1031        if (idx % 16) == 0:
1032            if ascii:
1033                print("  ", ascii)
1034                ascii = ""
1035            print("0x%x:" % (addr + idx), end=" ")
1036        print("%2.2x" % (uval), end=" ")
1037        if 0x20 <= uval and uval < 0x7F:
1038            ascii += "%c" % uval
1039        else:
1040            ascii += "."
1041        uval = packet.get_hex_uint8()
1042        idx = idx + 1
1043    if ascii:
1044        print("  ", ascii)
1045        ascii = ""
1046
1047
1048def cmd_write_memory(options, cmd, args):
1049    packet = Packet(args)
1050    addr = packet.get_hex_uint("big")
1051    if packet.get_char() != ",":
1052        print("error: invalid write memory command (missing comma after address)")
1053        return
1054    size = packet.get_hex_uint("big")
1055    if packet.get_char() != ":":
1056        print("error: invalid write memory command (missing colon after size)")
1057        return
1058    print("write_memory (addr = 0x%16.16x, size = %u, data:" % (addr, size))
1059    dump_hex_memory_buffer(addr, packet.str)
1060    return False
1061
1062
1063def cmd_alloc_memory(options, cmd, args):
1064    packet = Packet(args)
1065    byte_size = packet.get_hex_uint("big")
1066    if packet.get_char() != ",":
1067        print("error: invalid allocate memory command (missing comma after address)")
1068        return
1069    print(
1070        "allocate_memory (byte-size = %u (0x%x), permissions = %s)"
1071        % (byte_size, byte_size, packet.str)
1072    )
1073    return False
1074
1075
1076def rsp_alloc_memory(options, cmd, cmd_args, rsp):
1077    packet = Packet(rsp)
1078    addr = packet.get_hex_uint("big")
1079    print("addr = 0x%x" % addr)
1080
1081
1082def cmd_dealloc_memory(options, cmd, args):
1083    packet = Packet(args)
1084    addr = packet.get_hex_uint("big")
1085    if packet.get_char() != ",":
1086        print("error: invalid allocate memory command (missing comma after address)")
1087    else:
1088        print("deallocate_memory (addr = 0x%x, permissions = %s)" % (addr, packet.str))
1089    return False
1090
1091
1092def rsp_memory_bytes(options, cmd, cmd_args, rsp):
1093    addr = Packet(cmd_args).get_hex_uint("big")
1094    dump_hex_memory_buffer(addr, rsp)
1095
1096
1097def get_register_name_equal_value(options, reg_num, hex_value_str):
1098    if reg_num < len(g_register_infos):
1099        reg_info = g_register_infos[reg_num]
1100        value_str = reg_info.get_value_from_hex_string(hex_value_str)
1101        s = reg_info.name() + " = "
1102        if options.symbolicator:
1103            symbolicated_addresses = options.symbolicator.symbolicate(int(value_str, 0))
1104            if symbolicated_addresses:
1105                s += options.colors.magenta()
1106                s += "%s" % symbolicated_addresses[0]
1107                s += options.colors.reset()
1108                return s
1109        s += value_str
1110        return s
1111    else:
1112        reg_value = Packet(hex_value_str).get_hex_uint(g_byte_order)
1113        return "reg(%u) = 0x%x" % (reg_num, reg_value)
1114
1115
1116def cmd_read_one_reg(options, cmd, args):
1117    packet = Packet(args)
1118    reg_num = packet.get_hex_uint("big")
1119    tid = get_thread_from_thread_suffix(packet.str)
1120    name = None
1121    if reg_num < len(g_register_infos):
1122        name = g_register_infos[reg_num].name()
1123    if packet.str:
1124        packet.get_char()  # skip ;
1125        thread_info = packet.get_key_value_pairs()
1126        tid = int(thread_info[0][1], 16)
1127    s = "read_register (reg_num=%u" % reg_num
1128    if name:
1129        s += " (%s)" % (name)
1130    if tid is not None:
1131        s += ", tid = 0x%4.4x" % (tid)
1132    s += ")"
1133    print(s)
1134    return False
1135
1136
1137def rsp_read_one_reg(options, cmd, cmd_args, rsp):
1138    packet = Packet(cmd_args)
1139    reg_num = packet.get_hex_uint("big")
1140    print(get_register_name_equal_value(options, reg_num, rsp))
1141
1142
1143def cmd_write_one_reg(options, cmd, args):
1144    packet = Packet(args)
1145    reg_num = packet.get_hex_uint("big")
1146    if packet.get_char() != "=":
1147        print("error: invalid register write packet")
1148    else:
1149        name = None
1150        hex_value_str = packet.get_hex_chars()
1151        tid = get_thread_from_thread_suffix(packet.str)
1152        s = "write_register (reg_num=%u" % reg_num
1153        if name:
1154            s += " (%s)" % (name)
1155        s += ", value = "
1156        s += get_register_name_equal_value(options, reg_num, hex_value_str)
1157        if tid is not None:
1158            s += ", tid = 0x%4.4x" % (tid)
1159        s += ")"
1160        print(s)
1161    return False
1162
1163
1164def dump_all_regs(packet):
1165    for reg_info in g_register_infos:
1166        nibble_size = reg_info.bit_size() / 4
1167        hex_value_str = packet.get_hex_chars(nibble_size)
1168        if hex_value_str is not None:
1169            value = reg_info.get_value_from_hex_string(hex_value_str)
1170            print("%*s = %s" % (g_max_register_info_name_len, reg_info.name(), value))
1171        else:
1172            return
1173
1174
1175def cmd_read_all_regs(cmd, cmd_args):
1176    packet = Packet(cmd_args)
1177    packet.get_char()  # toss the 'g' command character
1178    tid = get_thread_from_thread_suffix(packet.str)
1179    if tid is not None:
1180        print("read_all_register(thread = 0x%4.4x)" % tid)
1181    else:
1182        print("read_all_register()")
1183    return False
1184
1185
1186def rsp_read_all_regs(options, cmd, cmd_args, rsp):
1187    packet = Packet(rsp)
1188    dump_all_regs(packet)
1189
1190
1191def cmd_write_all_regs(options, cmd, args):
1192    packet = Packet(args)
1193    print("write_all_registers()")
1194    dump_all_regs(packet)
1195    return False
1196
1197
1198g_bp_types = ["software_bp", "hardware_bp", "write_wp", "read_wp", "access_wp"]
1199
1200
1201def cmd_bp(options, cmd, args):
1202    if cmd == "Z":
1203        s = "set_"
1204    else:
1205        s = "clear_"
1206    packet = Packet(args)
1207    bp_type = packet.get_hex_uint("big")
1208    packet.get_char()  # Skip ,
1209    bp_addr = packet.get_hex_uint("big")
1210    packet.get_char()  # Skip ,
1211    bp_size = packet.get_hex_uint("big")
1212    s += g_bp_types[bp_type]
1213    s += " (addr = 0x%x, size = %u)" % (bp_addr, bp_size)
1214    print(s)
1215    return False
1216
1217
1218def cmd_mem_rgn_info(options, cmd, args):
1219    packet = Packet(args)
1220    packet.get_char()  # skip ':' character
1221    addr = packet.get_hex_uint("big")
1222    print("get_memory_region_info (addr=0x%x)" % (addr))
1223    return False
1224
1225
1226def cmd_kill(options, cmd, args):
1227    print("kill_process()")
1228    return False
1229
1230
1231def cmd_jThreadsInfo(options, cmd, args):
1232    print("jThreadsInfo()")
1233    return False
1234
1235
1236def cmd_jGetLoadedDynamicLibrariesInfos(options, cmd, args):
1237    print("jGetLoadedDynamicLibrariesInfos()")
1238    return False
1239
1240
1241def decode_packet(s, start_index=0):
1242    # print '\ndecode_packet("%s")' % (s[start_index:])
1243    index = s.find("}", start_index)
1244    have_escapes = index != -1
1245    if have_escapes:
1246        normal_s = s[start_index:index]
1247    else:
1248        normal_s = s[start_index:]
1249    # print 'normal_s = "%s"' % (normal_s)
1250    if have_escapes:
1251        escape_char = "%c" % (ord(s[index + 1]) ^ 0x20)
1252        # print 'escape_char for "%s" = %c' % (s[index:index+2], escape_char)
1253        return normal_s + escape_char + decode_packet(s, index + 2)
1254    else:
1255        return normal_s
1256
1257
1258def rsp_json(options, cmd, cmd_args, rsp):
1259    print("%s() reply:" % (cmd))
1260    if not rsp:
1261        return
1262    try:
1263        json_tree = json.loads(rsp)
1264        print(json.dumps(json_tree, indent=4, separators=(",", ": ")))
1265    except json.JSONDecodeError:
1266        return
1267
1268
1269def rsp_jGetLoadedDynamicLibrariesInfos(options, cmd, cmd_args, rsp):
1270    if cmd_args:
1271        rsp_json(options, cmd, cmd_args, rsp)
1272    else:
1273        rsp_ok_means_supported(options, cmd, cmd_args, rsp)
1274
1275
1276gdb_remote_commands = {
1277    "\\?": {"cmd": cmd_stop_reply, "rsp": rsp_stop_reply, "name": "stop reply pacpket"},
1278    "qThreadStopInfo": {
1279        "cmd": cmd_qThreadStopInfo,
1280        "rsp": rsp_stop_reply,
1281        "name": "stop reply pacpket",
1282    },
1283    "QStartNoAckMode": {
1284        "cmd": cmd_query_packet,
1285        "rsp": rsp_ok_means_supported,
1286        "name": "query if no ack mode is supported",
1287    },
1288    "QThreadSuffixSupported": {
1289        "cmd": cmd_query_packet,
1290        "rsp": rsp_ok_means_supported,
1291        "name": "query if thread suffix is supported",
1292    },
1293    "QListThreadsInStopReply": {
1294        "cmd": cmd_query_packet,
1295        "rsp": rsp_ok_means_supported,
1296        "name": "query if threads in stop reply packets are supported",
1297    },
1298    "QSetDetachOnError:": {
1299        "cmd": cmd_QSetWithUnsigned,
1300        "rsp": rsp_ok_means_success,
1301        "name": "set if we should detach on error",
1302    },
1303    "QSetDisableASLR:": {
1304        "cmd": cmd_QSetWithUnsigned,
1305        "rsp": rsp_ok_means_success,
1306        "name": "set if we should disable ASLR",
1307    },
1308    "qLaunchSuccess": {
1309        "cmd": cmd_query_packet,
1310        "rsp": rsp_ok_means_success,
1311        "name": "check on launch success for the A packet",
1312    },
1313    "A": {"cmd": cmd_A, "rsp": rsp_ok_means_success, "name": "launch process"},
1314    "QLaunchArch:": {
1315        "cmd": cmd_QSetWithString,
1316        "rsp": rsp_ok_means_supported,
1317        "name": "set the arch to launch in case the file contains multiple architectures",
1318    },
1319    "qVAttachOrWaitSupported": {
1320        "cmd": cmd_query_packet,
1321        "rsp": rsp_ok_means_supported,
1322        "name": "set the launch architecture",
1323    },
1324    "qHostInfo": {
1325        "cmd": cmd_query_packet,
1326        "rsp": rsp_dump_key_value_pairs,
1327        "name": "get host information",
1328    },
1329    "qC": {"cmd": cmd_qC, "rsp": rsp_qC, "name": "return the current thread ID"},
1330    "vCont": {"cmd": cmd_vCont, "rsp": rsp_vCont, "name": "extended continue command"},
1331    "qSpeedTest": {
1332        "cmd": cmd_qSpeedTest,
1333        "rsp": rsp_qSpeedTest,
1334        "name": "speed test packdet",
1335    },
1336    "vAttach": {"cmd": cmd_vAttach, "rsp": rsp_stop_reply, "name": "attach to process"},
1337    "c": {"cmd": cmd_c, "rsp": rsp_stop_reply, "name": "continue"},
1338    "s": {"cmd": cmd_s, "rsp": rsp_stop_reply, "name": "step"},
1339    "qRegisterInfo": {
1340        "cmd": cmd_qRegisterInfo,
1341        "rsp": rsp_qRegisterInfo,
1342        "name": "query register info",
1343    },
1344    "qfThreadInfo": {
1345        "cmd": cmd_qThreadInfo,
1346        "rsp": rsp_qThreadInfo,
1347        "name": "get current thread list",
1348    },
1349    "qsThreadInfo": {
1350        "cmd": cmd_qThreadInfo,
1351        "rsp": rsp_qThreadInfo,
1352        "name": "get current thread list",
1353    },
1354    "qShlibInfoAddr": {
1355        "cmd": cmd_query_packet,
1356        "rsp": rsp_hex_big_endian,
1357        "name": "get shared library info address",
1358    },
1359    "qMemoryRegionInfo": {
1360        "cmd": cmd_mem_rgn_info,
1361        "rsp": rsp_dump_key_value_pairs,
1362        "name": "get memory region information",
1363    },
1364    "qProcessInfo": {
1365        "cmd": cmd_query_packet,
1366        "rsp": rsp_dump_key_value_pairs,
1367        "name": "get process info",
1368    },
1369    "qSupported": {
1370        "cmd": cmd_query_packet,
1371        "rsp": rsp_ok_means_supported,
1372        "name": "query supported",
1373    },
1374    "qXfer:": {"cmd": cmd_qXfer, "rsp": rsp_qXfer, "name": "qXfer"},
1375    "qSymbol:": {"cmd": cmd_qSymbol, "rsp": rsp_qSymbol, "name": "qSymbol"},
1376    "QSetSTDIN:": {
1377        "cmd": cmd_QSetWithHexString,
1378        "rsp": rsp_ok_means_success,
1379        "name": "set STDIN prior to launching with A packet",
1380    },
1381    "QSetSTDOUT:": {
1382        "cmd": cmd_QSetWithHexString,
1383        "rsp": rsp_ok_means_success,
1384        "name": "set STDOUT prior to launching with A packet",
1385    },
1386    "QSetSTDERR:": {
1387        "cmd": cmd_QSetWithHexString,
1388        "rsp": rsp_ok_means_success,
1389        "name": "set STDERR prior to launching with A packet",
1390    },
1391    "QEnvironment:": {
1392        "cmd": cmd_QSetWithString,
1393        "rsp": rsp_ok_means_success,
1394        "name": "set an environment variable prior to launching with A packet",
1395    },
1396    "QEnvironmentHexEncoded:": {
1397        "cmd": cmd_QSetWithHexString,
1398        "rsp": rsp_ok_means_success,
1399        "name": "set an environment variable prior to launching with A packet",
1400    },
1401    "x": {
1402        "cmd": cmd_read_mem_bin,
1403        "rsp": rsp_mem_bin_bytes,
1404        "name": "read memory binary",
1405    },
1406    "X": {
1407        "cmd": cmd_write_memory,
1408        "rsp": rsp_ok_means_success,
1409        "name": "write memory binary",
1410    },
1411    "m": {"cmd": cmd_read_memory, "rsp": rsp_memory_bytes, "name": "read memory"},
1412    "M": {"cmd": cmd_write_memory, "rsp": rsp_ok_means_success, "name": "write memory"},
1413    "_M": {"cmd": cmd_alloc_memory, "rsp": rsp_alloc_memory, "name": "allocate memory"},
1414    "_m": {
1415        "cmd": cmd_dealloc_memory,
1416        "rsp": rsp_ok_means_success,
1417        "name": "deallocate memory",
1418    },
1419    "p": {
1420        "cmd": cmd_read_one_reg,
1421        "rsp": rsp_read_one_reg,
1422        "name": "read single register",
1423    },
1424    "P": {
1425        "cmd": cmd_write_one_reg,
1426        "rsp": rsp_ok_means_success,
1427        "name": "write single register",
1428    },
1429    "g": {
1430        "cmd": cmd_read_all_regs,
1431        "rsp": rsp_read_all_regs,
1432        "name": "read all registers",
1433    },
1434    "G": {
1435        "cmd": cmd_write_all_regs,
1436        "rsp": rsp_ok_means_success,
1437        "name": "write all registers",
1438    },
1439    "z": {
1440        "cmd": cmd_bp,
1441        "rsp": rsp_ok_means_success,
1442        "name": "clear breakpoint or watchpoint",
1443    },
1444    "Z": {
1445        "cmd": cmd_bp,
1446        "rsp": rsp_ok_means_success,
1447        "name": "set breakpoint or watchpoint",
1448    },
1449    "k": {"cmd": cmd_kill, "rsp": rsp_stop_reply, "name": "kill process"},
1450    "jThreadsInfo": {
1451        "cmd": cmd_jThreadsInfo,
1452        "rsp": rsp_json,
1453        "name": "JSON get all threads info",
1454    },
1455    "jGetLoadedDynamicLibrariesInfos:": {
1456        "cmd": cmd_jGetLoadedDynamicLibrariesInfos,
1457        "rsp": rsp_jGetLoadedDynamicLibrariesInfos,
1458        "name": "JSON get loaded dynamic libraries",
1459    },
1460}
1461
1462
1463def calculate_mean_and_standard_deviation(floats):
1464    sum = 0.0
1465    count = len(floats)
1466    if count == 0:
1467        return (0.0, 0.0)
1468    for f in floats:
1469        sum += f
1470    mean = sum / count
1471    accum = 0.0
1472    for f in floats:
1473        delta = f - mean
1474        accum += delta * delta
1475
1476    std_dev = math.sqrt(accum / (count - 1))
1477    return (mean, std_dev)
1478
1479
1480def parse_gdb_log_file(path, options):
1481    f = open(path)
1482    parse_gdb_log(f, options)
1483    f.close()
1484
1485
1486def round_up(n, incr):
1487    return float(((int(n) + incr) / incr) * incr)
1488
1489
1490def plot_latencies(sec_times):
1491    # import numpy as np
1492    import matplotlib.pyplot as plt
1493
1494    for i, name in enumerate(sec_times.keys()):
1495        times = sec_times[name]
1496        if len(times) <= 1:
1497            continue
1498        plt.subplot(2, 1, 1)
1499        plt.title('Packet "%s" Times' % (name))
1500        plt.xlabel("Packet")
1501        units = "ms"
1502        adj_times = []
1503        max_time = 0.0
1504        for time in times:
1505            time = time * 1000.0
1506            adj_times.append(time)
1507            if time > max_time:
1508                max_time = time
1509        if max_time < 1.0:
1510            units = "us"
1511            max_time = 0.0
1512            for i in range(len(adj_times)):
1513                adj_times[i] *= 1000.0
1514                if adj_times[i] > max_time:
1515                    max_time = adj_times[i]
1516        plt.ylabel("Time (%s)" % (units))
1517        max_y = None
1518        for i in [5.0, 10.0, 25.0, 50.0]:
1519            if max_time < i:
1520                max_y = round_up(max_time, i)
1521                break
1522        if max_y is None:
1523            max_y = round_up(max_time, 100.0)
1524        plt.ylim(0.0, max_y)
1525        plt.plot(adj_times, "o-")
1526        plt.show()
1527
1528
1529def parse_gdb_log(file, options):
1530    """Parse a GDB log file that was generated by enabling logging with:
1531    (lldb) log enable --threadsafe --timestamp --file <FILE> gdb-remote packets
1532    This log file will contain timestamps and this function will then normalize
1533    those packets to be relative to the first value timestamp that is found and
1534    show delta times between log lines and also keep track of how long it takes
1535    for GDB remote commands to make a send/receive round trip. This can be
1536    handy when trying to figure out why some operation in the debugger is taking
1537    a long time during a preset set of debugger commands."""
1538
1539    tricky_commands = ["qRegisterInfo"]
1540    timestamp_regex = re.compile("(\s*)([1-9][0-9]+\.[0-9]+)([^0-9].*)$")
1541    packet_name_regex = re.compile("([A-Za-z_]+)[^a-z]")
1542    packet_transmit_name_regex = re.compile(
1543        "(?P<direction>send|read) packet: (?P<packet>.*)"
1544    )
1545    packet_contents_name_regex = re.compile("\$([^#]*)#[0-9a-fA-F]{2}")
1546    packet_checksum_regex = re.compile(".*#[0-9a-fA-F]{2}$")
1547    packet_names_regex_str = "(" + "|".join(gdb_remote_commands.keys()) + ")(.*)"
1548    packet_names_regex = re.compile(packet_names_regex_str)
1549
1550    base_time = 0.0
1551    last_time = 0.0
1552    min_time = 100000000.0
1553    packet_total_times = {}
1554    all_packet_times = []
1555    packet_times = {}
1556    packet_counts = {}
1557    lines = file.read().splitlines()
1558    last_command = None
1559    last_command_args = None
1560    last_command_packet = None
1561    hide_next_response = False
1562    num_lines = len(lines)
1563    skip_count = 0
1564    for line_index, line in enumerate(lines):
1565        # See if we need to skip any lines
1566        if skip_count > 0:
1567            skip_count -= 1
1568            continue
1569        m = packet_transmit_name_regex.search(line)
1570        is_command = False
1571        direction = None
1572        if m:
1573            direction = m.group("direction")
1574            is_command = direction == "send"
1575            packet = m.group("packet")
1576            sys.stdout.write(options.colors.green())
1577            if not options.quiet and not hide_next_response:
1578                print("#  ", line)
1579            sys.stdout.write(options.colors.reset())
1580
1581            # print 'direction = "%s", packet = "%s"' % (direction, packet)
1582
1583            if packet[0] == "+":
1584                if is_command:
1585                    print("-->", end=" ")
1586                else:
1587                    print("<--", end=" ")
1588                if not options.quiet:
1589                    print("ACK")
1590                continue
1591            elif packet[0] == "-":
1592                if is_command:
1593                    print("-->", end=" ")
1594                else:
1595                    print("<--", end=" ")
1596                if not options.quiet:
1597                    print("NACK")
1598                continue
1599            elif packet[0] == "$":
1600                m = packet_contents_name_regex.match(packet)
1601                if not m and packet[0] == "$":
1602                    multiline_packet = packet
1603                    idx = line_index + 1
1604                    while idx < num_lines:
1605                        if not options.quiet and not hide_next_response:
1606                            print("#  ", lines[idx])
1607                        multiline_packet += lines[idx]
1608                        m = packet_contents_name_regex.match(multiline_packet)
1609                        if m:
1610                            packet = multiline_packet
1611                            skip_count = idx - line_index
1612                            break
1613                        else:
1614                            idx += 1
1615                if m:
1616                    if is_command:
1617                        print("-->", end=" ")
1618                    else:
1619                        print("<--", end=" ")
1620                    contents = decode_packet(m.group(1))
1621                    if is_command:
1622                        hide_next_response = False
1623                        m = packet_names_regex.match(contents)
1624                        if m:
1625                            last_command = m.group(1)
1626                            if last_command == "?":
1627                                last_command = "\\?"
1628                            packet_name = last_command
1629                            last_command_args = m.group(2)
1630                            last_command_packet = contents
1631                            hide_next_response = gdb_remote_commands[last_command][
1632                                "cmd"
1633                            ](options, last_command, last_command_args)
1634                        else:
1635                            packet_match = packet_name_regex.match(contents)
1636                            if packet_match:
1637                                packet_name = packet_match.group(1)
1638                                for tricky_cmd in tricky_commands:
1639                                    if packet_name.find(tricky_cmd) == 0:
1640                                        packet_name = tricky_cmd
1641                            else:
1642                                packet_name = contents
1643                            last_command = None
1644                            last_command_args = None
1645                            last_command_packet = None
1646                    elif last_command:
1647                        gdb_remote_commands[last_command]["rsp"](
1648                            options, last_command, last_command_args, contents
1649                        )
1650                else:
1651                    print('error: invalid packet: "', packet, '"')
1652            else:
1653                print("???")
1654        else:
1655            print("## ", line)
1656        match = timestamp_regex.match(line)
1657        if match:
1658            curr_time = float(match.group(2))
1659            if last_time and not is_command:
1660                delta = curr_time - last_time
1661                all_packet_times.append(delta)
1662            delta = 0.0
1663            if base_time:
1664                delta = curr_time - last_time
1665            else:
1666                base_time = curr_time
1667
1668            if not is_command:
1669                if line.find("read packet: $") >= 0 and packet_name:
1670                    if packet_name in packet_total_times:
1671                        packet_total_times[packet_name] += delta
1672                        packet_counts[packet_name] += 1
1673                    else:
1674                        packet_total_times[packet_name] = delta
1675                        packet_counts[packet_name] = 1
1676                    if packet_name not in packet_times:
1677                        packet_times[packet_name] = []
1678                    packet_times[packet_name].append(delta)
1679                    packet_name = None
1680                if min_time > delta:
1681                    min_time = delta
1682
1683            if not options or not options.quiet:
1684                print(
1685                    "%s%.6f %+.6f%s"
1686                    % (match.group(1), curr_time - base_time, delta, match.group(3))
1687                )
1688            last_time = curr_time
1689        # else:
1690        #     print line
1691    (average, std_dev) = calculate_mean_and_standard_deviation(all_packet_times)
1692    if average and std_dev:
1693        print(
1694            "%u packets with average packet time of %f and standard deviation of %f"
1695            % (len(all_packet_times), average, std_dev)
1696        )
1697    if packet_total_times:
1698        total_packet_time = 0.0
1699        total_packet_count = 0
1700        for key, vvv in packet_total_times.items():
1701            # print '  key = (%s) "%s"' % (type(key), key)
1702            # print 'value = (%s) %s' % (type(vvv), vvv)
1703            # if type(vvv) == 'float':
1704            total_packet_time += vvv
1705        for key, vvv in packet_counts.items():
1706            total_packet_count += vvv
1707
1708        print("#------------------------------------------------------------")
1709        print("# Packet timing summary:")
1710        print(
1711            "# Totals: time = %6f, count = %6d"
1712            % (total_packet_time, total_packet_count)
1713        )
1714        print("# Min packet time: time = %6f" % (min_time))
1715        print("#------------------------------------------------------------")
1716        print("# Packet                   Time (sec)  Percent Count  Latency")
1717        print("#------------------------- ----------- ------- ------ -------")
1718        if options and options.sort_count:
1719            res = sorted(packet_counts, key=packet_counts.__getitem__, reverse=True)
1720        else:
1721            res = sorted(
1722                packet_total_times, key=packet_total_times.__getitem__, reverse=True
1723            )
1724
1725        if last_time > 0.0:
1726            for item in res:
1727                packet_total_time = packet_total_times[item]
1728                packet_percent = (packet_total_time / total_packet_time) * 100.0
1729                packet_count = packet_counts[item]
1730                print(
1731                    "  %24s %11.6f  %5.2f%% %6d %9.6f"
1732                    % (
1733                        item,
1734                        packet_total_time,
1735                        packet_percent,
1736                        packet_count,
1737                        float(packet_total_time) / float(packet_count),
1738                    )
1739                )
1740        if options and options.plot:
1741            plot_latencies(packet_times)
1742
1743
1744if __name__ == "__main__":
1745    usage = "usage: gdbremote [options]"
1746    description = """The command disassembles a GDB remote packet log."""
1747    parser = optparse.OptionParser(
1748        description=description, prog="gdbremote", usage=usage
1749    )
1750    parser.add_option(
1751        "-v",
1752        "--verbose",
1753        action="store_true",
1754        dest="verbose",
1755        help="display verbose debug info",
1756        default=False,
1757    )
1758    parser.add_option(
1759        "-q",
1760        "--quiet",
1761        action="store_true",
1762        dest="quiet",
1763        help="display verbose debug info",
1764        default=False,
1765    )
1766    parser.add_option(
1767        "-C",
1768        "--color",
1769        action="store_true",
1770        dest="color",
1771        help="add terminal colors",
1772        default=False,
1773    )
1774    parser.add_option(
1775        "-c",
1776        "--sort-by-count",
1777        action="store_true",
1778        dest="sort_count",
1779        help="display verbose debug info",
1780        default=False,
1781    )
1782    parser.add_option(
1783        "--crashlog",
1784        type="string",
1785        dest="crashlog",
1786        help="symbolicate using a darwin crash log file",
1787        default=False,
1788    )
1789    try:
1790        (options, args) = parser.parse_args(sys.argv[1:])
1791    except:
1792        print("error: argument error")
1793        sys.exit(1)
1794
1795    options.colors = TerminalColors(options.color)
1796    options.symbolicator = None
1797    if options.crashlog:
1798        import lldb
1799
1800        lldb.debugger = lldb.SBDebugger.Create()
1801        import lldb.macosx.crashlog
1802
1803        options.symbolicator = lldb.macosx.crashlog.CrashLog(options.crashlog)
1804        print("%s" % (options.symbolicator))
1805
1806    # This script is being run from the command line, create a debugger in case we are
1807    # going to use any debugger functions in our function.
1808    if len(args):
1809        for file in args:
1810            print(
1811                "#----------------------------------------------------------------------"
1812            )
1813            print("# GDB remote log file: '%s'" % file)
1814            print(
1815                "#----------------------------------------------------------------------"
1816            )
1817            parse_gdb_log_file(file, options)
1818        if options.symbolicator:
1819            print("%s" % (options.symbolicator))
1820    else:
1821        parse_gdb_log(sys.stdin, options)
1822
1823
1824def __lldb_init_module(debugger, internal_dict):
1825    # This initializer is being run from LLDB in the embedded command interpreter
1826    # Add any commands contained in this module to LLDB
1827    debugger.HandleCommand(
1828        "command script add -o -f gdbremote.start_gdb_log start_gdb_log"
1829    )
1830    debugger.HandleCommand(
1831        "command script add -o -f gdbremote.stop_gdb_log stop_gdb_log"
1832    )
1833    print(
1834        'The "start_gdb_log" and "stop_gdb_log" commands are now installed and ready for use, type "start_gdb_log --help" or "stop_gdb_log --help" for more information'
1835    )
1836