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