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