1#!/usr/bin/python
2
3#----------------------------------------------------------------------
4# This module will enable GDB remote packet logging when the
5# 'start_gdb_log' command is called with a filename to log to. When the
6# 'stop_gdb_log' command is called, it will disable the logging and
7# print out statistics about how long commands took to execute and also
8# will primnt ou
9# Be sure to add the python path that points to the LLDB shared library.
10#
11# To use this in the embedded python interpreter using "lldb" just
12# import it with the full path using the "command script import"
13# command. This can be done from the LLDB command line:
14#   (lldb) command script import /path/to/gdbremote.py
15# Or it can be added to your ~/.lldbinit file so this module is always
16# available.
17#----------------------------------------------------------------------
18
19import binascii
20import commands
21import math
22import optparse
23import os
24import re
25import shlex
26import string
27import sys
28import tempfile
29import xml.etree.ElementTree as ET
30
31#----------------------------------------------------------------------
32# Global variables
33#----------------------------------------------------------------------
34g_log_file = ''
35g_byte_order = 'little'
36g_number_regex = re.compile('^(0x[0-9a-fA-F]+|[0-9]+)')
37
38class TerminalColors:
39    '''Simple terminal colors class'''
40    def __init__(self, enabled = True):
41        # TODO: discover terminal type from "file" and disable if
42        # it can't handle the color codes
43        self.enabled = enabled
44
45    def reset(self):
46        '''Reset all terminal colors and formatting.'''
47        if self.enabled:
48            return "\x1b[0m";
49        return ''
50
51    def bold(self, on = True):
52        '''Enable or disable bold depending on the "on" parameter.'''
53        if self.enabled:
54            if on:
55                return "\x1b[1m";
56            else:
57                return "\x1b[22m";
58        return ''
59
60    def italics(self, on = True):
61        '''Enable or disable italics depending on the "on" parameter.'''
62        if self.enabled:
63            if on:
64                return "\x1b[3m";
65            else:
66                return "\x1b[23m";
67        return ''
68
69    def underline(self, on = True):
70        '''Enable or disable underline depending on the "on" parameter.'''
71        if self.enabled:
72            if on:
73                return "\x1b[4m";
74            else:
75                return "\x1b[24m";
76        return ''
77
78    def inverse(self, on = True):
79        '''Enable or disable inverse depending on the "on" parameter.'''
80        if self.enabled:
81            if on:
82                return "\x1b[7m";
83            else:
84                return "\x1b[27m";
85        return ''
86
87    def strike(self, on = True):
88        '''Enable or disable strike through depending on the "on" parameter.'''
89        if self.enabled:
90            if on:
91                return "\x1b[9m";
92            else:
93                return "\x1b[29m";
94        return ''
95
96    def black(self, fg = True):
97        '''Set the foreground or background color to black.
98        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
99        if self.enabled:
100            if fg:
101                return "\x1b[30m";
102            else:
103                return "\x1b[40m";
104        return ''
105
106    def red(self, fg = True):
107        '''Set the foreground or background color to red.
108        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
109        if self.enabled:
110            if fg:
111                return "\x1b[31m";
112            else:
113                return "\x1b[41m";
114        return ''
115
116    def green(self, fg = True):
117        '''Set the foreground or background color to green.
118        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
119        if self.enabled:
120            if fg:
121                return "\x1b[32m";
122            else:
123                return "\x1b[42m";
124        return ''
125
126    def yellow(self, fg = True):
127        '''Set the foreground or background color to yellow.
128        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
129        if self.enabled:
130            if fg:
131                return "\x1b[43m";
132            else:
133                return "\x1b[33m";
134        return ''
135
136    def blue(self, fg = True):
137        '''Set the foreground or background color to blue.
138        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
139        if self.enabled:
140            if fg:
141                return "\x1b[34m";
142            else:
143                return "\x1b[44m";
144        return ''
145
146    def magenta(self, fg = True):
147        '''Set the foreground or background color to magenta.
148        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
149        if self.enabled:
150            if fg:
151                return "\x1b[35m";
152            else:
153                return "\x1b[45m";
154        return ''
155
156    def cyan(self, fg = True):
157        '''Set the foreground or background color to cyan.
158        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
159        if self.enabled:
160            if fg:
161                return "\x1b[36m";
162            else:
163                return "\x1b[46m";
164        return ''
165
166    def white(self, fg = True):
167        '''Set the foreground or background color to white.
168        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
169        if self.enabled:
170            if fg:
171                return "\x1b[37m";
172            else:
173                return "\x1b[47m";
174        return ''
175
176    def default(self, fg = True):
177        '''Set the foreground or background color to the default.
178        The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
179        if self.enabled:
180            if fg:
181                return "\x1b[39m";
182            else:
183                return "\x1b[49m";
184        return ''
185
186
187def start_gdb_log(debugger, command, result, dict):
188    '''Start logging GDB remote packets by enabling logging with timestamps and
189    thread safe logging. Follow a call to this function with a call to "stop_gdb_log"
190    in order to dump out the commands.'''
191    global g_log_file
192    command_args = shlex.split(command)
193    usage = "usage: start_gdb_log [options] [<LOGFILEPATH>]"
194    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
195    be aggregated and displayed.'''
196    parser = optparse.OptionParser(description=description, prog='start_gdb_log',usage=usage)
197    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
198    try:
199        (options, args) = parser.parse_args(command_args)
200    except:
201        return
202
203    if g_log_file:
204        result.PutCString ('error: logging is already in progress with file "%s"', g_log_file)
205    else:
206        args_len = len(args)
207        if args_len == 0:
208            g_log_file = tempfile.mktemp()
209        elif len(args) == 1:
210            g_log_file = args[0]
211
212        if g_log_file:
213            debugger.HandleCommand('log enable --threadsafe --timestamp --file "%s" gdb-remote packets' % g_log_file);
214            result.PutCString ("GDB packet logging enable with log file '%s'\nUse the 'stop_gdb_log' command to stop logging and show packet statistics." % g_log_file)
215            return
216
217        result.PutCString ('error: invalid log file path')
218    result.PutCString (usage)
219
220def stop_gdb_log(debugger, command, result, dict):
221    '''Stop logging GDB remote packets to the file that was specified in a call
222    to "start_gdb_log" and normalize the timestamps to be relative to the first
223    timestamp in the log file. Also print out statistics for how long each
224    command took to allow performance bottlenecks to be determined.'''
225    global g_log_file
226    # Any commands whose names might be followed by more valid C identifier
227    # characters must be listed here
228    command_args = shlex.split(command)
229    usage = "usage: stop_gdb_log [options]"
230    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.'''
231    parser = optparse.OptionParser(description=description, prog='stop_gdb_log',usage=usage)
232    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
233    parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='display verbose debug info', default=False)
234    parser.add_option('-C', '--color', action='store_true', dest='color', help='add terminal colors', default=False)
235    parser.add_option('-c', '--sort-by-count', action='store_true', dest='sort_count', help='display verbose debug info', default=False)
236    parser.add_option('-s', '--symbolicate', action='store_true', dest='symbolicate', help='symbolicate addresses in log using current "lldb.target"', default=False)
237    try:
238        (options, args) = parser.parse_args(command_args)
239    except:
240        return
241    options.colors = TerminalColors(options.color)
242    options.symbolicator = None
243    if options.symbolicate:
244        if lldb.target:
245            import lldb.utils.symbolication
246            options.symbolicator = lldb.utils.symbolication.Symbolicator()
247            options.symbolicator.target = lldb.target
248        else:
249            print "error: can't symbolicate without a target"
250
251    if not g_log_file:
252        result.PutCString ('error: logging must have been previously enabled with a call to "stop_gdb_log"')
253    elif os.path.exists (g_log_file):
254        if len(args) == 0:
255            debugger.HandleCommand('log disable gdb-remote packets');
256            result.PutCString ("GDB packet logging disabled. Logged packets are in '%s'" % g_log_file)
257            parse_gdb_log_file (g_log_file, options)
258        else:
259            result.PutCString (usage)
260    else:
261        print 'error: the GDB packet log file "%s" does not exist' % g_log_file
262
263def is_hex_byte(str):
264    if len(str) == 2:
265        return str[0] in string.hexdigits and str[1] in string.hexdigits;
266    return False
267
268# global register info list
269g_register_infos = list()
270g_max_register_info_name_len = 0
271
272class RegisterInfo:
273    """Class that represents register information"""
274    def __init__(self, kvp):
275        self.info = dict()
276        for kv in kvp:
277            key = kv[0]
278            value = kv[1]
279            self.info[key] = value
280    def name(self):
281        '''Get the name of the register.'''
282        if self.info and 'name' in self.info:
283            return self.info['name']
284        return None
285
286    def bit_size(self):
287        '''Get the size in bits of the register.'''
288        if self.info and 'bitsize' in self.info:
289            return int(self.info['bitsize'])
290        return 0
291
292    def byte_size(self):
293        '''Get the size in bytes of the register.'''
294        return self.bit_size() / 8
295
296    def get_value_from_hex_string(self, hex_str):
297        '''Dump the register value given a native byte order encoded hex ASCII byte string.'''
298        encoding = self.info['encoding']
299        bit_size = self.bit_size()
300        packet = Packet(hex_str)
301        if encoding == 'uint':
302            uval = packet.get_hex_uint(g_byte_order)
303            if bit_size == 8:
304                return '0x%2.2x' % (uval)
305            elif bit_size == 16:
306                return '0x%4.4x' % (uval)
307            elif bit_size == 32:
308                return '0x%8.8x' % (uval)
309            elif bit_size == 64:
310                return '0x%16.16x' % (uval)
311        bytes = list();
312        uval = packet.get_hex_uint8()
313        while uval != None:
314            bytes.append(uval)
315            uval = packet.get_hex_uint8()
316        value_str = '0x'
317        if g_byte_order == 'little':
318            bytes.reverse()
319        for byte in bytes:
320            value_str += '%2.2x' % byte
321        return '%s' % (value_str)
322
323    def __str__(self):
324        '''Dump the register info key/value pairs'''
325        s = ''
326        for key in self.info.keys():
327            if s:
328                s += ', '
329            s += "%s=%s " % (key, self.info[key])
330        return s
331
332class Packet:
333    """Class that represents a packet that contains string data"""
334    def __init__(self, packet_str):
335        self.str = packet_str
336
337    def peek_char(self):
338        ch = 0
339        if self.str:
340            ch = self.str[0]
341        return ch
342
343    def get_char(self):
344        ch = 0
345        if self.str:
346            ch = self.str[0]
347            self.str = self.str[1:]
348        return ch
349
350    def skip_exact_string(self, s):
351        if self.str and self.str.startswith(s):
352            self.str = self.str[len(s):]
353            return True
354        else:
355            return False
356
357    def get_hex_uint8(self):
358        if self.str and len(self.str) >= 2 and self.str[0] in string.hexdigits and self.str[1] in string.hexdigits:
359            uval = int(self.str[0:2], 16)
360            self.str = self.str[2:]
361            return uval
362        return None
363
364    def get_hex_uint16(self, byte_order):
365        uval = 0
366        if byte_order == 'big':
367            uval |= self.get_hex_uint8() << 8
368            uval |= self.get_hex_uint8()
369        else:
370            uval |= self.get_hex_uint8()
371            uval |= self.get_hex_uint8() << 8
372        return uval
373
374    def get_hex_uint32(self, byte_order):
375        uval = 0
376        if byte_order == 'big':
377            uval |= self.get_hex_uint8() << 24
378            uval |= self.get_hex_uint8() << 16
379            uval |= self.get_hex_uint8() << 8
380            uval |= self.get_hex_uint8()
381        else:
382            uval |= self.get_hex_uint8()
383            uval |= self.get_hex_uint8() << 8
384            uval |= self.get_hex_uint8() << 16
385            uval |= self.get_hex_uint8() << 24
386        return uval
387
388    def get_hex_uint64(self, byte_order):
389        uval = 0
390        if byte_order == 'big':
391            uval |= self.get_hex_uint8() << 56
392            uval |= self.get_hex_uint8() << 48
393            uval |= self.get_hex_uint8() << 40
394            uval |= self.get_hex_uint8() << 32
395            uval |= self.get_hex_uint8() << 24
396            uval |= self.get_hex_uint8() << 16
397            uval |= self.get_hex_uint8() << 8
398            uval |= self.get_hex_uint8()
399        else:
400            uval |= self.get_hex_uint8()
401            uval |= self.get_hex_uint8() << 8
402            uval |= self.get_hex_uint8() << 16
403            uval |= self.get_hex_uint8() << 24
404            uval |= self.get_hex_uint8() << 32
405            uval |= self.get_hex_uint8() << 40
406            uval |= self.get_hex_uint8() << 48
407            uval |= self.get_hex_uint8() << 56
408        return uval
409
410    def get_number(self, fail_value=-1):
411        '''Get a number from the packet. The number must be in big endian format and should be parsed
412        according to its prefix (starts with "0x" means hex, starts with "0" means octal, starts with
413        [1-9] means decimal, etc)'''
414        match = g_number_regex.match (self.str)
415        if match:
416            number_str = match.group(1)
417            self.str = self.str[len(number_str):]
418            return int(number_str, 0)
419        else:
420            return fail_value
421
422
423    def get_hex_ascii_str(self, n=0):
424        hex_chars = self.get_hex_chars(n)
425        if hex_chars:
426            return binascii.unhexlify(hex_chars)
427        else:
428            return None
429
430    def get_hex_chars(self, n = 0):
431        str_len = len(self.str)
432        if n == 0:
433            # n was zero, so we need to determine all hex chars and
434            # stop when we hit the end of the string of a non-hex character
435            while n < str_len and self.str[n] in string.hexdigits:
436                n = n + 1
437        else:
438            if n > str_len:
439                return None # Not enough chars
440            # Verify all chars are hex if a length was specified
441            for i in range(n):
442                if self.str[i] not in string.hexdigits:
443                    return None # Not all hex digits
444        if n == 0:
445            return None
446        hex_str = self.str[0:n]
447        self.str = self.str[n:]
448        return hex_str
449
450    def get_hex_uint(self, byte_order, n = 0):
451        if byte_order == 'big':
452            hex_str = self.get_hex_chars(n)
453            if hex_str == None:
454                return None
455            return int(hex_str, 16)
456        else:
457            uval = self.get_hex_uint8()
458            if uval == None:
459                return None
460            uval_result = 0
461            shift = 0
462            while uval != None:
463                uval_result |= (uval << shift)
464                shift += 8
465                uval = self.get_hex_uint8()
466            return uval_result
467
468    def get_key_value_pairs(self):
469        kvp = list()
470        if ';' in self.str:
471            key_value_pairs = string.split(self.str, ';')
472            for key_value_pair in key_value_pairs:
473                if len(key_value_pair):
474                    kvp.append(string.split(key_value_pair, ':'))
475        return kvp
476
477    def split(self, ch):
478        return string.split(self.str, ch)
479
480    def split_hex(self, ch, byte_order):
481        hex_values = list()
482        strings = string.split(self.str, ch)
483        for str in strings:
484            hex_values.append(Packet(str).get_hex_uint(byte_order))
485        return hex_values
486
487    def __str__(self):
488        return self.str
489
490    def __len__(self):
491        return len(self.str)
492
493g_thread_suffix_regex = re.compile(';thread:([0-9a-fA-F]+);')
494def get_thread_from_thread_suffix(str):
495    if str:
496        match = g_thread_suffix_regex.match (str)
497        if match:
498            return int(match.group(1), 16)
499    return None
500
501def cmd_stop_reply(options, cmd, args):
502    print "get_last_stop_info()"
503    return False
504
505def rsp_stop_reply(options, cmd, cmd_args, rsp):
506    global g_byte_order
507    packet = Packet(rsp)
508    stop_type = packet.get_char()
509    if stop_type == 'T' or stop_type == 'S':
510        signo  = packet.get_hex_uint8()
511        key_value_pairs = packet.get_key_value_pairs()
512        for key_value_pair in key_value_pairs:
513            key = key_value_pair[0]
514            if is_hex_byte(key):
515                reg_num = Packet(key).get_hex_uint8()
516                if reg_num < len(g_register_infos):
517                    reg_info = g_register_infos[reg_num]
518                    key_value_pair[0] = reg_info.name()
519                    key_value_pair[1] = reg_info.get_value_from_hex_string (key_value_pair[1])
520            elif key == 'jthreads' or key == 'jstopinfo':
521                key_value_pair[1] = binascii.unhexlify(key_value_pair[1])
522        key_value_pairs.insert(0, ['signal', signo])
523        print 'Stop reply:'
524        dump_key_value_pairs (key_value_pairs)
525    elif stop_type == 'W':
526        exit_status = packet.get_hex_uint8()
527        print 'exit (status=%i)' % exit_status
528    elif stop_type == 'O':
529        print 'stdout = %s' % packet.str
530
531
532def cmd_unknown_packet(options, cmd, args):
533    if args:
534        print "cmd: %s, args: %s", cmd, args
535    else:
536        print "cmd: %s", cmd
537    return False
538
539def cmd_qSymbol(options, cmd, args):
540    if args == ':':
541        print 'ready to serve symbols'
542    else:
543        packet = Packet(args)
544        symbol_addr = packet.get_hex_uint('big')
545        if symbol_addr is None:
546            if packet.skip_exact_string(':'):
547                symbol_name = packet.get_hex_ascii_str()
548                print 'lookup_symbol("%s") -> symbol not available yet' % (symbol_name)
549            else:
550                print 'error: bad command format'
551        else:
552            if packet.skip_exact_string(':'):
553                symbol_name = packet.get_hex_ascii_str()
554                print 'lookup_symbol("%s") -> 0x%x' % (symbol_name, symbol_addr)
555            else:
556                print 'error: bad command format'
557
558def rsp_qSymbol(options, cmd, cmd_args, rsp):
559    if len(rsp) == 0:
560        print "Unsupported"
561    else:
562        if rsp == "OK":
563            print "No more symbols to lookup"
564        else:
565            packet = Packet(rsp)
566            if packet.skip_exact_string("qSymbol:"):
567                symbol_name = packet.get_hex_ascii_str()
568                print 'lookup_symbol("%s")' % (symbol_name)
569            else:
570                print 'error: response string should start with "qSymbol:": respnse is "%s"' % (rsp)
571
572def cmd_qXfer(options, cmd, args):
573    # $qXfer:features:read:target.xml:0,1ffff#14
574    print "read target special data %s" % (args)
575    return True
576
577def rsp_qXfer(options, cmd, cmd_args, rsp):
578    data = string.split(cmd_args, ':')
579    if data[0] == 'features':
580        if data[1] == 'read':
581            filename, extension = os.path.splitext(data[2])
582            if extension == '.xml':
583                response = Packet(rsp)
584                xml_string = response.get_hex_ascii_str()
585                ch = xml_string[0]
586                if ch == 'l':
587                    xml_string = xml_string[1:]
588                    xml_root = ET.fromstring(xml_string)
589                    for reg_element in xml_root.findall("./feature/reg"):
590                        if not 'value_regnums' in reg_element.attrib:
591                            reg_info = RegisterInfo([])
592                            if 'name' in reg_element.attrib:
593                                reg_info.info['name'] = reg_element.attrib['name']
594                            else:
595                                reg_info.info['name'] = 'unspecified'
596                            if 'encoding' in reg_element.attrib:
597                                reg_info.info['encoding'] = reg_element.attrib['encoding']
598                            else:
599                                reg_info.info['encoding'] = 'uint'
600                            if 'offset' in reg_element.attrib:
601                                reg_info.info['offset'] = reg_element.attrib['offset']
602                            if 'bitsize' in reg_element.attrib:
603                                reg_info.info['bitsize'] = reg_element.attrib['bitsize']
604                            g_register_infos.append(reg_info)
605                    print 'XML for "%s":' % (data[2])
606                    ET.dump(xml_root)
607
608def cmd_query_packet(options, cmd, args):
609    if args:
610        print "%s%s" % (cmd, args)
611    else:
612        print "%s" % (cmd)
613    return False
614
615def rsp_ok_error(rsp):
616    print "rsp: ", rsp
617
618def rsp_ok_means_supported(options, cmd, cmd_args, rsp):
619    if rsp == 'OK':
620        print "%s%s is supported" % (cmd, cmd_args)
621    elif rsp == '':
622        print "%s%s is not supported" % (cmd, cmd_args)
623    else:
624        print "%s%s -> %s" % (cmd, cmd_args, rsp)
625
626def rsp_ok_means_success(options, cmd, cmd_args, rsp):
627    if rsp == 'OK':
628        print "success"
629    elif rsp == '':
630        print "%s%s is not supported" % (cmd, cmd_args)
631    else:
632        print "%s%s -> %s" % (cmd, cmd_args, rsp)
633
634def dump_key_value_pairs(key_value_pairs):
635    max_key_len = 0
636    for key_value_pair in key_value_pairs:
637        key_len = len(key_value_pair[0])
638        if max_key_len < key_len:
639           max_key_len = key_len
640    for key_value_pair in key_value_pairs:
641        key = key_value_pair[0]
642        value = key_value_pair[1]
643        print "%*s = %s" % (max_key_len, key, value)
644
645def rsp_dump_key_value_pairs(options, cmd, cmd_args, rsp):
646    if rsp:
647        print '%s response:' % (cmd)
648        packet = Packet(rsp)
649        key_value_pairs = packet.get_key_value_pairs()
650        dump_key_value_pairs(key_value_pairs)
651    else:
652        print "not supported"
653
654def cmd_c(options, cmd, args):
655    print "continue()"
656    return False
657
658def cmd_s(options, cmd, args):
659    print "step()"
660    return False
661
662def cmd_vCont(options, cmd, args):
663    if args == '?':
664        print "%s: get supported extended continue modes" % (cmd)
665    else:
666        got_other_threads = 0
667        s = ''
668        for thread_action in string.split(args[1:], ';'):
669            (short_action, thread) = string.split(thread_action, ':')
670            tid = int(thread, 16)
671            if short_action == 'c':
672                action = 'continue'
673            elif short_action == 's':
674                action = 'step'
675            elif short_action[0] == 'C':
676                action = 'continue with signal 0x%s' % (short_action[1:])
677            elif short_action == 'S':
678                action = 'step with signal 0x%s' % (short_action[1:])
679            else:
680                action = short_action
681            if s:
682                s += ', '
683            if tid == -1:
684                got_other_threads = 1
685                s += 'other-threads:'
686            else:
687                s += 'thread 0x%4.4x: %s' % (tid, action)
688        if got_other_threads:
689            print "extended_continue (%s)" % (s)
690        else:
691            print "extended_continue (%s, other-threads: suspend)" % (s)
692    return False
693
694def rsp_vCont(options, cmd, cmd_args, rsp):
695    if cmd_args == '?':
696        # Skip the leading 'vCont;'
697        rsp = rsp[6:]
698        modes = string.split(rsp, ';')
699        s = "%s: supported extended continue modes include: " % (cmd)
700
701        for i, mode in enumerate(modes):
702            if i:
703                s += ', '
704            if mode == 'c':
705                s += 'continue'
706            elif mode == 'C':
707                s += 'continue with signal'
708            elif mode == 's':
709                s += 'step'
710            elif mode == 'S':
711                s += 'step with signal'
712            else:
713                s += 'unrecognized vCont mode: ', mode
714        print s
715    elif rsp:
716        if rsp[0] == 'T' or rsp[0] == 'S' or rsp[0] == 'W' or rsp[0] == 'X':
717            rsp_stop_reply (options, cmd, cmd_args, rsp)
718            return
719        if rsp[0] == 'O':
720            print "stdout: %s" % (rsp)
721            return
722    else:
723        print "not supported (cmd = '%s', args = '%s', rsp = '%s')" % (cmd, cmd_args, rsp)
724
725def cmd_vAttach(options, cmd, args):
726    (extra_command, args) = string.split(args, ';')
727    if extra_command:
728        print "%s%s(%s)" % (cmd, extra_command, args)
729    else:
730        print "attach(pid = %u)" % int(args, 16)
731    return False
732
733
734def cmd_qRegisterInfo(options, cmd, args):
735    print 'query_register_info(reg_num=%i)' % (int(args, 16))
736    return False
737
738def rsp_qRegisterInfo(options, cmd, cmd_args, rsp):
739    global g_max_register_info_name_len
740    print 'query_register_info(reg_num=%i):' % (int(cmd_args, 16)),
741    if len(rsp) == 3 and rsp[0] == 'E':
742        g_max_register_info_name_len = 0
743        for reg_info in g_register_infos:
744            name_len = len(reg_info.name())
745            if g_max_register_info_name_len < name_len:
746                g_max_register_info_name_len = name_len
747        print' DONE'
748    else:
749        packet = Packet(rsp)
750        reg_info = RegisterInfo(packet.get_key_value_pairs())
751        g_register_infos.append(reg_info)
752        print reg_info
753    return False
754
755def cmd_qThreadInfo(options, cmd, args):
756    if cmd == 'qfThreadInfo':
757        query_type = 'first'
758    else:
759        query_type = 'subsequent'
760    print 'get_current_thread_list(type=%s)' % (query_type)
761    return False
762
763def rsp_qThreadInfo(options, cmd, cmd_args, rsp):
764    packet = Packet(rsp)
765    response_type = packet.get_char()
766    if response_type == 'm':
767        tids = packet.split_hex(';', 'big')
768        for i, tid in enumerate(tids):
769            if i:
770                print ',',
771            print '0x%x' % (tid),
772        print
773    elif response_type == 'l':
774        print 'END'
775
776def rsp_hex_big_endian(options, cmd, cmd_args, rsp):
777    packet = Packet(rsp)
778    uval = packet.get_hex_uint('big')
779    print '%s: 0x%x' % (cmd, uval)
780
781def cmd_read_mem_bin(options, cmd, args):
782    # x0x7fff5fc39200,0x200
783    packet = Packet(args)
784    addr = packet.get_number()
785    comma = packet.get_char()
786    size = packet.get_number()
787    print 'binary_read_memory (addr = 0x%16.16x, size = %u)' % (addr, size)
788    return False
789
790def rsp_mem_bin_bytes(options, cmd, cmd_args, rsp):
791    packet = Packet(cmd_args)
792    addr = packet.get_number()
793    comma = packet.get_char()
794    size = packet.get_number()
795    print 'memory:'
796    if size > 0:
797        dump_hex_memory_buffer (addr, rsp)
798
799def cmd_read_memory(options, cmd, args):
800    packet = Packet(args)
801    addr = packet.get_hex_uint('big')
802    comma = packet.get_char()
803    size = packet.get_hex_uint('big')
804    print 'read_memory (addr = 0x%16.16x, size = %u)' % (addr, size)
805    return False
806
807def dump_hex_memory_buffer(addr, hex_byte_str):
808    packet = Packet(hex_byte_str)
809    idx = 0
810    ascii = ''
811    uval = packet.get_hex_uint8()
812    while uval != None:
813        if ((idx % 16) == 0):
814            if ascii:
815                print '  ', ascii
816                ascii = ''
817            print '0x%x:' % (addr + idx),
818        print '%2.2x' % (uval),
819        if 0x20 <= uval and uval < 0x7f:
820            ascii += '%c' % uval
821        else:
822            ascii += '.'
823        uval = packet.get_hex_uint8()
824        idx = idx + 1
825    if ascii:
826        print '  ', ascii
827        ascii = ''
828
829def cmd_write_memory(options, cmd, args):
830    packet = Packet(args)
831    addr = packet.get_hex_uint('big')
832    if packet.get_char() != ',':
833        print 'error: invalid write memory command (missing comma after address)'
834        return
835    size = packet.get_hex_uint('big')
836    if packet.get_char() != ':':
837        print 'error: invalid write memory command (missing colon after size)'
838        return
839    print 'write_memory (addr = 0x%16.16x, size = %u, data:' % (addr, size)
840    dump_hex_memory_buffer (addr, packet.str)
841    return False
842
843def cmd_alloc_memory(options, cmd, args):
844    packet = Packet(args)
845    byte_size = packet.get_hex_uint('big')
846    if packet.get_char() != ',':
847        print 'error: invalid allocate memory command (missing comma after address)'
848        return
849    print 'allocate_memory (byte-size = %u (0x%x), permissions = %s)' % (byte_size, byte_size, packet.str)
850    return False
851
852def rsp_alloc_memory(options, cmd, cmd_args, rsp):
853    packet = Packet(rsp)
854    addr = packet.get_hex_uint('big')
855    print 'addr = 0x%x' % addr
856
857def cmd_dealloc_memory(options, cmd, args):
858    packet = Packet(args)
859    addr = packet.get_hex_uint('big')
860    if packet.get_char() != ',':
861        print 'error: invalid allocate memory command (missing comma after address)'
862    else:
863        print 'deallocate_memory (addr = 0x%x, permissions = %s)' % (addr, packet.str)
864    return False
865def rsp_memory_bytes(options, cmd, cmd_args, rsp):
866    addr = Packet(cmd_args).get_hex_uint('big')
867    dump_hex_memory_buffer (addr, rsp)
868
869def get_register_name_equal_value(options, reg_num, hex_value_str):
870    if reg_num < len(g_register_infos):
871        reg_info = g_register_infos[reg_num]
872        value_str = reg_info.get_value_from_hex_string (hex_value_str)
873        s = reg_info.name() + ' = '
874        if options.symbolicator:
875            symbolicated_addresses = options.symbolicator.symbolicate (int(value_str, 0))
876            if symbolicated_addresses:
877                s += options.colors.magenta()
878                s += '%s' % symbolicated_addresses[0]
879                s += options.colors.reset()
880                return s
881        s += value_str
882        return s
883    else:
884        reg_value = Packet(hex_value_str).get_hex_uint(g_byte_order)
885        return 'reg(%u) = 0x%x' % (reg_num, reg_value)
886
887def cmd_read_one_reg(options, cmd, args):
888    packet = Packet(args)
889    reg_num = packet.get_hex_uint('big')
890    tid = get_thread_from_thread_suffix (packet.str)
891    name = None
892    if reg_num < len(g_register_infos):
893        name = g_register_infos[reg_num].name ()
894    if packet.str:
895        packet.get_char() # skip ;
896        thread_info = packet.get_key_value_pairs()
897        tid = int(thread_info[0][1], 16)
898    s = 'read_register (reg_num=%u' % reg_num
899    if name:
900        s += ' (%s)' % (name)
901    if tid != None:
902        s += ', tid = 0x%4.4x' % (tid)
903    s += ')'
904    print s
905    return False
906
907def rsp_read_one_reg(options, cmd, cmd_args, rsp):
908    packet = Packet(cmd_args)
909    reg_num = packet.get_hex_uint('big')
910    print get_register_name_equal_value (options, reg_num, rsp)
911
912def cmd_write_one_reg(options, cmd, args):
913    packet = Packet(args)
914    reg_num = packet.get_hex_uint('big')
915    if packet.get_char() != '=':
916        print 'error: invalid register write packet'
917    else:
918        name = None
919        hex_value_str = packet.get_hex_chars()
920        tid = get_thread_from_thread_suffix (packet.str)
921        s = 'write_register (reg_num=%u' % reg_num
922        if name:
923            s += ' (%s)' % (name)
924        s += ', value = '
925        s += get_register_name_equal_value(options, reg_num, hex_value_str)
926        if tid != None:
927            s += ', tid = 0x%4.4x' % (tid)
928        s += ')'
929        print s
930    return False
931
932def dump_all_regs(packet):
933    for reg_info in g_register_infos:
934        nibble_size = reg_info.bit_size() / 4
935        hex_value_str = packet.get_hex_chars(nibble_size)
936        if hex_value_str != None:
937            value = reg_info.get_value_from_hex_string (hex_value_str)
938            print '%*s = %s' % (g_max_register_info_name_len, reg_info.name(), value)
939        else:
940            return
941
942def cmd_read_all_regs(cmd, cmd_args):
943    packet = Packet(cmd_args)
944    packet.get_char() # toss the 'g' command character
945    tid = get_thread_from_thread_suffix (packet.str)
946    if tid != None:
947        print 'read_all_register(thread = 0x%4.4x)' % tid
948    else:
949        print 'read_all_register()'
950    return False
951
952def rsp_read_all_regs(options, cmd, cmd_args, rsp):
953    packet = Packet(rsp)
954    dump_all_regs (packet)
955
956def cmd_write_all_regs(options, cmd, args):
957    packet = Packet(args)
958    print 'write_all_registers()'
959    dump_all_regs (packet)
960    return False
961
962g_bp_types = [ "software_bp", "hardware_bp", "write_wp", "read_wp", "access_wp" ]
963
964def cmd_bp(options, cmd, args):
965    if cmd == 'Z':
966        s = 'set_'
967    else:
968        s = 'clear_'
969    packet = Packet (args)
970    bp_type = packet.get_hex_uint('big')
971    packet.get_char() # Skip ,
972    bp_addr = packet.get_hex_uint('big')
973    packet.get_char() # Skip ,
974    bp_size = packet.get_hex_uint('big')
975    s += g_bp_types[bp_type]
976    s += " (addr = 0x%x, size = %u)" % (bp_addr, bp_size)
977    print s
978    return False
979
980def cmd_mem_rgn_info(options, cmd, args):
981    packet = Packet(args)
982    packet.get_char() # skip ':' character
983    addr = packet.get_hex_uint('big')
984    print 'get_memory_region_info (addr=0x%x)' % (addr)
985    return False
986
987def cmd_kill(options, cmd, args):
988    print 'kill_process()'
989    return False
990
991gdb_remote_commands = {
992    '\\?'                     : { 'cmd' : cmd_stop_reply    , 'rsp' : rsp_stop_reply          , 'name' : "stop reply pacpket"},
993    'QStartNoAckMode'         : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if no ack mode is supported"},
994    'QThreadSuffixSupported'  : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if thread suffix is supported" },
995    'QListThreadsInStopReply' : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if threads in stop reply packets are supported" },
996    'QSetDetachOnError' :       { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if we should detach on error" },
997    'qVAttachOrWaitSupported' : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if threads attach with optional wait is supported" },
998    'qHostInfo'               : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get host information" },
999    'vCont'                   : { 'cmd' : cmd_vCont         , 'rsp' : rsp_vCont               , 'name' : "extended continue command" },
1000    'vAttach'                 : { 'cmd' : cmd_vAttach       , 'rsp' : rsp_stop_reply          , 'name' : "attach to process" },
1001    'c'                       : { 'cmd' : cmd_c             , 'rsp' : rsp_stop_reply          , 'name' : "continue" },
1002    's'                       : { 'cmd' : cmd_s             , 'rsp' : rsp_stop_reply          , 'name' : "step" },
1003    'qRegisterInfo'           : { 'cmd' : cmd_qRegisterInfo , 'rsp' : rsp_qRegisterInfo       , 'name' : "query register info" },
1004    'qfThreadInfo'            : { 'cmd' : cmd_qThreadInfo   , 'rsp' : rsp_qThreadInfo         , 'name' : "get current thread list" },
1005    'qsThreadInfo'            : { 'cmd' : cmd_qThreadInfo   , 'rsp' : rsp_qThreadInfo         , 'name' : "get current thread list" },
1006    'qShlibInfoAddr'          : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_hex_big_endian      , 'name' : "get shared library info address" },
1007    'qMemoryRegionInfo'       : { 'cmd' : cmd_mem_rgn_info  , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get memory region information" },
1008    'qProcessInfo'            : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get process info" },
1009    'qSupported'              : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query supported" },
1010    'qXfer:'                  : { 'cmd' : cmd_qXfer         , 'rsp' : rsp_qXfer               , 'name' : "qXfer" },
1011    'qSymbol:'                : { 'cmd' : cmd_qSymbol       , 'rsp' : rsp_qSymbol             , 'name' : "qSymbol" },
1012    'x'                       : { 'cmd' : cmd_read_mem_bin  , 'rsp' : rsp_mem_bin_bytes       , 'name' : "read memory binary" },
1013    'X'                       : { 'cmd' : cmd_write_memory  , 'rsp' : rsp_ok_means_success    , 'name' : "write memory binary" },
1014    'm'                       : { 'cmd' : cmd_read_memory   , 'rsp' : rsp_memory_bytes        , 'name' : "read memory" },
1015    'M'                       : { 'cmd' : cmd_write_memory  , 'rsp' : rsp_ok_means_success    , 'name' : "write memory" },
1016    '_M'                      : { 'cmd' : cmd_alloc_memory  , 'rsp' : rsp_alloc_memory        , 'name' : "allocate memory" },
1017    '_m'                      : { 'cmd' : cmd_dealloc_memory, 'rsp' : rsp_ok_means_success    , 'name' : "deallocate memory" },
1018    'p'                       : { 'cmd' : cmd_read_one_reg  , 'rsp' : rsp_read_one_reg        , 'name' : "read single register" },
1019    'P'                       : { 'cmd' : cmd_write_one_reg , 'rsp' : rsp_ok_means_success    , 'name' : "write single register" },
1020    'g'                       : { 'cmd' : cmd_read_all_regs , 'rsp' : rsp_read_all_regs       , 'name' : "read all registers" },
1021    'G'                       : { 'cmd' : cmd_write_all_regs, 'rsp' : rsp_ok_means_success    , 'name' : "write all registers" },
1022    'z'                       : { 'cmd' : cmd_bp            , 'rsp' : rsp_ok_means_success    , 'name' : "clear breakpoint or watchpoint" },
1023    'Z'                       : { 'cmd' : cmd_bp            , 'rsp' : rsp_ok_means_success    , 'name' : "set breakpoint or watchpoint" },
1024    'k'                       : { 'cmd' : cmd_kill          , 'rsp' : rsp_stop_reply          , 'name' : "kill process" },
1025}
1026
1027def calculate_mean_and_standard_deviation(floats):
1028    sum = 0.0
1029    count = len(floats)
1030    if count == 0:
1031        return (0.0, 0.0)
1032    for f in floats:
1033        sum += f
1034    mean =  sum / count
1035    accum = 0.0
1036    for f in floats:
1037        delta = f - mean
1038        accum += delta * delta
1039
1040    std_dev = math.sqrt(accum / (count-1));
1041    return (mean, std_dev)
1042
1043def parse_gdb_log_file(path, options):
1044    f = open(path)
1045    parse_gdb_log(f, options)
1046    f.close()
1047
1048def parse_gdb_log(file, options):
1049    '''Parse a GDB log file that was generated by enabling logging with:
1050    (lldb) log enable --threadsafe --timestamp --file <FILE> gdb-remote packets
1051    This log file will contain timestamps and this function will then normalize
1052    those packets to be relative to the first value timestamp that is found and
1053    show delta times between log lines and also keep track of how long it takes
1054    for GDB remote commands to make a send/receive round trip. This can be
1055    handy when trying to figure out why some operation in the debugger is taking
1056    a long time during a preset set of debugger commands.'''
1057
1058    tricky_commands = [ 'qRegisterInfo' ]
1059    timestamp_regex = re.compile('(\s*)([1-9][0-9]+\.[0-9]+)([^0-9].*)$')
1060    packet_name_regex = re.compile('([A-Za-z_]+)[^a-z]')
1061    packet_transmit_name_regex = re.compile('(?P<direction>send|read) packet: (?P<packet>.*)')
1062    packet_contents_name_regex = re.compile('\$([^#]+)#[0-9a-fA-F]{2}')
1063    packet_names_regex_str = '(' + '|'.join(gdb_remote_commands.keys()) + ')(.*)';
1064    packet_names_regex = re.compile(packet_names_regex_str);
1065
1066    base_time = 0.0
1067    last_time = 0.0
1068    packet_send_time = 0.0
1069    packet_total_times = {}
1070    packet_times = []
1071    packet_count = {}
1072    lines = file.read().splitlines()
1073    last_command = None
1074    last_command_args = None
1075    last_command_packet = None
1076    hide_next_response = False
1077    for line in lines:
1078        m = packet_transmit_name_regex.search(line)
1079        is_command = False
1080        direction = None
1081        if m:
1082            direction = m.group('direction')
1083            is_command = direction == 'send'
1084            packet = m.group('packet')
1085            sys.stdout.write(options.colors.green())
1086            if not options.quiet and not hide_next_response:
1087                print '#  ', line
1088            sys.stdout.write(options.colors.reset())
1089
1090            #print 'direction = "%s", packet = "%s"' % (direction, packet)
1091
1092            if is_command:
1093                print '-->',
1094            else:
1095                print '<--',
1096            if packet[0] == '+':
1097                if not options.quiet: print 'ACK'
1098                continue
1099            elif packet[0] == '-':
1100                if not options.quiet: print 'NACK'
1101                continue
1102            elif packet[0] == '$':
1103                m = packet_contents_name_regex.match(packet)
1104                if m:
1105                    contents = m.group(1)
1106                    if is_command:
1107                        hide_next_response = False
1108                        m = packet_names_regex.match (contents)
1109                        if m:
1110                            last_command = m.group(1)
1111                            packet_name = last_command
1112                            last_command_args = m.group(2)
1113                            last_command_packet = contents
1114                            hide_next_response = gdb_remote_commands[last_command]['cmd'](options, last_command, last_command_args)
1115                        else:
1116                            packet_match = packet_name_regex.match (contents)
1117                            if packet_match:
1118                                packet_name = packet_match.group(1)
1119                                for tricky_cmd in tricky_commands:
1120                                    if packet_name.find (tricky_cmd) == 0:
1121                                        packet_name = tricky_cmd
1122                            else:
1123                                packet_name = contents
1124                            last_command = None
1125                            last_command_args = None
1126                            last_command_packet = None
1127                    elif last_command:
1128                        gdb_remote_commands[last_command]['rsp'](options, last_command, last_command_args, contents)
1129                else:
1130                    print 'error: invalid packet: "', packet, '"'
1131            else:
1132                print '???'
1133        else:
1134            print '## ', line
1135        match = timestamp_regex.match (line)
1136        if match:
1137            curr_time = float (match.group(2))
1138            if last_time and not is_command:
1139                delta = curr_time - last_time
1140                packet_times.append(delta)
1141            delta = 0.0
1142            if base_time:
1143                delta = curr_time - last_time
1144            else:
1145                base_time = curr_time
1146
1147            if is_command:
1148                packet_send_time = curr_time
1149            elif line.find('read packet: $') >= 0 and packet_name:
1150                if packet_name in packet_total_times:
1151                    packet_total_times[packet_name] += delta
1152                    packet_count[packet_name] += 1
1153                else:
1154                    packet_total_times[packet_name] = delta
1155                    packet_count[packet_name] = 1
1156                packet_name = None
1157
1158            if not options or not options.quiet:
1159                print '%s%.6f %+.6f%s' % (match.group(1), curr_time - base_time, delta, match.group(3))
1160            last_time = curr_time
1161        # else:
1162        #     print line
1163    (average, std_dev) = calculate_mean_and_standard_deviation(packet_times)
1164    if average and std_dev:
1165        print '%u packets with average packet time of %f and standard deviation of %f' % (len(packet_times), average, std_dev)
1166    if packet_total_times:
1167        total_packet_time = 0.0
1168        total_packet_count = 0
1169        for key, vvv in packet_total_times.items():
1170            # print '  key = (%s) "%s"' % (type(key), key)
1171            # print 'value = (%s) %s' % (type(vvv), vvv)
1172            # if type(vvv) == 'float':
1173            total_packet_time += vvv
1174        for key, vvv in packet_count.items():
1175            total_packet_count += vvv
1176
1177        print '#---------------------------------------------------'
1178        print '# Packet timing summary:'
1179        print '# Totals: time = %6f, count = %6d' % (total_packet_time, total_packet_count)
1180        print '#---------------------------------------------------'
1181        print '# Packet                   Time (sec) Percent Count '
1182        print '#------------------------- ---------- ------- ------'
1183        if options and options.sort_count:
1184            res = sorted(packet_count, key=packet_count.__getitem__, reverse=True)
1185        else:
1186            res = sorted(packet_total_times, key=packet_total_times.__getitem__, reverse=True)
1187
1188        if last_time > 0.0:
1189            for item in res:
1190                packet_total_time = packet_total_times[item]
1191                packet_percent = (packet_total_time / total_packet_time)*100.0
1192                if packet_percent >= 10.0:
1193                    print "  %24s %.6f   %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item])
1194                else:
1195                    print "  %24s %.6f   %.2f%%  %6d" % (item, packet_total_time, packet_percent, packet_count[item])
1196
1197
1198
1199if __name__ == '__main__':
1200    usage = "usage: gdbremote [options]"
1201    description='''The command disassembles a GDB remote packet log.'''
1202    parser = optparse.OptionParser(description=description, prog='gdbremote',usage=usage)
1203    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
1204    parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='display verbose debug info', default=False)
1205    parser.add_option('-C', '--color', action='store_true', dest='color', help='add terminal colors', default=False)
1206    parser.add_option('-c', '--sort-by-count', action='store_true', dest='sort_count', help='display verbose debug info', default=False)
1207    parser.add_option('--crashlog', type='string', dest='crashlog', help='symbolicate using a darwin crash log file', default=False)
1208    try:
1209        (options, args) = parser.parse_args(sys.argv[1:])
1210    except:
1211        print 'error: argument error'
1212        sys.exit(1)
1213
1214    options.colors = TerminalColors(options.color)
1215    options.symbolicator = None
1216    if options.crashlog:
1217        import lldb
1218        lldb.debugger = lldb.SBDebugger.Create()
1219        import lldb.macosx.crashlog
1220        options.symbolicator = lldb.macosx.crashlog.CrashLog(options.crashlog)
1221        print '%s' % (options.symbolicator)
1222
1223    # This script is being run from the command line, create a debugger in case we are
1224    # going to use any debugger functions in our function.
1225    if len(args):
1226        for file in args:
1227            print '#----------------------------------------------------------------------'
1228            print "# GDB remote log file: '%s'" % file
1229            print '#----------------------------------------------------------------------'
1230            parse_gdb_log_file (file, options)
1231        if options.symbolicator:
1232            print '%s' % (options.symbolicator)
1233    else:
1234        parse_gdb_log(sys.stdin, options)
1235
1236else:
1237    import lldb
1238    if lldb.debugger:
1239        # This initializer is being run from LLDB in the embedded command interpreter
1240        # Add any commands contained in this module to LLDB
1241        lldb.debugger.HandleCommand('command script add -f gdbremote.start_gdb_log start_gdb_log')
1242        lldb.debugger.HandleCommand('command script add -f gdbremote.stop_gdb_log stop_gdb_log')
1243        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'
1244