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