diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 |
commit | 9e6d35490a6542f9c97607f93c2ef8ca8e03cbcc (patch) | |
tree | dd2a1ddf0476664c2b823409c36cbccd52662ca7 /examples/python/gdbremote.py | |
parent | 3bd2e91faeb9eeec1aae82c64a3253afff551cfd (diff) |
Notes
Diffstat (limited to 'examples/python/gdbremote.py')
-rwxr-xr-x | examples/python/gdbremote.py | 1362 |
1 files changed, 1362 insertions, 0 deletions
diff --git a/examples/python/gdbremote.py b/examples/python/gdbremote.py new file mode 100755 index 0000000000000..4cbfdb2ba333a --- /dev/null +++ b/examples/python/gdbremote.py @@ -0,0 +1,1362 @@ +#!/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 binascii +import commands +import json +import math +import optparse +import os +import re +import shlex +import string +import sys +import tempfile +import xml.etree.ElementTree as ET + +#---------------------------------------------------------------------- +# Global variables +#---------------------------------------------------------------------- +g_log_file = '' +g_byte_order = 'little' +g_number_regex = re.compile('^(0x[0-9a-fA-F]+|[0-9]+)') +g_thread_id_regex = re.compile('^(-1|[0-9a-fA-F]+|0)') + +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[33m"; + else: + return "\x1b[43m"; + 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 skip_exact_string(self, s): + if self.str and self.str.startswith(s): + self.str = self.str[len(s):] + return True + else: + return False + + def get_thread_id(self, fail_value = -1): + match = g_number_regex.match (self.str) + if match: + number_str = match.group(1) + self.str = self.str[len(number_str):] + return int(number_str, 0) + else: + return fail_value + + 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_number(self, fail_value=-1): + '''Get a number from the packet. The number must be in big endian format and should be parsed + according to its prefix (starts with "0x" means hex, starts with "0" means octal, starts with + [1-9] means decimal, etc)''' + match = g_number_regex.match (self.str) + if match: + number_str = match.group(1) + self.str = self.str[len(number_str):] + return int(number_str, 0) + else: + return fail_value + + + def get_hex_ascii_str(self, n=0): + hex_chars = self.get_hex_chars(n) + if hex_chars: + return binascii.unhexlify(hex_chars) + else: + return None + + 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() + if ';' in self.str: + 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_qThreadStopInfo(options, cmd, args): + packet = Packet(args) + tid = packet.get_hex_uint('big') + print "get_thread_stop_info (tid = 0x%x)" % (tid) + +def cmd_stop_reply(options, cmd, args): + print "get_last_stop_info()" + return False + +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() + key_value_pairs = packet.get_key_value_pairs() + for key_value_pair in key_value_pairs: + key = key_value_pair[0] + if is_hex_byte(key): + reg_num = Packet(key).get_hex_uint8() + if reg_num < len(g_register_infos): + reg_info = g_register_infos[reg_num] + key_value_pair[0] = reg_info.name() + key_value_pair[1] = reg_info.get_value_from_hex_string (key_value_pair[1]) + elif key == 'jthreads' or key == 'jstopinfo': + key_value_pair[1] = binascii.unhexlify(key_value_pair[1]) + key_value_pairs.insert(0, ['signal', signo]) + print 'stop_reply():' + dump_key_value_pairs (key_value_pairs) + elif stop_type == 'W': + exit_status = packet.get_hex_uint8() + print 'stop_reply(): exit (status=%i)' % exit_status + elif stop_type == 'O': + print 'stop_reply(): 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 + return False + +def cmd_qSymbol(options, cmd, args): + if args == ':': + print 'ready to serve symbols' + else: + packet = Packet(args) + symbol_addr = packet.get_hex_uint('big') + if symbol_addr is None: + if packet.skip_exact_string(':'): + symbol_name = packet.get_hex_ascii_str() + print 'lookup_symbol("%s") -> symbol not available yet' % (symbol_name) + else: + print 'error: bad command format' + else: + if packet.skip_exact_string(':'): + symbol_name = packet.get_hex_ascii_str() + print 'lookup_symbol("%s") -> 0x%x' % (symbol_name, symbol_addr) + else: + print 'error: bad command format' + +def rsp_qSymbol(options, cmd, cmd_args, rsp): + if len(rsp) == 0: + print "Unsupported" + else: + if rsp == "OK": + print "No more symbols to lookup" + else: + packet = Packet(rsp) + if packet.skip_exact_string("qSymbol:"): + symbol_name = packet.get_hex_ascii_str() + print 'lookup_symbol("%s")' % (symbol_name) + else: + print 'error: response string should start with "qSymbol:": respnse is "%s"' % (rsp) + +def cmd_qXfer(options, cmd, args): + # $qXfer:features:read:target.xml:0,1ffff#14 + print "read target special data %s" % (args) + return True + +def rsp_qXfer(options, cmd, cmd_args, rsp): + data = string.split(cmd_args, ':') + if data[0] == 'features': + if data[1] == 'read': + filename, extension = os.path.splitext(data[2]) + if extension == '.xml': + response = Packet(rsp) + xml_string = response.get_hex_ascii_str() + ch = xml_string[0] + if ch == 'l': + xml_string = xml_string[1:] + xml_root = ET.fromstring(xml_string) + for reg_element in xml_root.findall("./feature/reg"): + if not 'value_regnums' in reg_element.attrib: + reg_info = RegisterInfo([]) + if 'name' in reg_element.attrib: + reg_info.info['name'] = reg_element.attrib['name'] + else: + reg_info.info['name'] = 'unspecified' + if 'encoding' in reg_element.attrib: + reg_info.info['encoding'] = reg_element.attrib['encoding'] + else: + reg_info.info['encoding'] = 'uint' + if 'offset' in reg_element.attrib: + reg_info.info['offset'] = reg_element.attrib['offset'] + if 'bitsize' in reg_element.attrib: + reg_info.info['bitsize'] = reg_element.attrib['bitsize'] + g_register_infos.append(reg_info) + print 'XML for "%s":' % (data[2]) + ET.dump(xml_root) + +def cmd_A(options, cmd, args): + print 'launch process:' + packet = Packet(args) + while 1: + arg_len = packet.get_number() + if arg_len == -1: + break + if not packet.skip_exact_string(','): + break + arg_idx = packet.get_number() + if arg_idx == -1: + break + if not packet.skip_exact_string(','): + break; + arg_value = packet.get_hex_ascii_str(arg_len) + print 'argv[%u] = "%s"' % (arg_idx, arg_value) + +def cmd_qC(options, cmd, args): + print "query_current_thread_id()" + +def rsp_qC(options, cmd, cmd_args, rsp): + packet = Packet(rsp) + if packet.skip_exact_string("QC"): + tid = packet.get_thread_id() + print "current_thread_id = %#x" % (tid) + else: + print "current_thread_id = old thread ID" + +def cmd_query_packet(options, cmd, args): + if args: + print "%s%s" % (cmd, args) + else: + print "%s" % (cmd) + return False + +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 dump_key_value_pairs(key_value_pairs): + max_key_len = 0 + for key_value_pair in key_value_pairs: + key_len = len(key_value_pair[0]) + if max_key_len < key_len: + max_key_len = key_len + for key_value_pair in key_value_pairs: + key = key_value_pair[0] + value = key_value_pair[1] + print "%*s = %s" % (max_key_len, key, value) + +def rsp_dump_key_value_pairs(options, cmd, cmd_args, rsp): + if rsp: + print '%s response:' % (cmd) + packet = Packet(rsp) + key_value_pairs = packet.get_key_value_pairs() + dump_key_value_pairs(key_value_pairs) + else: + print "not supported" + +def cmd_c(options, cmd, args): + print "continue()" + return False + +def cmd_s(options, cmd, args): + print "step()" + return False + +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) + return False + +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 = %u)" % int(args, 16) + return False + + +def cmd_qRegisterInfo(options, cmd, args): + print 'query_register_info(reg_num=%i)' % (int(args, 16)) + return False + +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 + return False + +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) + return False + +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_mem_bin(options, cmd, args): + # x0x7fff5fc39200,0x200 + packet = Packet(args) + addr = packet.get_number() + comma = packet.get_char() + size = packet.get_number() + print 'binary_read_memory (addr = 0x%16.16x, size = %u)' % (addr, size) + return False + +def rsp_mem_bin_bytes(options, cmd, cmd_args, rsp): + packet = Packet(cmd_args) + addr = packet.get_number() + comma = packet.get_char() + size = packet.get_number() + print 'memory:' + if size > 0: + dump_hex_memory_buffer (addr, rsp) + +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%16.16x, size = %u)' % (addr, size) + return False + +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%16.16x, size = %u, data:' % (addr, size) + dump_hex_memory_buffer (addr, packet.str) + return False + +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) + return False + +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)' + else: + print 'deallocate_memory (addr = 0x%x, permissions = %s)' % (addr, packet.str) + return False +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 + return False + +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 + return False + +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()' + return False + +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) + return False + +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 + return False + +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) + return False + +def cmd_kill(options, cmd, args): + print 'kill_process()' + return False + +def cmd_jThreadsInfo(options, cmd, args): + print 'jThreadsInfo()' + return False + +def cmd_jGetLoadedDynamicLibrariesInfos(options, cmd, args): + print 'jGetLoadedDynamicLibrariesInfos()' + return False + +def decode_packet(s, start_index = 0): + #print '\ndecode_packet("%s")' % (s[start_index:]) + index = s.find('}', start_index) + have_escapes = index != -1 + if have_escapes: + normal_s = s[start_index:index] + else: + normal_s = s[start_index:] + #print 'normal_s = "%s"' % (normal_s) + if have_escapes: + escape_char = '%c' % (ord(s[index+1]) ^ 0x20) + #print 'escape_char for "%s" = %c' % (s[index:index+2], escape_char) + return normal_s + escape_char + decode_packet(s, index+2) + else: + return normal_s + +def rsp_json(options, cmd, cmd_args, rsp): + print '%s() reply:' % (cmd) + json_tree = json.loads(rsp) + print json.dumps(json_tree, indent=4, separators=(',', ': ')) + + +def rsp_jGetLoadedDynamicLibrariesInfos(options, cmd, cmd_args, rsp): + if cmd_args: + rsp_json(options, cmd, cmd_args, rsp) + else: + rsp_ok_means_supported(options, cmd, cmd_args, rsp) + +gdb_remote_commands = { + '\\?' : { 'cmd' : cmd_stop_reply , 'rsp' : rsp_stop_reply , 'name' : "stop reply pacpket"}, + 'qThreadStopInfo' : { 'cmd' : cmd_qThreadStopInfo , '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" }, + 'QSetDetachOnError' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_success , 'name' : "set if we should detach on error" }, + 'QSetDisableASLR' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_success , 'name' : "set if we should disable ASLR" }, + 'qLaunchSuccess' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_success , 'name' : "check on launch success for the A packet" }, + 'A' : { 'cmd' : cmd_A , 'rsp' : rsp_ok_means_success , 'name' : "launch process" }, + 'QLaunchArch' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "set if we should disable ASLR" }, + 'qVAttachOrWaitSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "set the launch architecture" }, + 'qHostInfo' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get host information" }, + 'qC' : { 'cmd' : cmd_qC , 'rsp' : rsp_qC , 'name' : "return the current thread ID" }, + 'vCont' : { 'cmd' : cmd_vCont , 'rsp' : rsp_vCont , 'name' : "extended continue command" }, + 'vAttach' : { 'cmd' : cmd_vAttach , 'rsp' : rsp_stop_reply , 'name' : "attach to process" }, + 'c' : { 'cmd' : cmd_c , 'rsp' : rsp_stop_reply , 'name' : "continue" }, + 's' : { 'cmd' : cmd_s , 'rsp' : rsp_stop_reply , 'name' : "step" }, + '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" }, + 'qProcessInfo' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get process info" }, + 'qSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query supported" }, + 'qXfer:' : { 'cmd' : cmd_qXfer , 'rsp' : rsp_qXfer , 'name' : "qXfer" }, + 'qSymbol:' : { 'cmd' : cmd_qSymbol , 'rsp' : rsp_qSymbol , 'name' : "qSymbol" }, + 'x' : { 'cmd' : cmd_read_mem_bin , 'rsp' : rsp_mem_bin_bytes , 'name' : "read memory binary" }, + 'X' : { 'cmd' : cmd_write_memory , 'rsp' : rsp_ok_means_success , 'name' : "write memory binary" }, + '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" }, + 'jThreadsInfo' : { 'cmd' : cmd_jThreadsInfo , 'rsp' : rsp_json , 'name' : "JSON get all threads info" }, + 'jGetLoadedDynamicLibrariesInfos:' : { 'cmd' : cmd_jGetLoadedDynamicLibrariesInfos, 'rsp' : rsp_jGetLoadedDynamicLibrariesInfos, 'name' : 'JSON get loaded dynamic libraries' }, +} + +def calculate_mean_and_standard_deviation(floats): + sum = 0.0 + count = len(floats) + if count == 0: + return (0.0, 0.0) + for f in floats: + sum += f + mean = sum / count + accum = 0.0 + for f in floats: + delta = f - mean + accum += delta * delta + + std_dev = math.sqrt(accum / (count-1)); + return (mean, std_dev) + +def parse_gdb_log_file(path, options): + f = open(path) + parse_gdb_log(f, options) + f.close() + +def parse_gdb_log(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_checksum_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_times = [] + packet_count = {} + lines = file.read().splitlines() + last_command = None + last_command_args = None + last_command_packet = None + hide_next_response = False + num_lines = len(lines) + skip_count = 0 + for (line_index, line) in enumerate(lines): + # See if we need to skip any lines + if skip_count > 0: + skip_count -= 1 + continue + m = packet_transmit_name_regex.search(line) + is_command = False + direction = None + if m: + direction = m.group('direction') + is_command = direction == 'send' + packet = m.group('packet') + sys.stdout.write(options.colors.green()) + if not options.quiet and not hide_next_response: + print '# ', line + sys.stdout.write(options.colors.reset()) + + #print 'direction = "%s", packet = "%s"' % (direction, packet) + + if packet[0] == '+': + if is_command: + print '-->', + else: + print '<--', + if not options.quiet: print 'ACK' + continue + elif packet[0] == '-': + if is_command: + print '-->', + else: + print '<--', + if not options.quiet: print 'NACK' + continue + elif packet[0] == '$': + m = packet_contents_name_regex.match(packet) + if not m and packet[0] == '$': + multiline_packet = packet + idx = line_index + 1 + while idx < num_lines: + if not options.quiet and not hide_next_response: + print '# ', lines[idx] + multiline_packet += lines[idx] + m = packet_contents_name_regex.match(multiline_packet) + if m: + packet = multiline_packet + skip_count = idx - line_index + break + else: + idx += 1 + if m: + if is_command: + print '-->', + else: + print '<--', + contents = decode_packet(m.group(1)) + if is_command: + hide_next_response = False + m = packet_names_regex.match (contents) + if m: + last_command = m.group(1) + if last_command == '?': + last_command = '\\?' + packet_name = last_command + last_command_args = m.group(2) + last_command_packet = contents + hide_next_response = gdb_remote_commands[last_command]['cmd'](options, last_command, last_command_args) + else: + packet_match = packet_name_regex.match (contents) + 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)) + if last_time and not is_command: + delta = curr_time - last_time + packet_times.append(delta) + 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 + (average, std_dev) = calculate_mean_and_standard_deviation(packet_times) + if average and std_dev: + print '%u packets with average packet time of %f and standard deviation of %f' % (len(packet_times), average, std_dev) + 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. + if len(args): + 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: + parse_gdb_log(sys.stdin, options) + +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' |