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