forked from OSchip/llvm-project
1043 lines
41 KiB
Python
Executable File
1043 lines
41 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
#----------------------------------------------------------------------
|
|
# This module will enable GDB remote packet logging when the
|
|
# 'start_gdb_log' command is called with a filename to log to. When the
|
|
# 'stop_gdb_log' command is called, it will disable the logging and
|
|
# print out statistics about how long commands took to execute and also
|
|
# will primnt ou
|
|
# Be sure to add the python path that points to the LLDB shared library.
|
|
#
|
|
# To use this in the embedded python interpreter using "lldb" just
|
|
# import it with the full path using the "command script import"
|
|
# command. This can be done from the LLDB command line:
|
|
# (lldb) command script import /path/to/gdbremote.py
|
|
# Or it can be added to your ~/.lldbinit file so this module is always
|
|
# available.
|
|
#----------------------------------------------------------------------
|
|
|
|
import commands
|
|
import optparse
|
|
import os
|
|
import re
|
|
import shlex
|
|
import string
|
|
import sys
|
|
import tempfile
|
|
|
|
#----------------------------------------------------------------------
|
|
# Global variables
|
|
#----------------------------------------------------------------------
|
|
g_log_file = ''
|
|
g_byte_order = 'little'
|
|
|
|
class TerminalColors:
|
|
'''Simple terminal colors class'''
|
|
def __init__(self, enabled = True):
|
|
# TODO: discover terminal type from "file" and disable if
|
|
# it can't handle the color codes
|
|
self.enabled = enabled
|
|
|
|
def reset(self):
|
|
'''Reset all terminal colors and formatting.'''
|
|
if self.enabled:
|
|
return "\x1b[0m";
|
|
return ''
|
|
|
|
def bold(self, on = True):
|
|
'''Enable or disable bold depending on the "on" parameter.'''
|
|
if self.enabled:
|
|
if on:
|
|
return "\x1b[1m";
|
|
else:
|
|
return "\x1b[22m";
|
|
return ''
|
|
|
|
def italics(self, on = True):
|
|
'''Enable or disable italics depending on the "on" parameter.'''
|
|
if self.enabled:
|
|
if on:
|
|
return "\x1b[3m";
|
|
else:
|
|
return "\x1b[23m";
|
|
return ''
|
|
|
|
def underline(self, on = True):
|
|
'''Enable or disable underline depending on the "on" parameter.'''
|
|
if self.enabled:
|
|
if on:
|
|
return "\x1b[4m";
|
|
else:
|
|
return "\x1b[24m";
|
|
return ''
|
|
|
|
def inverse(self, on = True):
|
|
'''Enable or disable inverse depending on the "on" parameter.'''
|
|
if self.enabled:
|
|
if on:
|
|
return "\x1b[7m";
|
|
else:
|
|
return "\x1b[27m";
|
|
return ''
|
|
|
|
def strike(self, on = True):
|
|
'''Enable or disable strike through depending on the "on" parameter.'''
|
|
if self.enabled:
|
|
if on:
|
|
return "\x1b[9m";
|
|
else:
|
|
return "\x1b[29m";
|
|
return ''
|
|
|
|
def black(self, fg = True):
|
|
'''Set the foreground or background color to black.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[30m";
|
|
else:
|
|
return "\x1b[40m";
|
|
return ''
|
|
|
|
def red(self, fg = True):
|
|
'''Set the foreground or background color to red.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[31m";
|
|
else:
|
|
return "\x1b[41m";
|
|
return ''
|
|
|
|
def green(self, fg = True):
|
|
'''Set the foreground or background color to green.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[32m";
|
|
else:
|
|
return "\x1b[42m";
|
|
return ''
|
|
|
|
def yellow(self, fg = True):
|
|
'''Set the foreground or background color to yellow.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[43m";
|
|
else:
|
|
return "\x1b[33m";
|
|
return ''
|
|
|
|
def blue(self, fg = True):
|
|
'''Set the foreground or background color to blue.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[34m";
|
|
else:
|
|
return "\x1b[44m";
|
|
return ''
|
|
|
|
def magenta(self, fg = True):
|
|
'''Set the foreground or background color to magenta.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[35m";
|
|
else:
|
|
return "\x1b[45m";
|
|
return ''
|
|
|
|
def cyan(self, fg = True):
|
|
'''Set the foreground or background color to cyan.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[36m";
|
|
else:
|
|
return "\x1b[46m";
|
|
return ''
|
|
|
|
def white(self, fg = True):
|
|
'''Set the foreground or background color to white.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[37m";
|
|
else:
|
|
return "\x1b[47m";
|
|
return ''
|
|
|
|
def default(self, fg = True):
|
|
'''Set the foreground or background color to the default.
|
|
The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.'''
|
|
if self.enabled:
|
|
if fg:
|
|
return "\x1b[39m";
|
|
else:
|
|
return "\x1b[49m";
|
|
return ''
|
|
|
|
|
|
def start_gdb_log(debugger, command, result, dict):
|
|
'''Start logging GDB remote packets by enabling logging with timestamps and
|
|
thread safe logging. Follow a call to this function with a call to "stop_gdb_log"
|
|
in order to dump out the commands.'''
|
|
global g_log_file
|
|
command_args = shlex.split(command)
|
|
usage = "usage: start_gdb_log [options] [<LOGFILEPATH>]"
|
|
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
|
|
be aggregated and displayed.'''
|
|
parser = optparse.OptionParser(description=description, prog='start_gdb_log',usage=usage)
|
|
parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
|
|
try:
|
|
(options, args) = parser.parse_args(command_args)
|
|
except:
|
|
return
|
|
|
|
if g_log_file:
|
|
result.PutCString ('error: logging is already in progress with file "%s"', g_log_file)
|
|
else:
|
|
args_len = len(args)
|
|
if args_len == 0:
|
|
g_log_file = tempfile.mktemp()
|
|
elif len(args) == 1:
|
|
g_log_file = args[0]
|
|
|
|
if g_log_file:
|
|
debugger.HandleCommand('log enable --threadsafe --timestamp --file "%s" gdb-remote packets' % g_log_file);
|
|
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)
|
|
return
|
|
|
|
result.PutCString ('error: invalid log file path')
|
|
result.PutCString (usage)
|
|
|
|
def stop_gdb_log(debugger, command, result, dict):
|
|
'''Stop logging GDB remote packets to the file that was specified in a call
|
|
to "start_gdb_log" and normalize the timestamps to be relative to the first
|
|
timestamp in the log file. Also print out statistics for how long each
|
|
command took to allow performance bottlenecks to be determined.'''
|
|
global g_log_file
|
|
# Any commands whose names might be followed by more valid C identifier
|
|
# characters must be listed here
|
|
command_args = shlex.split(command)
|
|
usage = "usage: stop_gdb_log [options]"
|
|
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.'''
|
|
parser = optparse.OptionParser(description=description, prog='stop_gdb_log',usage=usage)
|
|
parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
|
|
parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='display verbose debug info', default=False)
|
|
parser.add_option('-C', '--color', action='store_true', dest='color', help='add terminal colors', default=False)
|
|
parser.add_option('-c', '--sort-by-count', action='store_true', dest='sort_count', help='display verbose debug info', default=False)
|
|
parser.add_option('-s', '--symbolicate', action='store_true', dest='symbolicate', help='symbolicate addresses in log using current "lldb.target"', default=False)
|
|
try:
|
|
(options, args) = parser.parse_args(command_args)
|
|
except:
|
|
return
|
|
options.colors = TerminalColors(options.color)
|
|
options.symbolicator = None
|
|
if options.symbolicate:
|
|
if lldb.target:
|
|
import lldb.utils.symbolication
|
|
options.symbolicator = lldb.utils.symbolication.Symbolicator()
|
|
options.symbolicator.target = lldb.target
|
|
else:
|
|
print "error: can't symbolicate without a target"
|
|
|
|
if not g_log_file:
|
|
result.PutCString ('error: logging must have been previously enabled with a call to "stop_gdb_log"')
|
|
elif os.path.exists (g_log_file):
|
|
if len(args) == 0:
|
|
debugger.HandleCommand('log disable gdb-remote packets');
|
|
result.PutCString ("GDB packet logging disabled. Logged packets are in '%s'" % g_log_file)
|
|
parse_gdb_log_file (g_log_file, options)
|
|
else:
|
|
result.PutCString (usage)
|
|
else:
|
|
print 'error: the GDB packet log file "%s" does not exist' % g_log_file
|
|
|
|
def is_hex_byte(str):
|
|
if len(str) == 2:
|
|
return str[0] in string.hexdigits and str[1] in string.hexdigits;
|
|
return False
|
|
|
|
# global register info list
|
|
g_register_infos = list()
|
|
g_max_register_info_name_len = 0
|
|
|
|
class RegisterInfo:
|
|
"""Class that represents register information"""
|
|
def __init__(self, kvp):
|
|
self.info = dict()
|
|
for kv in kvp:
|
|
key = kv[0]
|
|
value = kv[1]
|
|
self.info[key] = value
|
|
def name(self):
|
|
'''Get the name of the register.'''
|
|
if self.info and 'name' in self.info:
|
|
return self.info['name']
|
|
return None
|
|
|
|
def bit_size(self):
|
|
'''Get the size in bits of the register.'''
|
|
if self.info and 'bitsize' in self.info:
|
|
return int(self.info['bitsize'])
|
|
return 0
|
|
|
|
def byte_size(self):
|
|
'''Get the size in bytes of the register.'''
|
|
return self.bit_size() / 8
|
|
|
|
def get_value_from_hex_string(self, hex_str):
|
|
'''Dump the register value given a native byte order encoded hex ASCII byte string.'''
|
|
encoding = self.info['encoding']
|
|
bit_size = self.bit_size()
|
|
packet = Packet(hex_str)
|
|
if encoding == 'uint':
|
|
uval = packet.get_hex_uint(g_byte_order)
|
|
if bit_size == 8:
|
|
return '0x%2.2x' % (uval)
|
|
elif bit_size == 16:
|
|
return '0x%4.4x' % (uval)
|
|
elif bit_size == 32:
|
|
return '0x%8.8x' % (uval)
|
|
elif bit_size == 64:
|
|
return '0x%16.16x' % (uval)
|
|
bytes = list();
|
|
uval = packet.get_hex_uint8()
|
|
while uval != None:
|
|
bytes.append(uval)
|
|
uval = packet.get_hex_uint8()
|
|
value_str = '0x'
|
|
if g_byte_order == 'little':
|
|
bytes.reverse()
|
|
for byte in bytes:
|
|
value_str += '%2.2x' % byte
|
|
return '%s' % (value_str)
|
|
|
|
def __str__(self):
|
|
'''Dump the register info key/value pairs'''
|
|
s = ''
|
|
for key in self.info.keys():
|
|
if s:
|
|
s += ', '
|
|
s += "%s=%s " % (key, self.info[key])
|
|
return s
|
|
|
|
class Packet:
|
|
"""Class that represents a packet that contains string data"""
|
|
def __init__(self, packet_str):
|
|
self.str = packet_str
|
|
|
|
def peek_char(self):
|
|
ch = 0
|
|
if self.str:
|
|
ch = self.str[0]
|
|
return ch
|
|
|
|
def get_char(self):
|
|
ch = 0
|
|
if self.str:
|
|
ch = self.str[0]
|
|
self.str = self.str[1:]
|
|
return ch
|
|
|
|
def get_hex_uint8(self):
|
|
if self.str and len(self.str) >= 2 and self.str[0] in string.hexdigits and self.str[1] in string.hexdigits:
|
|
uval = int(self.str[0:2], 16)
|
|
self.str = self.str[2:]
|
|
return uval
|
|
return None
|
|
|
|
def get_hex_uint16(self, byte_order):
|
|
uval = 0
|
|
if byte_order == 'big':
|
|
uval |= self.get_hex_uint8() << 8
|
|
uval |= self.get_hex_uint8()
|
|
else:
|
|
uval |= self.get_hex_uint8()
|
|
uval |= self.get_hex_uint8() << 8
|
|
return uval
|
|
|
|
def get_hex_uint32(self, byte_order):
|
|
uval = 0
|
|
if byte_order == 'big':
|
|
uval |= self.get_hex_uint8() << 24
|
|
uval |= self.get_hex_uint8() << 16
|
|
uval |= self.get_hex_uint8() << 8
|
|
uval |= self.get_hex_uint8()
|
|
else:
|
|
uval |= self.get_hex_uint8()
|
|
uval |= self.get_hex_uint8() << 8
|
|
uval |= self.get_hex_uint8() << 16
|
|
uval |= self.get_hex_uint8() << 24
|
|
return uval
|
|
|
|
def get_hex_uint64(self, byte_order):
|
|
uval = 0
|
|
if byte_order == 'big':
|
|
uval |= self.get_hex_uint8() << 56
|
|
uval |= self.get_hex_uint8() << 48
|
|
uval |= self.get_hex_uint8() << 40
|
|
uval |= self.get_hex_uint8() << 32
|
|
uval |= self.get_hex_uint8() << 24
|
|
uval |= self.get_hex_uint8() << 16
|
|
uval |= self.get_hex_uint8() << 8
|
|
uval |= self.get_hex_uint8()
|
|
else:
|
|
uval |= self.get_hex_uint8()
|
|
uval |= self.get_hex_uint8() << 8
|
|
uval |= self.get_hex_uint8() << 16
|
|
uval |= self.get_hex_uint8() << 24
|
|
uval |= self.get_hex_uint8() << 32
|
|
uval |= self.get_hex_uint8() << 40
|
|
uval |= self.get_hex_uint8() << 48
|
|
uval |= self.get_hex_uint8() << 56
|
|
return uval
|
|
|
|
def get_hex_chars(self, n = 0):
|
|
str_len = len(self.str)
|
|
if n == 0:
|
|
# n was zero, so we need to determine all hex chars and
|
|
# stop when we hit the end of the string of a non-hex character
|
|
while n < str_len and self.str[n] in string.hexdigits:
|
|
n = n + 1
|
|
else:
|
|
if n > str_len:
|
|
return None # Not enough chars
|
|
# Verify all chars are hex if a length was specified
|
|
for i in range(n):
|
|
if self.str[i] not in string.hexdigits:
|
|
return None # Not all hex digits
|
|
if n == 0:
|
|
return None
|
|
hex_str = self.str[0:n]
|
|
self.str = self.str[n:]
|
|
return hex_str
|
|
|
|
def get_hex_uint(self, byte_order, n = 0):
|
|
if byte_order == 'big':
|
|
hex_str = self.get_hex_chars(n)
|
|
if hex_str == None:
|
|
return None
|
|
return int(hex_str, 16)
|
|
else:
|
|
uval = self.get_hex_uint8()
|
|
if uval == None:
|
|
return None
|
|
uval_result = 0
|
|
shift = 0
|
|
while uval != None:
|
|
uval_result |= (uval << shift)
|
|
shift += 8
|
|
uval = self.get_hex_uint8()
|
|
return uval_result
|
|
|
|
def get_key_value_pairs(self):
|
|
kvp = list()
|
|
key_value_pairs = string.split(self.str, ';')
|
|
for key_value_pair in key_value_pairs:
|
|
if len(key_value_pair):
|
|
kvp.append(string.split(key_value_pair, ':'))
|
|
return kvp
|
|
|
|
def split(self, ch):
|
|
return string.split(self.str, ch)
|
|
|
|
def split_hex(self, ch, byte_order):
|
|
hex_values = list()
|
|
strings = string.split(self.str, ch)
|
|
for str in strings:
|
|
hex_values.append(Packet(str).get_hex_uint(byte_order))
|
|
return hex_values
|
|
|
|
def __str__(self):
|
|
return self.str
|
|
|
|
def __len__(self):
|
|
return len(self.str)
|
|
|
|
g_thread_suffix_regex = re.compile(';thread:([0-9a-fA-F]+);')
|
|
def get_thread_from_thread_suffix(str):
|
|
if str:
|
|
match = g_thread_suffix_regex.match (str)
|
|
if match:
|
|
return int(match.group(1), 16)
|
|
return None
|
|
|
|
def cmd_stop_reply(options, cmd, args):
|
|
print "get_last_stop_info()"
|
|
|
|
def rsp_stop_reply(options, cmd, cmd_args, rsp):
|
|
global g_byte_order
|
|
packet = Packet(rsp)
|
|
stop_type = packet.get_char()
|
|
if stop_type == 'T' or stop_type == 'S':
|
|
signo = packet.get_hex_uint8()
|
|
print ' signal = %i' % signo
|
|
key_value_pairs = packet.get_key_value_pairs()
|
|
for key_value_pair in key_value_pairs:
|
|
key = key_value_pair[0]
|
|
value = key_value_pair[1]
|
|
if is_hex_byte(key):
|
|
reg_num = Packet(key).get_hex_uint8()
|
|
print ' ' + get_register_name_equal_value (options, reg_num, value)
|
|
else:
|
|
print ' %s = %s' % (key, value)
|
|
elif stop_type == 'W':
|
|
exit_status = packet.get_hex_uint8()
|
|
print 'exit (status=%i)' % exit_status
|
|
elif stop_type == 'O':
|
|
print 'stdout = %s' % packet.str
|
|
|
|
|
|
def cmd_unknown_packet(options, cmd, args):
|
|
if args:
|
|
print "cmd: %s, args: %s", cmd, args
|
|
else:
|
|
print "cmd: %s", cmd
|
|
|
|
def cmd_query_packet(options, cmd, args):
|
|
if args:
|
|
print "query: %s, args: %s" % (cmd, args)
|
|
else:
|
|
print "query: %s" % (cmd)
|
|
|
|
def rsp_ok_error(rsp):
|
|
print "rsp: ", rsp
|
|
|
|
def rsp_ok_means_supported(options, cmd, cmd_args, rsp):
|
|
if rsp == 'OK':
|
|
print "%s%s is supported" % (cmd, cmd_args)
|
|
elif rsp == '':
|
|
print "%s%s is not supported" % (cmd, cmd_args)
|
|
else:
|
|
print "%s%s -> %s" % (cmd, cmd_args, rsp)
|
|
|
|
def rsp_ok_means_success(options, cmd, cmd_args, rsp):
|
|
if rsp == 'OK':
|
|
print "success"
|
|
elif rsp == '':
|
|
print "%s%s is not supported" % (cmd, cmd_args)
|
|
else:
|
|
print "%s%s -> %s" % (cmd, cmd_args, rsp)
|
|
|
|
def rsp_dump_key_value_pairs(options, cmd, cmd_args, rsp):
|
|
if rsp:
|
|
packet = Packet(rsp)
|
|
key_value_pairs = packet.get_key_value_pairs()
|
|
for key_value_pair in key_value_pairs:
|
|
key = key_value_pair[0]
|
|
value = key_value_pair[1]
|
|
print " %s = %s" % (key, value)
|
|
else:
|
|
print "not supported"
|
|
|
|
def cmd_vCont(options, cmd, args):
|
|
if args == '?':
|
|
print "%s: get supported extended continue modes" % (cmd)
|
|
else:
|
|
got_other_threads = 0
|
|
s = ''
|
|
for thread_action in string.split(args[1:], ';'):
|
|
(short_action, thread) = string.split(thread_action, ':')
|
|
tid = int(thread, 16)
|
|
if short_action == 'c':
|
|
action = 'continue'
|
|
elif short_action == 's':
|
|
action = 'step'
|
|
elif short_action[0] == 'C':
|
|
action = 'continue with signal 0x%s' % (short_action[1:])
|
|
elif short_action == 'S':
|
|
action = 'step with signal 0x%s' % (short_action[1:])
|
|
else:
|
|
action = short_action
|
|
if s:
|
|
s += ', '
|
|
if tid == -1:
|
|
got_other_threads = 1
|
|
s += 'other-threads:'
|
|
else:
|
|
s += 'thread 0x%4.4x: %s' % (tid, action)
|
|
if got_other_threads:
|
|
print "extended_continue (%s)" % (s)
|
|
else:
|
|
print "extended_continue (%s, other-threads: suspend)" % (s)
|
|
|
|
def rsp_vCont(options, cmd, cmd_args, rsp):
|
|
if cmd_args == '?':
|
|
# Skip the leading 'vCont;'
|
|
rsp = rsp[6:]
|
|
modes = string.split(rsp, ';')
|
|
s = "%s: supported extended continue modes include: " % (cmd)
|
|
|
|
for i, mode in enumerate(modes):
|
|
if i:
|
|
s += ', '
|
|
if mode == 'c':
|
|
s += 'continue'
|
|
elif mode == 'C':
|
|
s += 'continue with signal'
|
|
elif mode == 's':
|
|
s += 'step'
|
|
elif mode == 'S':
|
|
s += 'step with signal'
|
|
else:
|
|
s += 'unrecognized vCont mode: ', mode
|
|
print s
|
|
elif rsp:
|
|
if rsp[0] == 'T' or rsp[0] == 'S' or rsp[0] == 'W' or rsp[0] == 'X':
|
|
rsp_stop_reply (options, cmd, cmd_args, rsp)
|
|
return
|
|
if rsp[0] == 'O':
|
|
print "stdout: %s" % (rsp)
|
|
return
|
|
else:
|
|
print "not supported (cmd = '%s', args = '%s', rsp = '%s')" % (cmd, cmd_args, rsp)
|
|
|
|
def cmd_vAttach(options, cmd, args):
|
|
(extra_command, args) = string.split(args, ';')
|
|
if extra_command:
|
|
print "%s%s(%s)" % (cmd, extra_command, args)
|
|
else:
|
|
print "attach_pid(%s)" % args
|
|
|
|
def cmd_qRegisterInfo(options, cmd, args):
|
|
print 'query_register_info(reg_num=%i)' % (int(args, 16))
|
|
|
|
def rsp_qRegisterInfo(options, cmd, cmd_args, rsp):
|
|
global g_max_register_info_name_len
|
|
print 'query_register_info(reg_num=%i):' % (int(cmd_args, 16)),
|
|
if len(rsp) == 3 and rsp[0] == 'E':
|
|
g_max_register_info_name_len = 0
|
|
for reg_info in g_register_infos:
|
|
name_len = len(reg_info.name())
|
|
if g_max_register_info_name_len < name_len:
|
|
g_max_register_info_name_len = name_len
|
|
print' DONE'
|
|
else:
|
|
packet = Packet(rsp)
|
|
reg_info = RegisterInfo(packet.get_key_value_pairs())
|
|
g_register_infos.append(reg_info)
|
|
print reg_info
|
|
|
|
|
|
def cmd_qThreadInfo(options, cmd, args):
|
|
if cmd == 'qfThreadInfo':
|
|
query_type = 'first'
|
|
else:
|
|
query_type = 'subsequent'
|
|
print 'get_current_thread_list(type=%s)' % (query_type)
|
|
|
|
def rsp_qThreadInfo(options, cmd, cmd_args, rsp):
|
|
packet = Packet(rsp)
|
|
response_type = packet.get_char()
|
|
if response_type == 'm':
|
|
tids = packet.split_hex(';', 'big')
|
|
for i, tid in enumerate(tids):
|
|
if i:
|
|
print ',',
|
|
print '0x%x' % (tid),
|
|
print
|
|
elif response_type == 'l':
|
|
print 'END'
|
|
|
|
def rsp_hex_big_endian(options, cmd, cmd_args, rsp):
|
|
packet = Packet(rsp)
|
|
uval = packet.get_hex_uint('big')
|
|
print '%s: 0x%x' % (cmd, uval)
|
|
|
|
def cmd_read_memory(options, cmd, args):
|
|
packet = Packet(args)
|
|
addr = packet.get_hex_uint('big')
|
|
comma = packet.get_char()
|
|
size = packet.get_hex_uint('big')
|
|
print 'read_memory (addr = 0x%x, size = %u)' % (addr, size)
|
|
|
|
def dump_hex_memory_buffer(addr, hex_byte_str):
|
|
packet = Packet(hex_byte_str)
|
|
idx = 0
|
|
ascii = ''
|
|
uval = packet.get_hex_uint8()
|
|
while uval != None:
|
|
if ((idx % 16) == 0):
|
|
if ascii:
|
|
print ' ', ascii
|
|
ascii = ''
|
|
print '0x%x:' % (addr + idx),
|
|
print '%2.2x' % (uval),
|
|
if 0x20 <= uval and uval < 0x7f:
|
|
ascii += '%c' % uval
|
|
else:
|
|
ascii += '.'
|
|
uval = packet.get_hex_uint8()
|
|
idx = idx + 1
|
|
if ascii:
|
|
print ' ', ascii
|
|
ascii = ''
|
|
|
|
def cmd_write_memory(options, cmd, args):
|
|
packet = Packet(args)
|
|
addr = packet.get_hex_uint('big')
|
|
if packet.get_char() != ',':
|
|
print 'error: invalid write memory command (missing comma after address)'
|
|
return
|
|
size = packet.get_hex_uint('big')
|
|
if packet.get_char() != ':':
|
|
print 'error: invalid write memory command (missing colon after size)'
|
|
return
|
|
print 'write_memory (addr = 0x%x, size = %u, data:' % (addr, size)
|
|
dump_hex_memory_buffer (addr, packet.str)
|
|
|
|
def cmd_alloc_memory(options, cmd, args):
|
|
packet = Packet(args)
|
|
byte_size = packet.get_hex_uint('big')
|
|
if packet.get_char() != ',':
|
|
print 'error: invalid allocate memory command (missing comma after address)'
|
|
return
|
|
print 'allocate_memory (byte-size = %u (0x%x), permissions = %s)' % (byte_size, byte_size, packet.str)
|
|
|
|
def rsp_alloc_memory(options, cmd, cmd_args, rsp):
|
|
packet = Packet(rsp)
|
|
addr = packet.get_hex_uint('big')
|
|
print 'addr = 0x%x' % addr
|
|
|
|
def cmd_dealloc_memory(options, cmd, args):
|
|
packet = Packet(args)
|
|
addr = packet.get_hex_uint('big')
|
|
if packet.get_char() != ',':
|
|
print 'error: invalid allocate memory command (missing comma after address)'
|
|
return
|
|
print 'deallocate_memory (addr = 0x%x, permissions = %s)' % (addr, packet.str)
|
|
|
|
def rsp_memory_bytes(options, cmd, cmd_args, rsp):
|
|
addr = Packet(cmd_args).get_hex_uint('big')
|
|
dump_hex_memory_buffer (addr, rsp)
|
|
|
|
def get_register_name_equal_value(options, reg_num, hex_value_str):
|
|
if reg_num < len(g_register_infos):
|
|
reg_info = g_register_infos[reg_num]
|
|
value_str = reg_info.get_value_from_hex_string (hex_value_str)
|
|
s = reg_info.name() + ' = '
|
|
if options.symbolicator:
|
|
symbolicated_addresses = options.symbolicator.symbolicate (int(value_str, 0))
|
|
if symbolicated_addresses:
|
|
s += options.colors.magenta()
|
|
s += '%s' % symbolicated_addresses[0]
|
|
s += options.colors.reset()
|
|
return s
|
|
s += value_str
|
|
return s
|
|
else:
|
|
reg_value = Packet(hex_value_str).get_hex_uint(g_byte_order)
|
|
return 'reg(%u) = 0x%x' % (reg_num, reg_value)
|
|
|
|
def cmd_read_one_reg(options, cmd, args):
|
|
packet = Packet(args)
|
|
reg_num = packet.get_hex_uint('big')
|
|
tid = get_thread_from_thread_suffix (packet.str)
|
|
name = None
|
|
if reg_num < len(g_register_infos):
|
|
name = g_register_infos[reg_num].name ()
|
|
if packet.str:
|
|
packet.get_char() # skip ;
|
|
thread_info = packet.get_key_value_pairs()
|
|
tid = int(thread_info[0][1], 16)
|
|
s = 'read_register (reg_num=%u' % reg_num
|
|
if name:
|
|
s += ' (%s)' % (name)
|
|
if tid != None:
|
|
s += ', tid = 0x%4.4x' % (tid)
|
|
s += ')'
|
|
print s
|
|
|
|
def rsp_read_one_reg(options, cmd, cmd_args, rsp):
|
|
packet = Packet(cmd_args)
|
|
reg_num = packet.get_hex_uint('big')
|
|
print get_register_name_equal_value (options, reg_num, rsp)
|
|
|
|
def cmd_write_one_reg(options, cmd, args):
|
|
packet = Packet(args)
|
|
reg_num = packet.get_hex_uint('big')
|
|
if packet.get_char() != '=':
|
|
print 'error: invalid register write packet'
|
|
else:
|
|
name = None
|
|
hex_value_str = packet.get_hex_chars()
|
|
tid = get_thread_from_thread_suffix (packet.str)
|
|
s = 'write_register (reg_num=%u' % reg_num
|
|
if name:
|
|
s += ' (%s)' % (name)
|
|
s += ', value = '
|
|
s += get_register_name_equal_value(options, reg_num, hex_value_str)
|
|
if tid != None:
|
|
s += ', tid = 0x%4.4x' % (tid)
|
|
s += ')'
|
|
print s
|
|
|
|
def dump_all_regs(packet):
|
|
for reg_info in g_register_infos:
|
|
nibble_size = reg_info.bit_size() / 4
|
|
hex_value_str = packet.get_hex_chars(nibble_size)
|
|
if hex_value_str != None:
|
|
value = reg_info.get_value_from_hex_string (hex_value_str)
|
|
print '%*s = %s' % (g_max_register_info_name_len, reg_info.name(), value)
|
|
else:
|
|
return
|
|
|
|
def cmd_read_all_regs(cmd, cmd_args):
|
|
packet = Packet(cmd_args)
|
|
packet.get_char() # toss the 'g' command character
|
|
tid = get_thread_from_thread_suffix (packet.str)
|
|
if tid != None:
|
|
print 'read_all_register(thread = 0x%4.4x)' % tid
|
|
else:
|
|
print 'read_all_register()'
|
|
|
|
def rsp_read_all_regs(options, cmd, cmd_args, rsp):
|
|
packet = Packet(rsp)
|
|
dump_all_regs (packet)
|
|
|
|
def cmd_write_all_regs(options, cmd, args):
|
|
packet = Packet(args)
|
|
print 'write_all_registers()'
|
|
dump_all_regs (packet)
|
|
|
|
g_bp_types = [ "software_bp", "hardware_bp", "write_wp", "read_wp", "access_wp" ]
|
|
|
|
def cmd_bp(options, cmd, args):
|
|
if cmd == 'Z':
|
|
s = 'set_'
|
|
else:
|
|
s = 'clear_'
|
|
packet = Packet (args)
|
|
bp_type = packet.get_hex_uint('big')
|
|
packet.get_char() # Skip ,
|
|
bp_addr = packet.get_hex_uint('big')
|
|
packet.get_char() # Skip ,
|
|
bp_size = packet.get_hex_uint('big')
|
|
s += g_bp_types[bp_type]
|
|
s += " (addr = 0x%x, size = %u)" % (bp_addr, bp_size)
|
|
print s
|
|
|
|
def cmd_mem_rgn_info(options, cmd, args):
|
|
packet = Packet(args)
|
|
packet.get_char() # skip ':' character
|
|
addr = packet.get_hex_uint('big')
|
|
print 'get_memory_region_info (addr=0x%x)' % (addr)
|
|
|
|
def cmd_kill(options, cmd, args):
|
|
print 'kill_process()'
|
|
|
|
gdb_remote_commands = {
|
|
'\\?' : { 'cmd' : cmd_stop_reply , 'rsp' : rsp_stop_reply , 'name' : "stop reply pacpket"},
|
|
'QStartNoAckMode' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if no ack mode is supported"},
|
|
'QThreadSuffixSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if thread suffix is supported" },
|
|
'QListThreadsInStopReply' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if threads in stop reply packets are supported" },
|
|
'qVAttachOrWaitSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if threads attach with optional wait is supported" },
|
|
'qHostInfo' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get host information" },
|
|
'vCont' : { 'cmd' : cmd_vCont , 'rsp' : rsp_vCont , 'name' : "extended continue command" },
|
|
'vAttach' : { 'cmd' : cmd_vAttach , 'rsp' : rsp_stop_reply , 'name' : "attach to process" },
|
|
'qRegisterInfo' : { 'cmd' : cmd_qRegisterInfo , 'rsp' : rsp_qRegisterInfo , 'name' : "query register info" },
|
|
'qfThreadInfo' : { 'cmd' : cmd_qThreadInfo , 'rsp' : rsp_qThreadInfo , 'name' : "get current thread list" },
|
|
'qsThreadInfo' : { 'cmd' : cmd_qThreadInfo , 'rsp' : rsp_qThreadInfo , 'name' : "get current thread list" },
|
|
'qShlibInfoAddr' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_hex_big_endian , 'name' : "get shared library info address" },
|
|
'qMemoryRegionInfo' : { 'cmd' : cmd_mem_rgn_info , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get memory region information" },
|
|
'm' : { 'cmd' : cmd_read_memory , 'rsp' : rsp_memory_bytes , 'name' : "read memory" },
|
|
'M' : { 'cmd' : cmd_write_memory , 'rsp' : rsp_ok_means_success , 'name' : "write memory" },
|
|
'_M' : { 'cmd' : cmd_alloc_memory , 'rsp' : rsp_alloc_memory , 'name' : "allocate memory" },
|
|
'_m' : { 'cmd' : cmd_dealloc_memory, 'rsp' : rsp_ok_means_success , 'name' : "deallocate memory" },
|
|
'p' : { 'cmd' : cmd_read_one_reg , 'rsp' : rsp_read_one_reg , 'name' : "read single register" },
|
|
'P' : { 'cmd' : cmd_write_one_reg , 'rsp' : rsp_ok_means_success , 'name' : "write single register" },
|
|
'g' : { 'cmd' : cmd_read_all_regs , 'rsp' : rsp_read_all_regs , 'name' : "read all registers" },
|
|
'G' : { 'cmd' : cmd_write_all_regs, 'rsp' : rsp_ok_means_success , 'name' : "write all registers" },
|
|
'z' : { 'cmd' : cmd_bp , 'rsp' : rsp_ok_means_success , 'name' : "clear breakpoint or watchpoint" },
|
|
'Z' : { 'cmd' : cmd_bp , 'rsp' : rsp_ok_means_success , 'name' : "set breakpoint or watchpoint" },
|
|
'k' : { 'cmd' : cmd_kill , 'rsp' : rsp_stop_reply , 'name' : "kill process" },
|
|
}
|
|
def parse_gdb_log_file(file, options):
|
|
'''Parse a GDB log file that was generated by enabling logging with:
|
|
(lldb) log enable --threadsafe --timestamp --file <FILE> gdb-remote packets
|
|
This log file will contain timestamps and this function will then normalize
|
|
those packets to be relative to the first value timestamp that is found and
|
|
show delta times between log lines and also keep track of how long it takes
|
|
for GDB remote commands to make a send/receive round trip. This can be
|
|
handy when trying to figure out why some operation in the debugger is taking
|
|
a long time during a preset set of debugger commands.'''
|
|
|
|
tricky_commands = [ 'qRegisterInfo' ]
|
|
timestamp_regex = re.compile('(\s*)([1-9][0-9]+\.[0-9]+)([^0-9].*)$')
|
|
packet_name_regex = re.compile('([A-Za-z_]+)[^a-z]')
|
|
packet_transmit_name_regex = re.compile('(?P<direction>send|read) packet: (?P<packet>.*)')
|
|
packet_contents_name_regex = re.compile('\$([^#]+)#[0-9a-fA-F]{2}')
|
|
packet_names_regex_str = '(' + '|'.join(gdb_remote_commands.keys()) + ')(.*)';
|
|
packet_names_regex = re.compile(packet_names_regex_str);
|
|
|
|
base_time = 0.0
|
|
last_time = 0.0
|
|
packet_send_time = 0.0
|
|
packet_total_times = {}
|
|
packet_count = {}
|
|
file = open(file)
|
|
lines = file.read().splitlines()
|
|
last_command = None
|
|
last_command_args = None
|
|
last_command_packet = None
|
|
for line in lines:
|
|
packet_name = None
|
|
m = packet_transmit_name_regex.search(line)
|
|
is_command = False
|
|
if m:
|
|
direction = m.group('direction')
|
|
is_command = direction == 'send'
|
|
packet = m.group('packet')
|
|
sys.stdout.write(options.colors.green())
|
|
if options.quiet:
|
|
if is_command:
|
|
print '-->', packet
|
|
else:
|
|
print '<--', packet
|
|
else:
|
|
print '# ', line
|
|
sys.stdout.write(options.colors.reset())
|
|
|
|
#print 'direction = "%s", packet = "%s"' % (direction, packet)
|
|
|
|
if packet[0] == '+':
|
|
print 'ACK'
|
|
elif packet[0] == '-':
|
|
print 'NACK'
|
|
elif packet[0] == '$':
|
|
m = packet_contents_name_regex.match(packet)
|
|
if m:
|
|
contents = m.group(1)
|
|
if is_command:
|
|
m = packet_names_regex.match (contents)
|
|
if m:
|
|
last_command = m.group(1)
|
|
packet_name = last_command
|
|
last_command_args = m.group(2)
|
|
last_command_packet = contents
|
|
gdb_remote_commands[last_command]['cmd'](options, last_command, last_command_args)
|
|
else:
|
|
packet_match = packet_name_regex.match (line[idx+14:])
|
|
if packet_match:
|
|
packet_name = packet_match.group(1)
|
|
for tricky_cmd in tricky_commands:
|
|
if packet_name.find (tricky_cmd) == 0:
|
|
packet_name = tricky_cmd
|
|
else:
|
|
packet_name = contents
|
|
last_command = None
|
|
last_command_args = None
|
|
last_command_packet = None
|
|
elif last_command:
|
|
gdb_remote_commands[last_command]['rsp'](options, last_command, last_command_args, contents)
|
|
else:
|
|
print 'error: invalid packet: "', packet, '"'
|
|
else:
|
|
print '???'
|
|
else:
|
|
print '## ', line
|
|
match = timestamp_regex.match (line)
|
|
if match:
|
|
curr_time = float (match.group(2))
|
|
delta = 0.0
|
|
if base_time:
|
|
delta = curr_time - last_time
|
|
else:
|
|
base_time = curr_time
|
|
|
|
if is_command:
|
|
packet_send_time = curr_time
|
|
elif line.find('read packet: $') >= 0 and packet_name:
|
|
if packet_name in packet_total_times:
|
|
packet_total_times[packet_name] += delta
|
|
packet_count[packet_name] += 1
|
|
else:
|
|
packet_total_times[packet_name] = delta
|
|
packet_count[packet_name] = 1
|
|
packet_name = None
|
|
|
|
if not options or not options.quiet:
|
|
print '%s%.6f %+.6f%s' % (match.group(1), curr_time - base_time, delta, match.group(3))
|
|
last_time = curr_time
|
|
# else:
|
|
# print line
|
|
if packet_total_times:
|
|
total_packet_time = 0.0
|
|
total_packet_count = 0
|
|
for key, vvv in packet_total_times.items():
|
|
# print ' key = (%s) "%s"' % (type(key), key)
|
|
# print 'value = (%s) %s' % (type(vvv), vvv)
|
|
# if type(vvv) == 'float':
|
|
total_packet_time += vvv
|
|
for key, vvv in packet_count.items():
|
|
total_packet_count += vvv
|
|
|
|
print '#---------------------------------------------------'
|
|
print '# Packet timing summary:'
|
|
print '# Totals: time - %6f count %6d' % (total_packet_time, total_packet_count)
|
|
print '#---------------------------------------------------'
|
|
print '# Packet Time (sec) Percent Count '
|
|
print '#------------------------- ---------- ------- ------'
|
|
if options and options.sort_count:
|
|
res = sorted(packet_count, key=packet_count.__getitem__, reverse=True)
|
|
else:
|
|
res = sorted(packet_total_times, key=packet_total_times.__getitem__, reverse=True)
|
|
|
|
if last_time > 0.0:
|
|
for item in res:
|
|
packet_total_time = packet_total_times[item]
|
|
packet_percent = (packet_total_time / total_packet_time)*100.0
|
|
if packet_percent >= 10.0:
|
|
print " %24s %.6f %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item])
|
|
else:
|
|
print " %24s %.6f %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item])
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
usage = "usage: gdbremote [options]"
|
|
description='''The command disassembles a GDB remote packet log.'''
|
|
parser = optparse.OptionParser(description=description, prog='gdbremote',usage=usage)
|
|
parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
|
|
parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='display verbose debug info', default=False)
|
|
parser.add_option('-C', '--color', action='store_true', dest='color', help='add terminal colors', default=False)
|
|
parser.add_option('-c', '--sort-by-count', action='store_true', dest='sort_count', help='display verbose debug info', default=False)
|
|
parser.add_option('--crashlog', type='string', dest='crashlog', help='symbolicate using a darwin crash log file', default=False)
|
|
try:
|
|
(options, args) = parser.parse_args(sys.argv[1:])
|
|
except:
|
|
print 'error: argument error'
|
|
sys.exit(1)
|
|
|
|
options.colors = TerminalColors(options.color)
|
|
options.symbolicator = None
|
|
if options.crashlog:
|
|
import lldb
|
|
lldb.debugger = lldb.SBDebugger.Create()
|
|
import lldb.macosx.crashlog
|
|
options.symbolicator = lldb.macosx.crashlog.CrashLog(options.crashlog)
|
|
print '%s' % (options.symbolicator)
|
|
|
|
# This script is being run from the command line, create a debugger in case we are
|
|
# going to use any debugger functions in our function.
|
|
for file in args:
|
|
print '#----------------------------------------------------------------------'
|
|
print "# GDB remote log file: '%s'" % file
|
|
print '#----------------------------------------------------------------------'
|
|
parse_gdb_log_file (file, options)
|
|
if options.symbolicator:
|
|
print '%s' % (options.symbolicator)
|
|
|
|
else:
|
|
import lldb
|
|
if lldb.debugger:
|
|
# This initializer is being run from LLDB in the embedded command interpreter
|
|
# Add any commands contained in this module to LLDB
|
|
lldb.debugger.HandleCommand('command script add -f gdbremote.start_gdb_log start_gdb_log')
|
|
lldb.debugger.HandleCommand('command script add -f gdbremote.stop_gdb_log stop_gdb_log')
|
|
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'
|