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