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