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            elif key == 'jthreads' or key == 'jstopinfo':
499                key_value_pair[1] = binascii.unhexlify(key_value_pair[1])
500        key_value_pairs.insert(0, ['signal', signo])
501        dump_key_value_pairs (key_value_pairs)
502    elif stop_type == 'W':
503        exit_status = packet.get_hex_uint8()
504        print 'exit (status=%i)' % exit_status
505    elif stop_type == 'O':
506        print 'stdout = %s' % packet.str
507
508
509def cmd_unknown_packet(options, cmd, args):
510    if args:
511        print "cmd: %s, args: %s", cmd, args
512    else:
513        print "cmd: %s", cmd
514
515def cmd_qXfer(options, cmd, args):
516    # $qXfer:features:read:target.xml:0,1ffff#14
517    print "read target special data %s" % (args)
518
519def rsp_qXfer(options, cmd, cmd_args, rsp):
520    data = string.split(cmd_args, ':')
521    if data[0] == 'features':
522        if data[1] == 'read':
523            filename, extension = os.path.splitext(data[2])
524            if extension == '.xml':
525                response = Packet(rsp)
526                xml_string = response.get_hex_ascii_str()
527                ch = xml_string[0]
528                if ch == 'l':
529                    xml_string = xml_string[1:]
530                    xml_root = ET.fromstring(xml_string)
531                    for reg_element in xml_root.findall("./feature/reg"):
532                        if not 'value_regnums' in reg_element.attrib:
533                            reg_info = RegisterInfo([])
534                            if 'name' in reg_element.attrib:
535                                reg_info.info['name'] = reg_element.attrib['name']
536                            else:
537                                reg_info.info['name'] = 'unspecified'
538                            if 'encoding' in reg_element.attrib:
539                                reg_info.info['encoding'] = reg_element.attrib['encoding']
540                            else:
541                                reg_info.info['encoding'] = 'uint'
542                            if 'offset' in reg_element.attrib:
543                                reg_info.info['offset'] = reg_element.attrib['offset']
544                            if 'bitsize' in reg_element.attrib:
545                                reg_info.info['bitsize'] = reg_element.attrib['bitsize']
546                            g_register_infos.append(reg_info)
547
548
549def cmd_query_packet(options, cmd, args):
550    if args:
551        print "query: %s, args: %s" % (cmd, args)
552    else:
553        print "query: %s" % (cmd)
554
555def rsp_ok_error(rsp):
556    print "rsp: ", rsp
557
558def rsp_ok_means_supported(options, cmd, cmd_args, rsp):
559    if rsp == 'OK':
560        print "%s%s is supported" % (cmd, cmd_args)
561    elif rsp == '':
562        print "%s%s is not supported" % (cmd, cmd_args)
563    else:
564        print "%s%s -> %s" % (cmd, cmd_args, rsp)
565
566def rsp_ok_means_success(options, cmd, cmd_args, rsp):
567    if rsp == 'OK':
568        print "success"
569    elif rsp == '':
570        print "%s%s is not supported" % (cmd, cmd_args)
571    else:
572        print "%s%s -> %s" % (cmd, cmd_args, rsp)
573
574def dump_key_value_pairs(key_value_pairs):
575    max_key_len = 0
576    for key_value_pair in key_value_pairs:
577        key_len = len(key_value_pair[0])
578        if max_key_len < key_len:
579           max_key_len = key_len
580    for key_value_pair in key_value_pairs:
581        key = key_value_pair[0]
582        value = key_value_pair[1]
583        print "%*s = %s" % (max_key_len, key, value)
584
585def rsp_dump_key_value_pairs(options, cmd, cmd_args, rsp):
586    if rsp:
587        packet = Packet(rsp)
588        key_value_pairs = packet.get_key_value_pairs()
589        dump_key_value_pairs(key_value_pairs)
590    else:
591        print "not supported"
592
593def cmd_c(options, cmd, args):
594    print "continue()"
595
596def cmd_s(options, cmd, args):
597    print "step()"
598
599def cmd_vCont(options, cmd, args):
600    if args == '?':
601        print "%s: get supported extended continue modes" % (cmd)
602    else:
603        got_other_threads = 0
604        s = ''
605        for thread_action in string.split(args[1:], ';'):
606            (short_action, thread) = string.split(thread_action, ':')
607            tid = int(thread, 16)
608            if short_action == 'c':
609                action = 'continue'
610            elif short_action == 's':
611                action = 'step'
612            elif short_action[0] == 'C':
613                action = 'continue with signal 0x%s' % (short_action[1:])
614            elif short_action == 'S':
615                action = 'step with signal 0x%s' % (short_action[1:])
616            else:
617                action = short_action
618            if s:
619                s += ', '
620            if tid == -1:
621                got_other_threads = 1
622                s += 'other-threads:'
623            else:
624                s += 'thread 0x%4.4x: %s' % (tid, action)
625        if got_other_threads:
626            print "extended_continue (%s)" % (s)
627        else:
628            print "extended_continue (%s, other-threads: suspend)" % (s)
629
630def rsp_vCont(options, cmd, cmd_args, rsp):
631    if cmd_args == '?':
632        # Skip the leading 'vCont;'
633        rsp = rsp[6:]
634        modes = string.split(rsp, ';')
635        s = "%s: supported extended continue modes include: " % (cmd)
636
637        for i, mode in enumerate(modes):
638            if i:
639                s += ', '
640            if mode == 'c':
641                s += 'continue'
642            elif mode == 'C':
643                s += 'continue with signal'
644            elif mode == 's':
645                s += 'step'
646            elif mode == 'S':
647                s += 'step with signal'
648            else:
649                s += 'unrecognized vCont mode: ', mode
650        print s
651    elif rsp:
652        if rsp[0] == 'T' or rsp[0] == 'S' or rsp[0] == 'W' or rsp[0] == 'X':
653            rsp_stop_reply (options, cmd, cmd_args, rsp)
654            return
655        if rsp[0] == 'O':
656            print "stdout: %s" % (rsp)
657            return
658    else:
659        print "not supported (cmd = '%s', args = '%s', rsp = '%s')" % (cmd, cmd_args, rsp)
660
661def cmd_vAttach(options, cmd, args):
662    (extra_command, args) = string.split(args, ';')
663    if extra_command:
664        print "%s%s(%s)" % (cmd, extra_command, args)
665    else:
666        print "attach_pid(%s)" % args
667
668def cmd_qRegisterInfo(options, cmd, args):
669    print 'query_register_info(reg_num=%i)' % (int(args, 16))
670
671def rsp_qRegisterInfo(options, cmd, cmd_args, rsp):
672    global g_max_register_info_name_len
673    print 'query_register_info(reg_num=%i):' % (int(cmd_args, 16)),
674    if len(rsp) == 3 and rsp[0] == 'E':
675        g_max_register_info_name_len = 0
676        for reg_info in g_register_infos:
677            name_len = len(reg_info.name())
678            if g_max_register_info_name_len < name_len:
679                g_max_register_info_name_len = name_len
680        print' DONE'
681    else:
682        packet = Packet(rsp)
683        reg_info = RegisterInfo(packet.get_key_value_pairs())
684        g_register_infos.append(reg_info)
685        print reg_info
686
687
688def cmd_qThreadInfo(options, cmd, args):
689    if cmd == 'qfThreadInfo':
690        query_type = 'first'
691    else:
692        query_type = 'subsequent'
693    print 'get_current_thread_list(type=%s)' % (query_type)
694
695def rsp_qThreadInfo(options, cmd, cmd_args, rsp):
696    packet = Packet(rsp)
697    response_type = packet.get_char()
698    if response_type == 'm':
699        tids = packet.split_hex(';', 'big')
700        for i, tid in enumerate(tids):
701            if i:
702                print ',',
703            print '0x%x' % (tid),
704        print
705    elif response_type == 'l':
706        print 'END'
707
708def rsp_hex_big_endian(options, cmd, cmd_args, rsp):
709    packet = Packet(rsp)
710    uval = packet.get_hex_uint('big')
711    print '%s: 0x%x' % (cmd, uval)
712
713def cmd_read_memory(options, cmd, args):
714    packet = Packet(args)
715    addr = packet.get_hex_uint('big')
716    comma = packet.get_char()
717    size = packet.get_hex_uint('big')
718    print 'read_memory (addr = 0x%x, size = %u)' % (addr, size)
719
720def dump_hex_memory_buffer(addr, hex_byte_str):
721    packet = Packet(hex_byte_str)
722    idx = 0
723    ascii = ''
724    uval = packet.get_hex_uint8()
725    while uval != None:
726        if ((idx % 16) == 0):
727            if ascii:
728                print '  ', ascii
729                ascii = ''
730            print '0x%x:' % (addr + idx),
731        print '%2.2x' % (uval),
732        if 0x20 <= uval and uval < 0x7f:
733            ascii += '%c' % uval
734        else:
735            ascii += '.'
736        uval = packet.get_hex_uint8()
737        idx = idx + 1
738    if ascii:
739        print '  ', ascii
740        ascii = ''
741
742def cmd_write_memory(options, cmd, args):
743    packet = Packet(args)
744    addr = packet.get_hex_uint('big')
745    if packet.get_char() != ',':
746        print 'error: invalid write memory command (missing comma after address)'
747        return
748    size = packet.get_hex_uint('big')
749    if packet.get_char() != ':':
750        print 'error: invalid write memory command (missing colon after size)'
751        return
752    print 'write_memory (addr = 0x%x, size = %u, data:' % (addr, size)
753    dump_hex_memory_buffer (addr, packet.str)
754
755def cmd_alloc_memory(options, cmd, args):
756    packet = Packet(args)
757    byte_size = packet.get_hex_uint('big')
758    if packet.get_char() != ',':
759        print 'error: invalid allocate memory command (missing comma after address)'
760        return
761    print 'allocate_memory (byte-size = %u (0x%x), permissions = %s)' % (byte_size, byte_size, packet.str)
762
763def rsp_alloc_memory(options, cmd, cmd_args, rsp):
764    packet = Packet(rsp)
765    addr = packet.get_hex_uint('big')
766    print 'addr = 0x%x' % addr
767
768def cmd_dealloc_memory(options, cmd, args):
769    packet = Packet(args)
770    addr = packet.get_hex_uint('big')
771    if packet.get_char() != ',':
772        print 'error: invalid allocate memory command (missing comma after address)'
773        return
774    print 'deallocate_memory (addr = 0x%x, permissions = %s)' % (addr, packet.str)
775
776def rsp_memory_bytes(options, cmd, cmd_args, rsp):
777    addr = Packet(cmd_args).get_hex_uint('big')
778    dump_hex_memory_buffer (addr, rsp)
779
780def get_register_name_equal_value(options, reg_num, hex_value_str):
781    if reg_num < len(g_register_infos):
782        reg_info = g_register_infos[reg_num]
783        value_str = reg_info.get_value_from_hex_string (hex_value_str)
784        s = reg_info.name() + ' = '
785        if options.symbolicator:
786            symbolicated_addresses = options.symbolicator.symbolicate (int(value_str, 0))
787            if symbolicated_addresses:
788                s += options.colors.magenta()
789                s += '%s' % symbolicated_addresses[0]
790                s += options.colors.reset()
791                return s
792        s += value_str
793        return s
794    else:
795        reg_value = Packet(hex_value_str).get_hex_uint(g_byte_order)
796        return 'reg(%u) = 0x%x' % (reg_num, reg_value)
797
798def cmd_read_one_reg(options, cmd, args):
799    packet = Packet(args)
800    reg_num = packet.get_hex_uint('big')
801    tid = get_thread_from_thread_suffix (packet.str)
802    name = None
803    if reg_num < len(g_register_infos):
804        name = g_register_infos[reg_num].name ()
805    if packet.str:
806        packet.get_char() # skip ;
807        thread_info = packet.get_key_value_pairs()
808        tid = int(thread_info[0][1], 16)
809    s = 'read_register (reg_num=%u' % reg_num
810    if name:
811        s += ' (%s)' % (name)
812    if tid != None:
813        s += ', tid = 0x%4.4x' % (tid)
814    s += ')'
815    print s
816
817def rsp_read_one_reg(options, cmd, cmd_args, rsp):
818    packet = Packet(cmd_args)
819    reg_num = packet.get_hex_uint('big')
820    print get_register_name_equal_value (options, reg_num, rsp)
821
822def cmd_write_one_reg(options, cmd, args):
823    packet = Packet(args)
824    reg_num = packet.get_hex_uint('big')
825    if packet.get_char() != '=':
826        print 'error: invalid register write packet'
827    else:
828        name = None
829        hex_value_str = packet.get_hex_chars()
830        tid = get_thread_from_thread_suffix (packet.str)
831        s = 'write_register (reg_num=%u' % reg_num
832        if name:
833            s += ' (%s)' % (name)
834        s += ', value = '
835        s += get_register_name_equal_value(options, reg_num, hex_value_str)
836        if tid != None:
837            s += ', tid = 0x%4.4x' % (tid)
838        s += ')'
839        print s
840
841def dump_all_regs(packet):
842    for reg_info in g_register_infos:
843        nibble_size = reg_info.bit_size() / 4
844        hex_value_str = packet.get_hex_chars(nibble_size)
845        if hex_value_str != None:
846            value = reg_info.get_value_from_hex_string (hex_value_str)
847            print '%*s = %s' % (g_max_register_info_name_len, reg_info.name(), value)
848        else:
849            return
850
851def cmd_read_all_regs(cmd, cmd_args):
852    packet = Packet(cmd_args)
853    packet.get_char() # toss the 'g' command character
854    tid = get_thread_from_thread_suffix (packet.str)
855    if tid != None:
856        print 'read_all_register(thread = 0x%4.4x)' % tid
857    else:
858        print 'read_all_register()'
859
860def rsp_read_all_regs(options, cmd, cmd_args, rsp):
861    packet = Packet(rsp)
862    dump_all_regs (packet)
863
864def cmd_write_all_regs(options, cmd, args):
865    packet = Packet(args)
866    print 'write_all_registers()'
867    dump_all_regs (packet)
868
869g_bp_types = [ "software_bp", "hardware_bp", "write_wp", "read_wp", "access_wp" ]
870
871def cmd_bp(options, cmd, args):
872    if cmd == 'Z':
873        s = 'set_'
874    else:
875        s = 'clear_'
876    packet = Packet (args)
877    bp_type = packet.get_hex_uint('big')
878    packet.get_char() # Skip ,
879    bp_addr = packet.get_hex_uint('big')
880    packet.get_char() # Skip ,
881    bp_size = packet.get_hex_uint('big')
882    s += g_bp_types[bp_type]
883    s += " (addr = 0x%x, size = %u)" % (bp_addr, bp_size)
884    print s
885
886def cmd_mem_rgn_info(options, cmd, args):
887    packet = Packet(args)
888    packet.get_char() # skip ':' character
889    addr = packet.get_hex_uint('big')
890    print 'get_memory_region_info (addr=0x%x)' % (addr)
891
892def cmd_kill(options, cmd, args):
893    print 'kill_process()'
894
895gdb_remote_commands = {
896    '\\?'                     : { 'cmd' : cmd_stop_reply    , 'rsp' : rsp_stop_reply          , 'name' : "stop reply pacpket"},
897    'QStartNoAckMode'         : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if no ack mode is supported"},
898    'QThreadSuffixSupported'  : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if thread suffix is supported" },
899    'QListThreadsInStopReply' : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if threads in stop reply packets are supported" },
900    'QSetDetachOnError' :       { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if we should detach on error" },
901    'qVAttachOrWaitSupported' : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query if threads attach with optional wait is supported" },
902    'qHostInfo'               : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get host information" },
903    'vCont'                   : { 'cmd' : cmd_vCont         , 'rsp' : rsp_vCont               , 'name' : "extended continue command" },
904    'vAttach'                 : { 'cmd' : cmd_vAttach       , 'rsp' : rsp_stop_reply          , 'name' : "attach to process" },
905    'c'                       : { 'cmd' : cmd_c             , 'rsp' : rsp_stop_reply          , 'name' : "continue" },
906    's'                       : { 'cmd' : cmd_s             , 'rsp' : rsp_stop_reply          , 'name' : "step" },
907    'qRegisterInfo'           : { 'cmd' : cmd_qRegisterInfo , 'rsp' : rsp_qRegisterInfo       , 'name' : "query register info" },
908    'qfThreadInfo'            : { 'cmd' : cmd_qThreadInfo   , 'rsp' : rsp_qThreadInfo         , 'name' : "get current thread list" },
909    'qsThreadInfo'            : { 'cmd' : cmd_qThreadInfo   , 'rsp' : rsp_qThreadInfo         , 'name' : "get current thread list" },
910    'qShlibInfoAddr'          : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_hex_big_endian      , 'name' : "get shared library info address" },
911    'qMemoryRegionInfo'       : { 'cmd' : cmd_mem_rgn_info  , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get memory region information" },
912    'qProcessInfo'            : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get process info" },
913    'qSupported'              : { 'cmd' : cmd_query_packet  , 'rsp' : rsp_ok_means_supported  , 'name' : "query supported" },
914    'qXfer:'                  : { 'cmd' : cmd_qXfer         , 'rsp' : rsp_qXfer               , 'name' : "qXfer" },
915    'm'                       : { 'cmd' : cmd_read_memory   , 'rsp' : rsp_memory_bytes        , 'name' : "read memory" },
916    'M'                       : { 'cmd' : cmd_write_memory  , 'rsp' : rsp_ok_means_success    , 'name' : "write memory" },
917    '_M'                      : { 'cmd' : cmd_alloc_memory  , 'rsp' : rsp_alloc_memory        , 'name' : "allocate memory" },
918    '_m'                      : { 'cmd' : cmd_dealloc_memory, 'rsp' : rsp_ok_means_success    , 'name' : "deallocate memory" },
919    'p'                       : { 'cmd' : cmd_read_one_reg  , 'rsp' : rsp_read_one_reg        , 'name' : "read single register" },
920    'P'                       : { 'cmd' : cmd_write_one_reg , 'rsp' : rsp_ok_means_success    , 'name' : "write single register" },
921    'g'                       : { 'cmd' : cmd_read_all_regs , 'rsp' : rsp_read_all_regs       , 'name' : "read all registers" },
922    'G'                       : { 'cmd' : cmd_write_all_regs, 'rsp' : rsp_ok_means_success    , 'name' : "write all registers" },
923    'z'                       : { 'cmd' : cmd_bp            , 'rsp' : rsp_ok_means_success    , 'name' : "clear breakpoint or watchpoint" },
924    'Z'                       : { 'cmd' : cmd_bp            , 'rsp' : rsp_ok_means_success    , 'name' : "set breakpoint or watchpoint" },
925    'k'                       : { 'cmd' : cmd_kill          , 'rsp' : rsp_stop_reply          , 'name' : "kill process" },
926}
927
928def calculate_mean_and_standard_deviation(floats):
929    sum = 0.0
930    count = len(floats)
931    if count == 0:
932        return (0.0, 0.0)
933    for f in floats:
934        sum += f
935    mean =  sum / count
936    accum = 0.0
937    for f in floats:
938        delta = f - mean
939        accum += delta * delta
940
941    std_dev = math.sqrt(accum / (count-1));
942    return (mean, std_dev)
943
944def parse_gdb_log_file(path, options):
945    f = open(path)
946    parse_gdb_log(f)
947    f.close()
948
949def parse_gdb_log(file, options):
950    '''Parse a GDB log file that was generated by enabling logging with:
951    (lldb) log enable --threadsafe --timestamp --file <FILE> gdb-remote packets
952    This log file will contain timestamps and this function will then normalize
953    those packets to be relative to the first value timestamp that is found and
954    show delta times between log lines and also keep track of how long it takes
955    for GDB remote commands to make a send/receive round trip. This can be
956    handy when trying to figure out why some operation in the debugger is taking
957    a long time during a preset set of debugger commands.'''
958
959    tricky_commands = [ 'qRegisterInfo' ]
960    timestamp_regex = re.compile('(\s*)([1-9][0-9]+\.[0-9]+)([^0-9].*)$')
961    packet_name_regex = re.compile('([A-Za-z_]+)[^a-z]')
962    packet_transmit_name_regex = re.compile('(?P<direction>send|read) packet: (?P<packet>.*)')
963    packet_contents_name_regex = re.compile('\$([^#]+)#[0-9a-fA-F]{2}')
964    packet_names_regex_str = '(' + '|'.join(gdb_remote_commands.keys()) + ')(.*)';
965    packet_names_regex = re.compile(packet_names_regex_str);
966
967    base_time = 0.0
968    last_time = 0.0
969    packet_send_time = 0.0
970    packet_total_times = {}
971    packet_times = []
972    packet_count = {}
973    lines = file.read().splitlines()
974    last_command = None
975    last_command_args = None
976    last_command_packet = None
977    for line in lines:
978        m = packet_transmit_name_regex.search(line)
979        is_command = False
980        direction = None
981        if m:
982            direction = m.group('direction')
983            is_command = direction == 'send'
984            packet = m.group('packet')
985            sys.stdout.write(options.colors.green())
986            if not options.quiet:
987                print '#  ', line
988            sys.stdout.write(options.colors.reset())
989
990            #print 'direction = "%s", packet = "%s"' % (direction, packet)
991
992            if packet[0] == '+':
993                if not options.quiet: print 'ACK'
994                continue
995            elif packet[0] == '-':
996                if not options.quiet: print 'NACK'
997                continue
998            elif packet[0] == '$':
999                m = packet_contents_name_regex.match(packet)
1000                if m:
1001                    contents = m.group(1)
1002                    if is_command:
1003                        m = packet_names_regex.match (contents)
1004                        if m:
1005                            last_command = m.group(1)
1006                            packet_name = last_command
1007                            last_command_args = m.group(2)
1008                            last_command_packet = contents
1009                            gdb_remote_commands[last_command]['cmd'](options, last_command, last_command_args)
1010                        else:
1011                            packet_match = packet_name_regex.match (contents)
1012                            if packet_match:
1013                                packet_name = packet_match.group(1)
1014                                for tricky_cmd in tricky_commands:
1015                                    if packet_name.find (tricky_cmd) == 0:
1016                                        packet_name = tricky_cmd
1017                            else:
1018                                packet_name = contents
1019                            last_command = None
1020                            last_command_args = None
1021                            last_command_packet = None
1022                    elif last_command:
1023                        gdb_remote_commands[last_command]['rsp'](options, last_command, last_command_args, contents)
1024                else:
1025                    print 'error: invalid packet: "', packet, '"'
1026            else:
1027                print '???'
1028        else:
1029            print '## ', line
1030        match = timestamp_regex.match (line)
1031        if match:
1032            curr_time = float (match.group(2))
1033            if last_time and not is_command:
1034                delta = curr_time - last_time
1035                packet_times.append(delta)
1036            delta = 0.0
1037            if base_time:
1038                delta = curr_time - last_time
1039            else:
1040                base_time = curr_time
1041
1042            if is_command:
1043                packet_send_time = curr_time
1044            elif line.find('read packet: $') >= 0 and packet_name:
1045                if packet_name in packet_total_times:
1046                    packet_total_times[packet_name] += delta
1047                    packet_count[packet_name] += 1
1048                else:
1049                    packet_total_times[packet_name] = delta
1050                    packet_count[packet_name] = 1
1051                packet_name = None
1052
1053            if not options or not options.quiet:
1054                print '%s%.6f %+.6f%s' % (match.group(1), curr_time - base_time, delta, match.group(3))
1055            last_time = curr_time
1056        # else:
1057        #     print line
1058    (average, std_dev) = calculate_mean_and_standard_deviation(packet_times)
1059    print '%u packets with average packet time of %f and standard deviation of %f' % (len(packet_times), average, std_dev)
1060    if packet_total_times:
1061        total_packet_time = 0.0
1062        total_packet_count = 0
1063        for key, vvv in packet_total_times.items():
1064            # print '  key = (%s) "%s"' % (type(key), key)
1065            # print 'value = (%s) %s' % (type(vvv), vvv)
1066            # if type(vvv) == 'float':
1067            total_packet_time += vvv
1068        for key, vvv in packet_count.items():
1069            total_packet_count += vvv
1070
1071        print '#---------------------------------------------------'
1072        print '# Packet timing summary:'
1073        print '# Totals: time = %6f, count = %6d' % (total_packet_time, total_packet_count)
1074        print '#---------------------------------------------------'
1075        print '# Packet                   Time (sec) Percent Count '
1076        print '#------------------------- ---------- ------- ------'
1077        if options and options.sort_count:
1078            res = sorted(packet_count, key=packet_count.__getitem__, reverse=True)
1079        else:
1080            res = sorted(packet_total_times, key=packet_total_times.__getitem__, reverse=True)
1081
1082        if last_time > 0.0:
1083            for item in res:
1084                packet_total_time = packet_total_times[item]
1085                packet_percent = (packet_total_time / total_packet_time)*100.0
1086                if packet_percent >= 10.0:
1087                    print "  %24s %.6f   %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item])
1088                else:
1089                    print "  %24s %.6f   %.2f%%  %6d" % (item, packet_total_time, packet_percent, packet_count[item])
1090
1091
1092
1093if __name__ == '__main__':
1094    usage = "usage: gdbremote [options]"
1095    description='''The command disassembles a GDB remote packet log.'''
1096    parser = optparse.OptionParser(description=description, prog='gdbremote',usage=usage)
1097    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
1098    parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='display verbose debug info', default=False)
1099    parser.add_option('-C', '--color', action='store_true', dest='color', help='add terminal colors', default=False)
1100    parser.add_option('-c', '--sort-by-count', action='store_true', dest='sort_count', help='display verbose debug info', default=False)
1101    parser.add_option('--crashlog', type='string', dest='crashlog', help='symbolicate using a darwin crash log file', default=False)
1102    try:
1103        (options, args) = parser.parse_args(sys.argv[1:])
1104    except:
1105        print 'error: argument error'
1106        sys.exit(1)
1107
1108    options.colors = TerminalColors(options.color)
1109    options.symbolicator = None
1110    if options.crashlog:
1111        import lldb
1112        lldb.debugger = lldb.SBDebugger.Create()
1113        import lldb.macosx.crashlog
1114        options.symbolicator = lldb.macosx.crashlog.CrashLog(options.crashlog)
1115        print '%s' % (options.symbolicator)
1116
1117    # This script is being run from the command line, create a debugger in case we are
1118    # going to use any debugger functions in our function.
1119    if len(args):
1120        for file in args:
1121            print '#----------------------------------------------------------------------'
1122            print "# GDB remote log file: '%s'" % file
1123            print '#----------------------------------------------------------------------'
1124            parse_gdb_log_file (file, options)
1125        if options.symbolicator:
1126            print '%s' % (options.symbolicator)
1127    else:
1128        parse_gdb_log(sys.stdin, options)
1129
1130else:
1131    import lldb
1132    if lldb.debugger:
1133        # This initializer is being run from LLDB in the embedded command interpreter
1134        # Add any commands contained in this module to LLDB
1135        lldb.debugger.HandleCommand('command script add -f gdbremote.start_gdb_log start_gdb_log')
1136        lldb.debugger.HandleCommand('command script add -f gdbremote.stop_gdb_log stop_gdb_log')
1137        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'
1138