forked from OSchip/llvm-project
1632 lines
57 KiB
Python
Executable File
1632 lines
57 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.
|
|
#----------------------------------------------------------------------
|
|
|
|
from __future__ import print_function
|
|
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).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 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 = 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) = 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')
|