X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/3903760236c30e3b5ace7a4eefac3a269d68957c..0a7de7458d150b5d4dffc935ba399be265ef0a1a:/tools/lldbmacros/xnu.py diff --git a/tools/lldbmacros/xnu.py b/tools/lldbmacros/xnu.py old mode 100644 new mode 100755 index f93fd476d..688001678 --- a/tools/lldbmacros/xnu.py +++ b/tools/lldbmacros/xnu.py @@ -1,4 +1,4 @@ -import sys, subprocess, os, re, time, getopt, shlex +import sys, subprocess, os, re, time, getopt, shlex, xnudefines import lldb from functools import wraps from ctypes import c_ulonglong as uint64_t @@ -12,15 +12,24 @@ from core.kernelcore import * from utils import * from core.lazytarget import * -MODULE_NAME=__name__ +MODULE_NAME=__name__ """ Kernel Debugging macros for lldb. Please make sure you read the README COMPLETELY BEFORE reading anything below. - It is very critical that you read coding guidelines in Section E in README file. + It is very critical that you read coding guidelines in Section E in README file. """ +COMMON_HELP_STRING = """ + -h Show the help string for the command. + -o The output of this command execution will be saved to file. Parser information or errors will + not be sent to file though. eg /tmp/output.txt + -s The "filter_string" param is parsed to python regex expression and each line of output + will be printed/saved only if it matches the expression. + -v [-v...] Each additional -v will increase the verbosity of the command. + -p Send the output of the command to plugin. Please see README for usage of plugins. +""" # End Utility functions -# Debugging specific utility functions +# Debugging specific utility functions #decorators. Not to be called directly. @@ -85,7 +94,7 @@ def lldb_command(cmd_name, option_string = ''): def _cmd(obj): def _internal_command_function(debugger, command, result, internal_dict): global config, lldb_run_command_state - stream = CommandOutput(result) + stream = CommandOutput(cmd_name, result) # need to avoid printing on stdout if called from lldb_run_command. if 'active' in lldb_run_command_state and lldb_run_command_state['active']: debuglog('Running %s from lldb_run_command' % command) @@ -147,6 +156,7 @@ However, it is recommended that you report the exception to lldb/kernel debuggin if not obj.__doc__ : print "ERROR: Cannot register command({:s}) without documentation".format(cmd_name) return obj + obj.__doc__ += "\n" + COMMON_HELP_STRING command_function.__doc__ = obj.__doc__ global lldb_command_documentation if cmd_name in lldb_command_documentation: @@ -298,7 +308,7 @@ def GetKextSymbolInfo(load_addr): symbol_name = "None" symbol_offset = load_addr kmod_val = kern.globals.kmod - if kern.arch not in ('arm64',): + if not kern.arch.startswith('arm64'): for kval in IterateLinkedList(kmod_val, 'next'): if load_addr >= unsigned(kval.address) and \ load_addr <= (unsigned(kval.address) + unsigned(kval.size)): @@ -450,18 +460,9 @@ def KernelDebugCommandsHelp(cmd_args=None): print " {0: <20s} - {1}".format(cmd , lldb_command_documentation[cmd][1].split("\n")[0].strip()) else: print " {0: <20s} - {1}".format(cmd , "No help string found.") - print """ - Each of the functions listed here accept the following common options. - -h Show the help string for the command. - -o The output of this command execution will be saved to file. Parser information or errors will - not be sent to file though. eg /tmp/output.txt - -s The "filter_string" param is parsed to python regex expression and each line of output - will be printed/saved only if it matches the expression. - -v [-v...] Each additional -v will increase the verbosity of the command. - -p Send the output of the command to plugin. Please see README for usage of plugins. - - Additionally, each command implementation may have more options. "(lldb) help " will show these options. - """ + print 'Each of the functions listed here accept the following common options. ' + print COMMON_HELP_STRING + print 'Additionally, each command implementation may have more options. "(lldb) help " will show these options.' return None @@ -572,65 +573,92 @@ def ShowPanicLog(cmd_args=None, cmd_options={}): -v : increase verbosity -S : parse stackshot data (if panic stackshot available) """ - binary_data_bytes_to_skip = 0 - if hasattr(kern.globals, "kc_panic_data"): - binary_data_bytes_to_skip = unsigned(kern.globals.kc_panic_data.kcd_addr_end) - unsigned(kern.globals.kc_panic_data.kcd_addr_begin) - if binary_data_bytes_to_skip > 0: - binary_data_bytes_to_skip += sizeof("struct kcdata_item") - else: - binary_data_bytes_to_skip = 0 if "-S" in cmd_options: if hasattr(kern.globals, "kc_panic_data"): - kc_data = unsigned(addressof(kern.globals.kc_panic_data)) - ts = int(time.time()) - ss_binfile = "/tmp/panic_%d.bin" % ts - ss_ipsfile = "/tmp/stacks_%d.ips" % ts - print "savekcdata 0x%x -O %s" % (kc_data, ss_binfile) - SaveKCDataToFile(["0x%x" % kc_data], {"-O":ss_binfile}) - self_path = str(__file__) - base_dir_name = self_path[:self_path.rfind("/")] - print "python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile) - (c,so,se) = RunShellCommand("python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile)) - if c == 0: - print "Saved ips stackshot file as %s" % ss_ipsfile + stackshot_saved = False + # TODO: Update logic to handle "in-memory" panic stackshot on Gibraltar platforms + # once we drop support for the on disk one there. + if kern.arch == 'x86_64': + if kern.globals.panic_stackshot_len != 0: + stackshot_saved = True + else: + print "No panic stackshot available" else: - print "Failed to run command: exit code: %d, SO: %s SE: %s" % (c, so, se) + if unsigned(kern.globals.panic_info.eph_panic_flags) & xnudefines.EMBEDDED_PANIC_STACKSHOT_SUCCEEDED_FLAG: + stackshot_saved = True + else: + print "No panic stackshot available" + if stackshot_saved: + kc_data = unsigned(addressof(kern.globals.kc_panic_data)) + ts = int(time.time()) + ss_binfile = "/tmp/panic_%d.bin" % ts + ss_ipsfile = "/tmp/stacks_%d.ips" % ts + print "savekcdata 0x%x -O %s" % (kc_data, ss_binfile) + SaveKCDataToFile(["0x%x" % kc_data], {"-O":ss_binfile}) + self_path = str(__file__) + base_dir_name = self_path[:self_path.rfind("/")] + print "python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile) + (c,so,se) = RunShellCommand("python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile)) + if c == 0: + print "Saved ips stackshot file as %s" % ss_ipsfile + else: + print "Failed to run command: exit code: %d, SO: %s SE: %s" % (c, so, se) else: print "kc_panic_data is unavailable for this kernel config." - panic_buf = kern.globals.debug_buf_addr - panic_buf_start = unsigned(panic_buf) - panic_buf_end = unsigned(kern.globals.debug_buf_ptr) - num_bytes = panic_buf_end - panic_buf_start - if num_bytes == 0 : - return out_str = "" warn_str = "" - num_print_bytes = 0 - in_binary_data_region = False - pos = 0 - while pos < num_bytes: - p_char = str(panic_buf[pos]) + + if kern.arch == 'x86_64': + panic_buf = Cast(kern.globals.panic_info, 'char *') + panic_log_magic = unsigned(kern.globals.panic_info.mph_magic) + panic_log_begin_offset = unsigned(kern.globals.panic_info.mph_panic_log_offset) + panic_log_len = unsigned(kern.globals.panic_info.mph_panic_log_len) + other_log_begin_offset = unsigned(kern.globals.panic_info.mph_other_log_offset) + other_log_len = unsigned(kern.globals.panic_info.mph_other_log_len) + cur_debug_buf_ptr_offset = (unsigned(kern.globals.debug_buf_ptr) - unsigned(kern.globals.panic_info)) + if other_log_begin_offset != 0 and (other_log_len == 0 or other_log_len < (cur_debug_buf_ptr_offset - other_log_begin_offset)): + other_log_len = cur_debug_buf_ptr_offset - other_log_begin_offset + expected_panic_magic = xnudefines.MACOS_PANIC_MAGIC + else: + panic_buf = Cast(kern.globals.panic_info, 'char *') + panic_log_magic = unsigned(kern.globals.panic_info.eph_magic) + panic_log_begin_offset = unsigned(kern.globals.panic_info.eph_panic_log_offset) + panic_log_len = unsigned(kern.globals.panic_info.eph_panic_log_len) + other_log_begin_offset = unsigned(kern.globals.panic_info.eph_other_log_offset) + other_log_len = unsigned(kern.globals.panic_info.eph_other_log_len) + expected_panic_magic = xnudefines.EMBEDDED_PANIC_MAGIC + + if panic_log_begin_offset == 0: + return + + if panic_log_magic != 0 and panic_log_magic != expected_panic_magic: + warn_str += "BAD MAGIC! Found 0x%x expected 0x%x".format(panic_log_magic, + expected_panic_magic) + + if panic_log_begin_offset == 0: + if warn_str: + print "\n %s" % warn_str + return + + panic_log_curindex = 0 + while panic_log_curindex < panic_log_len: + p_char = str(panic_buf[(panic_log_begin_offset + panic_log_curindex)]) out_str += p_char - if p_char == '\n': - if not in_binary_data_region: - num_print_bytes += 1 - print out_str - if (out_str.find("Data: BEGIN>>") >= 0): - in_binary_data_region = True - pos += binary_data_bytes_to_skip - 1 - if (out_str.find("<= 0): - in_binary_data_region = False - out_str = "" - if num_print_bytes > 4096 and config['verbosity'] == vHUMAN: - warn_str = "LLDBMacro Warning: The paniclog is too large. Trimming to 4096 bytes." - warn_str += " If you wish to see entire log please use '-v' argument." - break - pos += 1 + panic_log_curindex += 1 + + if other_log_begin_offset != 0: + other_log_curindex = 0 + while other_log_curindex < other_log_len: + p_char = str(panic_buf[(other_log_begin_offset + other_log_curindex)]) + out_str += p_char + other_log_curindex += 1 + + print out_str if warn_str: - print warn_str + print "\n %s" % warn_str return @@ -765,6 +793,135 @@ def WalkList(cmd_args=[], cmd_options={}): else: print "{0: <#020x}".format(i) +def iotrace_parse_Copt(Copt): + """Parses the -C option argument and returns a list of CPUs + """ + cpusOpt = Copt + cpuList = cpusOpt.split(",") + chosen_cpus = [] + for cpu_num_string in cpuList: + try: + if '-' in cpu_num_string: + parts = cpu_num_string.split('-') + if len(parts) != 2 or not (parts[0].isdigit() and parts[1].isdigit()): + raise ArgumentError("Invalid cpu specification: %s" % cpu_num_string) + firstRange = int(parts[0]) + lastRange = int(parts[1]) + if firstRange >= kern.globals.real_ncpus or lastRange >= kern.globals.real_ncpus: + raise ValueError() + if lastRange < firstRange: + raise ArgumentError("Invalid CPU range specified: `%s'" % cpu_num_string) + for cpu_num in range(firstRange, lastRange + 1): + if cpu_num not in chosen_cpus: + chosen_cpus.append(cpu_num) + else: + chosen_cpu = int(cpu_num_string) + if chosen_cpu < 0 or chosen_cpu >= kern.globals.real_ncpus: + raise ValueError() + if chosen_cpu not in chosen_cpus: + chosen_cpus.append(chosen_cpu) + except ValueError: + raise ArgumentError("Invalid CPU number specified. Valid range is 0..%d" % (kern.globals.real_ncpus - 1)) + + return chosen_cpus + + +@lldb_command('iotrace', 'C:N:S:RB') +def IOTrace_cmd(cmd_args=[], cmd_options={}): + """ Prints the iotrace ring buffers for all CPUs by default. + Arguments: + -B : Print backtraces for each ring entry + -C [,...,] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a + single CPU number or a range separated by a dash (e.g. "0-3")) + -N : Limit output to the first entries (across all chosen CPUs) + -R : Display results in reverse-sorted order (oldest first; default is newest-first) + -S : Sort output by specified iotrace_entry_t field name (instead of by timestamp) + """ + IDX_CPU = 0 + IDX_RINGPOS = 1 + IDX_RINGENTRY = 2 + MAX_IOTRACE_BACKTRACES = 16 + + if kern.arch != "x86_64": + print "Sorry, iotrace is an x86-only command." + return + + if '-S' in cmd_options: + field_arg = cmd_options['-S'] + try: + getattr(kern.globals.iotrace_ring[0][0], field_arg) + sort_key_field_name = field_arg + except AttributeError: + raise ArgumentError("Invalid sort key field name `%s'" % field_arg) + else: + sort_key_field_name = 'start_time_abs' + + if '-C' in cmd_options: + chosen_cpus = iotrace_parse_Copt(cmd_options['-C']) + else: + chosen_cpus = [x for x in range(kern.globals.real_ncpus)] + + try: + limit_output_count = int(cmd_options['-N']) + except ValueError: + raise ArgumentError("Invalid output count `%s'" % cmd_options['-N']); + except KeyError: + limit_output_count = None + + reverse_sort = '-R' in cmd_options + backtraces = '-B' in cmd_options + + # entries will be a list of 3-tuples, each holding the CPU on which the iotrace entry was collected, + # the original ring index, and the iotrace entry. + entries = [] + for x in chosen_cpus: + ring_slice = [(x, y, kern.globals.iotrace_ring[x][y]) for y in range(kern.globals.iotrace_entries_per_cpu)] + entries.extend(ring_slice) + + total_entries = len(entries) + + entries.sort(key=lambda x: getattr(x[IDX_RINGENTRY], sort_key_field_name), reverse=reverse_sort) + + if limit_output_count is not None and limit_output_count > total_entries: + print ("NOTE: Output count `%d' is too large; showing all %d entries" % (limit_output_count, total_entries)); + limit_output_count = total_entries + + if len(chosen_cpus) < kern.globals.real_ncpus: + print "NOTE: Limiting to entries from cpu%s %s" % ("s" if len(chosen_cpus) > 1 else "", str(chosen_cpus)) + + if limit_output_count is not None and limit_output_count < total_entries: + entries_to_display = limit_output_count + print "NOTE: Limiting to the %s" % ("first entry" if entries_to_display == 1 else ("first %d entries" % entries_to_display)) + else: + entries_to_display = total_entries + + print "%-19s %-8s %-10s %-20s SZ %-18s %-17s DATA" % ( + "START TIME", + "DURATION", + "CPU#[RIDX]", + " TYPE", + " VIRT ADDR", + " PHYS ADDR") + + for x in xrange(entries_to_display): + print "%-20u(%6u) %6s[%02d] %-20s %d 0x%016x 0x%016x 0x%x" % ( + entries[x][IDX_RINGENTRY].start_time_abs, + entries[x][IDX_RINGENTRY].duration, + "CPU%d" % entries[x][IDX_CPU], + entries[x][IDX_RINGPOS], + str(entries[x][IDX_RINGENTRY].iotype).split("=")[1].strip(), + entries[x][IDX_RINGENTRY].size, + entries[x][IDX_RINGENTRY].vaddr, + entries[x][IDX_RINGENTRY].paddr, + entries[x][IDX_RINGENTRY].val) + if backtraces: + for btidx in range(MAX_IOTRACE_BACKTRACES): + nextbt = entries[x][IDX_RINGENTRY].backtrace[btidx] + if nextbt == 0: + break + print "\t" + GetSourceInformationForAddress(nextbt) + + from memory import * @@ -774,6 +931,7 @@ from pmap import * from ioreg import * from mbufs import * from net import * +from skywalk import * from kdp import * from userspace import * from pci import * @@ -784,9 +942,15 @@ from atm import * from structanalyze import * from ipcimportancedetail import * from bank import * +from turnstile import * +from kasan import * from kauth import * from waitq import * from usertaskgdbserver import * from ktrace import * from pgtrace import * from xnutriage import * +from kevent import * +from workqueue import * +from ntstat import * +from zonetriage import *