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