-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
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 <path/to/filename> 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 <filter_string> 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 <plugin_name> 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.
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)
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:
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)):
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 <path/to/filename> 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 <filter_string> 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 <plugin_name> 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 <command> " 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 <command> " will show these options.'
return None
-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("<<END") >= 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
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 <cpuSpec#>[,...,<cpuSpec#N>] : 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 <count> : Limit output to the first <count> entries (across all chosen CPUs)
+ -R : Display results in reverse-sorted order (oldest first; default is newest-first)
+ -S <sort_key_field_name> : 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 *
from ioreg import *
from mbufs import *
from net import *
+from skywalk import *
from kdp import *
from userspace import *
from pci 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 *