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