diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2019-01-19 10:06:29 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2019-01-19 10:06:29 +0000 |
commit | 94994d372d014ce4c8758b9605d63fae651bd8aa (patch) | |
tree | 51c0b708bd59f205d6b35cb2a8c24d62f0c33d77 /examples/python | |
parent | 39be7ce23363d12ae3e49aeb1fdb2bfeb892e836 (diff) |
Notes
Diffstat (limited to 'examples/python')
-rwxr-xr-x | examples/python/bsd.py | 105 | ||||
-rwxr-xr-x | examples/python/crashlog.py | 83 | ||||
-rwxr-xr-x | examples/python/gdbremote.py | 190 |
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', |