summaryrefslogtreecommitdiffstats
path: root/gnu/llvm/lldb/examples/python/gdbremote.py
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/llvm/lldb/examples/python/gdbremote.py')
-rwxr-xr-xgnu/llvm/lldb/examples/python/gdbremote.py1630
1 files changed, 1630 insertions, 0 deletions
diff --git a/gnu/llvm/lldb/examples/python/gdbremote.py b/gnu/llvm/lldb/examples/python/gdbremote.py
new file mode 100755
index 00000000000..dcacbfccdaf
--- /dev/null
+++ b/gnu/llvm/lldb/examples/python/gdbremote.py
@@ -0,0 +1,1630 @@
+#!/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 subprocess
+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
+
+def get_hex_string_if_all_printable(str):
+ try:
+ s = binascii.unhexlify(str)
+ if all(c in string.printable for c in s):
+ return s
+ except TypeError:
+ pass
+ return None
+
+# 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 is not 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 is None:
+ return None
+ return int(hex_str, 16)
+ else:
+ uval = self.get_hex_uint8()
+ if uval is None:
+ return None
+ uval_result = 0
+ shift = 0
+ while uval is not 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 cmd_QSetWithHexString(options, cmd, args):
+ print('%s("%s")' % (cmd[:-1], binascii.unhexlify(args)))
+
+def cmd_QSetWithString(options, cmd, args):
+ print('%s("%s")' % (cmd[:-1], args))
+
+def cmd_QSetWithUnsigned(options, cmd, args):
+ print('%s(%i)' % (cmd[:-1], int(args)))
+
+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()
+ if xml_string:
+ 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 True:
+ 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]
+ unhex_value = get_hex_string_if_all_printable(value)
+ if unhex_value:
+ print("%*s = %s (%s)" % (max_key_len, key, value, unhex_value))
+ else:
+ 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_qSpeedTest(options, cmd, args):
+ print(("qSpeedTest: cmd='%s', args='%s'" % (cmd, args)))
+
+
+def rsp_qSpeedTest(options, cmd, cmd_args, rsp):
+ print(("qSpeedTest: rsp='%s' cmd='%s', args='%s'" % (rsp, cmd, args)))
+
+
+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'
+ elif mode == 't':
+ s += 'stop'
+ # else:
+ # s += 'unrecognized vCont mode: ', str(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)), end=' ')
+ 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(',', end=' ')
+ print('0x%x' % (tid), end=' ')
+ print()
+ elif response_type == 'l':
+ print('END')
+
+
+def rsp_hex_big_endian(options, cmd, cmd_args, rsp):
+ if rsp == '':
+ print("%s%s is not supported" % (cmd, cmd_args))
+ else:
+ 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_hex_uint('big')
+ comma = packet.get_char()
+ size = packet.get_hex_uint('big')
+ 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_hex_uint('big')
+ comma = packet.get_char()
+ size = packet.get_hex_uint('big')
+ 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 is not None:
+ if ((idx % 16) == 0):
+ if ascii:
+ print(' ', ascii)
+ ascii = ''
+ print('0x%x:' % (addr + idx), end=' ')
+ print('%2.2x' % (uval), end=' ')
+ 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 is not 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 is not 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 is not 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 is not 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_QSetWithUnsigned, 'rsp': rsp_ok_means_success, 'name': "set if we should detach on error"},
+ 'QSetDisableASLR:': {'cmd': cmd_QSetWithUnsigned, '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_QSetWithString, 'rsp': rsp_ok_means_supported, 'name': "set the arch to launch in case the file contains multiple architectures"},
+ '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"},
+ 'qSpeedTest': {'cmd':cmd_qSpeedTest, 'rsp': rsp_qSpeedTest, 'name': 'speed test packdet'},
+ '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"},
+ 'QSetSTDIN:' : {'cmd' : cmd_QSetWithHexString, 'rsp' : rsp_ok_means_success, 'name': "set STDIN prior to launching with A packet"},
+ 'QSetSTDOUT:' : {'cmd' : cmd_QSetWithHexString, 'rsp' : rsp_ok_means_success, 'name': "set STDOUT prior to launching with A packet"},
+ 'QSetSTDERR:' : {'cmd' : cmd_QSetWithHexString, 'rsp' : rsp_ok_means_success, 'name': "set STDERR prior to launching with A packet"},
+ 'QEnvironment:' : {'cmd' : cmd_QSetWithString, 'rsp' : rsp_ok_means_success, 'name': "set an environment variable prior to launching with A packet"},
+ 'QEnvironmentHexEncoded:' : {'cmd' : cmd_QSetWithHexString, 'rsp' : rsp_ok_means_success, 'name': "set an environment variable prior to launching with A packet"},
+ '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 round_up(n, incr):
+ return float(((int(n) + incr) / incr) * incr)
+
+
+def plot_latencies(sec_times):
+ # import numpy as np
+ import matplotlib.pyplot as plt
+
+ for (i, name) in enumerate(sec_times.keys()):
+ times = sec_times[name]
+ if len(times) <= 1:
+ continue
+ plt.subplot(2, 1, 1)
+ plt.title('Packet "%s" Times' % (name))
+ plt.xlabel('Packet')
+ units = 'ms'
+ adj_times = []
+ max_time = 0.0
+ for time in times:
+ time = time * 1000.0
+ adj_times.append(time)
+ if time > max_time:
+ max_time = time
+ if max_time < 1.0:
+ units = 'us'
+ max_time = 0.0
+ for i in range(len(adj_times)):
+ adj_times[i] *= 1000.0
+ if adj_times[i] > max_time:
+ max_time = adj_times[i]
+ plt.ylabel('Time (%s)' % (units))
+ max_y = None
+ for i in [5.0, 10.0, 25.0, 50.0]:
+ if max_time < i:
+ max_y = round_up(max_time, i)
+ break
+ if max_y is None:
+ max_y = round_up(max_time, 100.0)
+ plt.ylim(0.0, max_y)
+ plt.plot(adj_times, 'o-')
+ plt.show()
+
+
+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
+ min_time = 100000000.0
+ packet_total_times = {}
+ all_packet_times = []
+ packet_times = {}
+ packet_counts = {}
+ 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('-->', end=' ')
+ else:
+ print('<--', end=' ')
+ if not options.quiet:
+ print('ACK')
+ continue
+ elif packet[0] == '-':
+ if is_command:
+ print('-->', end=' ')
+ else:
+ print('<--', end=' ')
+ 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('-->', end=' ')
+ else:
+ print('<--', end=' ')
+ 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
+ all_packet_times.append(delta)
+ delta = 0.0
+ if base_time:
+ delta = curr_time - last_time
+ else:
+ base_time = curr_time
+
+ if not is_command:
+ if line.find('read packet: $') >= 0 and packet_name:
+ if packet_name in packet_total_times:
+ packet_total_times[packet_name] += delta
+ packet_counts[packet_name] += 1
+ else:
+ packet_total_times[packet_name] = delta
+ packet_counts[packet_name] = 1
+ if packet_name not in packet_times:
+ packet_times[packet_name] = []
+ packet_times[packet_name].append(delta)
+ packet_name = None
+ if min_time > delta:
+ min_time = delta
+
+ 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(all_packet_times)
+ if average and std_dev:
+ print('%u packets with average packet time of %f and standard deviation of %f' % (len(all_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_counts.items():
+ total_packet_count += vvv
+
+ print('#------------------------------------------------------------')
+ print('# Packet timing summary:')
+ print('# Totals: time = %6f, count = %6d' % (total_packet_time,
+ total_packet_count))
+ print('# Min packet time: time = %6f' % (min_time))
+ print('#------------------------------------------------------------')
+ print('# Packet Time (sec) Percent Count Latency')
+ print('#------------------------- ----------- ------- ------ -------')
+ if options and options.sort_count:
+ res = sorted(
+ packet_counts,
+ key=packet_counts.__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
+ packet_count = packet_counts[item]
+ print(" %24s %11.6f %5.2f%% %6d %9.6f" % (
+ item, packet_total_time, packet_percent, packet_count,
+ float(packet_total_time) / float(packet_count)))
+ if options.plot:
+ plot_latencies(packet_times)
+
+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(
+ '--plot',
+ action='store_true',
+ dest='plot',
+ help='plot packet latencies by packet type',
+ 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')