summaryrefslogtreecommitdiff
path: root/examples/python
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2019-01-19 10:06:29 +0000
committerDimitry Andric <dim@FreeBSD.org>2019-01-19 10:06:29 +0000
commit94994d372d014ce4c8758b9605d63fae651bd8aa (patch)
tree51c0b708bd59f205d6b35cb2a8c24d62f0c33d77 /examples/python
parent39be7ce23363d12ae3e49aeb1fdb2bfeb892e836 (diff)
Notes
Diffstat (limited to 'examples/python')
-rwxr-xr-xexamples/python/bsd.py105
-rwxr-xr-xexamples/python/crashlog.py83
-rwxr-xr-xexamples/python/gdbremote.py190
3 files changed, 272 insertions, 106 deletions
diff --git a/examples/python/bsd.py b/examples/python/bsd.py
index 8218f4ae6323..3e9528c65845 100755
--- a/examples/python/bsd.py
+++ b/examples/python/bsd.py
@@ -1,5 +1,6 @@
#!/usr/bin/python
+import cmd
import optparse
import os
import shlex
@@ -76,6 +77,22 @@ class Object(object):
self.file.seek(saved_pos, 0)
return bytes
+ def save(self, path=None, overwrite=False):
+ '''
+ Save the contents of the object to disk using 'path' argument as
+ the path, or save it to the current working directory using the
+ object name.
+ '''
+
+ if path is None:
+ path = self.name
+ if not overwrite and os.path.exists(path):
+ print('error: outfile "%s" already exists' % (path))
+ return
+ print('Saving "%s" to "%s"...' % (self.name, path))
+ with open(path, 'w') as f:
+ f.write(self.get_bytes())
+
class StringTable(object):
def __init__(self, bytes):
@@ -186,6 +203,67 @@ class Archive(object):
for obj in self.objects:
obj.dump(f=f, flat=flat)
+class Interactive(cmd.Cmd):
+ '''Interactive prompt for exploring contents of BSD archive files, type
+ "help" to see a list of supported commands.'''
+ image_option_parser = None
+
+ def __init__(self, archives):
+ cmd.Cmd.__init__(self)
+ self.use_rawinput = False
+ self.intro = ('Interactive BSD archive prompt, type "help" to see a '
+ 'list of supported commands.')
+ self.archives = archives
+ self.prompt = '% '
+
+ def default(self, line):
+ '''Catch all for unknown command, which will exit the interpreter.'''
+ print("unknown command: %s" % line)
+ return True
+
+ def do_q(self, line):
+ '''Quit command'''
+ return True
+
+ def do_quit(self, line):
+ '''Quit command'''
+ return True
+
+ def do_extract(self, line):
+ args = shlex.split(line)
+ if args:
+ extracted = False
+ for object_name in args:
+ for archive in self.archives:
+ matches = archive.find(object_name)
+ if matches:
+ for object in matches:
+ object.save(overwrite=False)
+ extracted = True
+ if not extracted:
+ print('error: no object matches "%s" in any archives' % (
+ object_name))
+ else:
+ print('error: must specify the name of an object to extract')
+
+ def do_ls(self, line):
+ args = shlex.split(line)
+ if args:
+ for object_name in args:
+ for archive in self.archives:
+ matches = archive.find(object_name)
+ if matches:
+ for object in matches:
+ object.dump(flat=False)
+ else:
+ print('error: no object matches "%s" in "%s"' % (
+ object_name, archive.path))
+ else:
+ for archive in self.archives:
+ archive.dump(flat=True)
+ print('')
+
+
def main():
parser = optparse.OptionParser(
@@ -243,9 +321,24 @@ def main():
'then the extracted object file will be extracted into the '
'current working directory if a file doesn\'t already exist '
'with that name.'))
+ parser.add_option(
+ '-i', '--interactive',
+ action='store_true',
+ dest='interactive',
+ default=False,
+ help=('Enter an interactive shell that allows users to interactively '
+ 'explore contents of .a files.'))
(options, args) = parser.parse_args(sys.argv[1:])
+ if options.interactive:
+ archives = []
+ for path in args:
+ archives.append(Archive(path))
+ interpreter = Interactive(archives)
+ interpreter.cmdloop()
+ return
+
for path in args:
archive = Archive(path)
if options.object_name:
@@ -256,17 +349,7 @@ def main():
if options.extract:
if len(matches) == 1:
dump_all = False
- if options.outfile is None:
- outfile_path = matches[0].name
- else:
- outfile_path = options.outfile
- if os.path.exists(outfile_path):
- print('error: outfile "%s" already exists' % (
- outfile_path))
- else:
- print('Saving file to "%s"...' % (outfile_path))
- with open(outfile_path, 'w') as outfile:
- outfile.write(matches[0].get_bytes())
+ matches[0].save(path=options.outfile, overwrite=False)
else:
print('error: multiple objects match "%s". Specify '
'the modification time using --mtime.' % (
diff --git a/examples/python/crashlog.py b/examples/python/crashlog.py
index 227fce114052..7eb86db7ce09 100755
--- a/examples/python/crashlog.py
+++ b/examples/python/crashlog.py
@@ -94,11 +94,9 @@ class CrashLog(symbolication.Symbolicator):
thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
app_backtrace_regex = re.compile(
'^Application Specific Backtrace ([0-9]+)([^:]*):(.*)')
- frame_regex = re.compile('^([0-9]+)\s+([^ ]+)\s+(0x[0-9a-fA-F]+) +(.*)')
+ frame_regex = re.compile('^([0-9]+)\s+(.+?)\s+(0x[0-9a-fA-F]{7}[0-9a-fA-F]+) +(.*)')
image_regex_uuid = re.compile(
- '(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)')
- image_regex_no_uuid = re.compile(
- '(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)')
+ '(0x[0-9a-fA-F]+)[-\s]+(0x[0-9a-fA-F]+)\s+[+]?(.+?)\s+(\(.+\))?\s?(<([-0-9a-fA-F]+)>)? (.*)')
empty_line_regex = re.compile('^$')
class Thread:
@@ -246,6 +244,25 @@ class CrashLog(symbolication.Symbolicator):
self.identifier = identifier
self.version = version
+ def find_matching_slice(self):
+ dwarfdump_cmd_output = commands.getoutput(
+ 'dwarfdump --uuid "%s"' % self.path)
+ self_uuid = self.get_uuid()
+ for line in dwarfdump_cmd_output.splitlines():
+ match = self.dwarfdump_uuid_regex.search(line)
+ if match:
+ dwarf_uuid_str = match.group(1)
+ dwarf_uuid = uuid.UUID(dwarf_uuid_str)
+ if self_uuid == dwarf_uuid:
+ self.resolved_path = self.path
+ self.arch = match.group(2)
+ return True
+ if not self.resolved_path:
+ self.unavailable = True
+ print("error\n error: unable to locate '%s' with UUID %s"
+ % (self.path, uuid_str))
+ return False
+
def locate_module_and_debug_symbols(self):
# Don't load a module twice...
if self.resolved:
@@ -277,22 +294,25 @@ class CrashLog(symbolication.Symbolicator):
plist['DBGSymbolRichExecutable'])
self.resolved_path = self.path
if not self.resolved_path and os.path.exists(self.path):
- dwarfdump_cmd_output = commands.getoutput(
- 'dwarfdump --uuid "%s"' % self.path)
- self_uuid = self.get_uuid()
- for line in dwarfdump_cmd_output.splitlines():
- match = self.dwarfdump_uuid_regex.search(line)
- if match:
- dwarf_uuid_str = match.group(1)
- dwarf_uuid = uuid.UUID(dwarf_uuid_str)
- if self_uuid == dwarf_uuid:
- self.resolved_path = self.path
- self.arch = match.group(2)
- break
- if not self.resolved_path:
- self.unavailable = True
- print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
+ if not self.find_matching_slice():
return False
+ if not self.resolved_path and not os.path.exists(self.path):
+ try:
+ import subprocess
+ dsym = subprocess.check_output(
+ ["/usr/bin/mdfind",
+ "com_apple_xcode_dsym_uuids == %s"%uuid_str])[:-1]
+ if dsym and os.path.exists(dsym):
+ print('falling back to binary inside "%s"'%dsym)
+ self.symfile = dsym
+ dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF')
+ for filename in os.listdir(dwarf_dir):
+ self.path = os.path.join(dwarf_dir, filename)
+ if not self.find_matching_slice():
+ return False
+ break
+ except:
+ pass
if (self.resolved_path and os.path.exists(self.resolved_path)) or (
self.path and os.path.exists(self.path)):
print 'ok'
@@ -455,25 +475,16 @@ class CrashLog(symbolication.Symbolicator):
elif parse_mode == PARSE_MODE_IMAGES:
image_match = self.image_regex_uuid.search(line)
if image_match:
- image = CrashLog.DarwinImage(int(image_match.group(1), 0),
- int(image_match.group(2), 0),
- image_match.group(3).strip(),
- image_match.group(4).strip(),
- uuid.UUID(image_match.group(5)),
- image_match.group(6))
+ (img_lo, img_hi, img_name, img_version,
+ _, img_uuid, img_path) = image_match.groups()
+ image = CrashLog.DarwinImage(int(img_lo, 0), int(img_hi, 0),
+ img_name.strip(),
+ img_version.strip()
+ if img_version else "",
+ uuid.UUID(img_uuid), img_path)
self.images.append(image)
else:
- image_match = self.image_regex_no_uuid.search(line)
- if image_match:
- image = CrashLog.DarwinImage(int(image_match.group(1), 0),
- int(image_match.group(2), 0),
- image_match.group(3).strip(),
- image_match.group(4).strip(),
- None,
- image_match.group(5))
- self.images.append(image)
- else:
- print "error: image regex failed for: %s" % line
+ print "error: image regex failed for: %s" % line
elif parse_mode == PARSE_MODE_THREGS:
stripped_line = line.strip()
diff --git a/examples/python/gdbremote.py b/examples/python/gdbremote.py
index a6ff3f5978e4..4ca8a1b82e84 100755
--- a/examples/python/gdbremote.py
+++ b/examples/python/gdbremote.py
@@ -685,32 +685,33 @@ def rsp_qXfer(options, cmd, cmd_args, rsp):
if extension == '.xml':
response = Packet(rsp)
xml_string = response.get_hex_ascii_str()
- ch = xml_string[0]
- if ch == 'l':
- xml_string = xml_string[1:]
- xml_root = ET.fromstring(xml_string)
- for reg_element in xml_root.findall("./feature/reg"):
- if not 'value_regnums' in reg_element.attrib:
- reg_info = RegisterInfo([])
- if 'name' in reg_element.attrib:
- reg_info.info[
- 'name'] = reg_element.attrib['name']
- else:
- reg_info.info['name'] = 'unspecified'
- if 'encoding' in reg_element.attrib:
- reg_info.info['encoding'] = reg_element.attrib[
- 'encoding']
- else:
- reg_info.info['encoding'] = 'uint'
- if 'offset' in reg_element.attrib:
- reg_info.info[
- 'offset'] = reg_element.attrib['offset']
- if 'bitsize' in reg_element.attrib:
- reg_info.info[
- 'bitsize'] = reg_element.attrib['bitsize']
- g_register_infos.append(reg_info)
- print 'XML for "%s":' % (data[2])
- ET.dump(xml_root)
+ 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):
@@ -810,6 +811,14 @@ def cmd_s(options, cmd, args):
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)
@@ -861,8 +870,10 @@ def rsp_vCont(options, cmd, cmd_args, rsp):
s += 'step'
elif mode == 'S':
s += 'step with signal'
- else:
- s += 'unrecognized vCont mode: ', mode
+ 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':
@@ -933,7 +944,7 @@ def rsp_qThreadInfo(options, cmd, cmd_args, rsp):
def rsp_hex_big_endian(options, cmd, cmd_args, rsp):
if rsp == '':
print "%s%s is not supported" % (cmd, cmd_args)
- else:
+ else:
packet = Packet(rsp)
uval = packet.get_hex_uint('big')
print '%s: 0x%x' % (cmd, uval)
@@ -1225,6 +1236,7 @@ gdb_remote_commands = {
'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"},
@@ -1283,6 +1295,49 @@ def parse_gdb_log_file(path, 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
@@ -1306,10 +1361,11 @@ def parse_gdb_log(file, options):
base_time = 0.0
last_time = 0.0
- packet_send_time = 0.0
+ min_time = 100000000.0
packet_total_times = {}
- packet_times = []
- packet_count = {}
+ all_packet_times = []
+ packet_times = {}
+ packet_counts = {}
lines = file.read().splitlines()
last_command = None
last_command_args = None
@@ -1412,32 +1468,39 @@ def parse_gdb_log(file, options):
curr_time = float(match.group(2))
if last_time and not is_command:
delta = curr_time - last_time
- packet_times.append(delta)
+ all_packet_times.append(delta)
delta = 0.0
if base_time:
delta = curr_time - last_time
else:
base_time = curr_time
- if is_command:
- packet_send_time = curr_time
- elif line.find('read packet: $') >= 0 and packet_name:
- if packet_name in packet_total_times:
- packet_total_times[packet_name] += delta
- packet_count[packet_name] += 1
- else:
- packet_total_times[packet_name] = delta
- packet_count[packet_name] = 1
- packet_name = None
+ if not 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))
+ print '%s%.6f %+.6f%s' % (match.group(1),
+ curr_time - base_time,
+ delta,
+ match.group(3))
last_time = curr_time
# else:
# print line
- (average, std_dev) = calculate_mean_and_standard_deviation(packet_times)
+ (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(packet_times), average, 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
@@ -1446,19 +1509,21 @@ def parse_gdb_log(file, options):
# print 'value = (%s) %s' % (type(vvv), vvv)
# if type(vvv) == 'float':
total_packet_time += vvv
- for key, vvv in packet_count.items():
+ for key, vvv in packet_counts.items():
total_packet_count += vvv
- print '#---------------------------------------------------'
+ print '#------------------------------------------------------------'
print '# Packet timing summary:'
- print '# Totals: time = %6f, count = %6d' % (total_packet_time, total_packet_count)
- print '#---------------------------------------------------'
- print '# Packet Time (sec) Percent Count '
- print '#------------------------- ---------- ------- ------'
+ 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_count,
- key=packet_count.__getitem__,
+ packet_counts,
+ key=packet_counts.__getitem__,
reverse=True)
else:
res = sorted(
@@ -1471,11 +1536,12 @@ def parse_gdb_log(file, options):
packet_total_time = packet_total_times[item]
packet_percent = (
packet_total_time / total_packet_time) * 100.0
- if packet_percent >= 10.0:
- print " %24s %.6f %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item])
- else:
- print " %24s %.6f %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item])
-
+ 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]"
@@ -1492,6 +1558,12 @@ if __name__ == '__main__':
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',