1835 lines
59 KiB
Python
Executable file
1835 lines
59 KiB
Python
Executable file
#!/usr/bin/env 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(
|
|
"--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(
|
|
"-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).decode()
|
|
if all(c in string.printable for c in s):
|
|
return s
|
|
except (TypeError, binascii.Error, UnicodeDecodeError):
|
|
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 = self.str.split(";")
|
|
for key_value_pair in key_value_pairs:
|
|
if len(key_value_pair):
|
|
kvp.append(key_value_pair.split(":", 1))
|
|
return kvp
|
|
|
|
def split(self, ch):
|
|
return self.str.split(ch)
|
|
|
|
def split_hex(self, ch, byte_order):
|
|
hex_values = list()
|
|
strings = self.str.split(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 = cmd_args.split(":")
|
|
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 args[1:].split(";"):
|
|
(short_action, thread) = thread_action.split(":", 1)
|
|
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 = rsp.split(";")
|
|
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) = args.split(";")
|
|
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))
|
|
if not rsp:
|
|
return
|
|
try:
|
|
json_tree = json.loads(rsp)
|
|
print(json.dumps(json_tree, indent=4, separators=(",", ": ")))
|
|
except json.JSONDecodeError:
|
|
return
|
|
|
|
|
|
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 and 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(
|
|
"-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)
|
|
|
|
|
|
def __lldb_init_module(debugger, internal_dict):
|
|
# This initializer is being run from LLDB in the embedded command interpreter
|
|
# Add any commands contained in this module to LLDB
|
|
debugger.HandleCommand(
|
|
"command script add -o -f gdbremote.start_gdb_log start_gdb_log"
|
|
)
|
|
debugger.HandleCommand(
|
|
"command script add -o -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'
|
|
)
|