from utils import *
import xnudefines
from process import *
+import macho
+import json
+from ctypes import c_int64
+
+def vm_unpack_pointer(packed, params, type_str = 'void *'):
+ """ Unpack a pointer packed with VM_PACK_POINTER()
+ params:
+ packed - value : The packed pointer value
+ params - value : The packing parameters of type vm_packing_params_t
+ type_str - str : The type to cast the unpacked pointer into
+ returns:
+ The unpacked pointer
+ """
+ if params.vmpp_base_relative:
+ addr = unsigned(packed) << int(params.vmpp_shift)
+ if addr: addr += int(params.vmpp_base)
+ else:
+ bits = int(params.vmpp_bits)
+ shift = int(params.vmpp_shift)
+ addr = c_int64(unsigned(packed) << (64 - bits)).value
+ addr >>= 64 - bits - shift
+ return kern.GetValueFromAddress(addr, type_str)
+
+def IterateZPerCPU(root, element_type):
+ """ Iterates over a percpu variable
+ params:
+ root - value : Value object for per-cpu variable
+ element_type - str : Type of element
+ returns:
+ one slot
+ """
+ pagesize = kern.globals.page_size
+ mangle = 1 << (8 * kern.ptrsize - 1)
+ for i in range(0, kern.globals.zpercpu_early_count):
+ yield kern.GetValueFromAddress((int(root) | mangle) + i * pagesize, element_type)
+
+@lldb_command('showzpcpu', "S")
+def ShowZPerCPU(cmd_args=None, cmd_options={}):
+ """ Routine to show per-cpu zone allocated variables
+
+ Usage: showzpcpu [-S] expression [field]
+ -S : sum the values instead of printing them
+ """
+ if not cmd_args:
+ raise ArgumentError("No arguments passed")
+ pagesize = kern.globals.page_size
+ mangle = 1 << (8 * kern.ptrsize - 1)
+ sbv = pagesize.GetSBValue()
+ v = sbv.CreateValueFromExpression(None, cmd_args[0])
+ e = value(v)
+ acc = 0
+ for i in range(0, kern.globals.zpercpu_early_count):
+ if len(cmd_args) == 1:
+ t = sbv.CreateValueFromExpression(None, '(%s)%d' % (v.GetTypeName(), (int(e) | mangle) + i * pagesize)).Dereference()
+ else:
+ t = sbv.CreateValueFromExpression(None, '((%s)%d)->%s' % (v.GetTypeName(), (int(e) | mangle) + i * pagesize, cmd_args[1]))
+ if "-S" in cmd_options:
+ acc += value(t)
+ else:
+ print value(t)
+
+ if "-S" in cmd_options:
+ print acc
+
+def ZoneName(zone):
+ """ Formats the name for a given zone
+ params:
+ zone - value : A pointer to a zone
+ returns:
+ the formated name for the zone
+ """
+ names = [ "", "default.", "data.", "kext."]
+ return "{:s}{:s}".format(names[int(zone.kalloc_heap)], zone.z_name)
+
+def PrettyPrintDictionary(d):
+ """ Internal function to pretty print a dictionary with string or integer values
+ params: The dictionary to print
+ """
+ for key, value in d.items():
+ key += ":"
+ if isinstance(value, int):
+ print "{:<30s} {: >10d}".format(key, value)
+ else:
+ print "{:<30s} {: >10s}".format(key, value)
# Macro: memstats
-@lldb_command('memstats')
-def Memstats(cmd_args=None):
+@lldb_command('memstats', 'J')
+def Memstats(cmd_args=None, cmd_options={}):
""" Prints out a summary of various memory statistics. In particular vm_page_wire_count should be greater than 2K or you are under memory pressure.
+ usage: memstats -J
+ Output json
"""
+ print_json = False
+ if "-J" in cmd_options:
+ print_json = True
+
+ memstats = {}
try:
- print "memorystatus_level: {: >10d}".format(kern.globals.memorystatus_level)
- except ValueError:
- pass
- try:
- print "memorystatus_available_pages: {: >10d}".format(kern.globals.memorystatus_available_pages)
+ memstats["memorystatus_level"] = int(kern.globals.memorystatus_level)
+ memstats["memorystatus_available_pages"] = int(kern.globals.memorystatus_available_pages)
+ memstats["inuse_ptepages_count"] = int(kern.globals.inuse_ptepages_count)
except ValueError:
pass
- print "vm_page_throttled_count: {: >10d}".format(kern.globals.vm_page_throttled_count)
- print "vm_page_active_count: {: >10d}".format(kern.globals.vm_page_active_count)
- print "vm_page_inactive_count: {: >10d}".format(kern.globals.vm_page_inactive_count)
- print "vm_page_wire_count: {: >10d}".format(kern.globals.vm_page_wire_count)
- print "vm_page_free_count: {: >10d}".format(kern.globals.vm_page_free_count)
- print "vm_page_purgeable_count: {: >10d}".format(kern.globals.vm_page_purgeable_count)
- print "vm_page_inactive_target: {: >10d}".format(kern.globals.vm_page_inactive_target)
- print "vm_page_free_target: {: >10d}".format(kern.globals.vm_page_free_target)
- print "inuse_ptepages_count: {: >10d}".format(kern.globals.inuse_ptepages_count)
- print "vm_page_free_reserved: {: >10d}".format(kern.globals.vm_page_free_reserved)
+ if hasattr(kern.globals, 'compressor_object'):
+ memstats["compressor_page_count"] = int(kern.globals.compressor_object.resident_page_count)
+ memstats["vm_page_throttled_count"] = int(kern.globals.vm_page_throttled_count)
+ memstats["vm_page_active_count"] = int(kern.globals.vm_page_active_count)
+ memstats["vm_page_inactive_count"] = int(kern.globals.vm_page_inactive_count)
+ memstats["vm_page_wire_count"] = int(kern.globals.vm_page_wire_count)
+ memstats["vm_page_free_count"] = int(kern.globals.vm_page_free_count)
+ memstats["vm_page_purgeable_count"] = int(kern.globals.vm_page_purgeable_count)
+ memstats["vm_page_inactive_target"] = int(kern.globals.vm_page_inactive_target)
+ memstats["vm_page_free_target"] = int(kern.globals.vm_page_free_target)
+ memstats["vm_page_free_reserved"] = int(kern.globals.vm_page_free_reserved)
+
+ if print_json:
+ print json.dumps(memstats)
+ else:
+ PrettyPrintDictionary(memstats)
@xnudebug_test('test_memstats')
def TestMemstats(kernel_target, config, lldb_obj, isConnected ):
""" Test the functionality of memstats command
- returns
+ returns
- False on failure
- - True on success
+ - True on success
"""
if not isConnected:
print "Target is not connected. Cannot test memstats"
res = lldb.SBCommandReturnObject()
lldb_obj.debugger.GetCommandInterpreter().HandleCommand("memstats", res)
result = res.GetOutput()
- if result.split(":")[1].strip().find('None') == -1 :
+ if result.split(":")[1].strip().find('None') == -1 :
return True
- else:
+ else:
return False
# EndMacro: memstats
# Macro: showmemorystatus
def CalculateLedgerPeak(phys_footprint_entry):
""" Internal function to calculate ledger peak value for the given phys footprint entry
- params: phys_footprint_entry - value representing struct ledger_entry *
+ params: phys_footprint_entry - value representing struct ledger_entry *
return: value - representing the ledger peak for the given phys footprint entry
"""
now = kern.globals.sched_tick / 20
- ledger_peak = phys_footprint_entry.le_credit - phys_footprint_entry.le_debit
- if (now - phys_footprint_entry._le.le_peaks[0].le_time <= 1) and (phys_footprint_entry._le.le_peaks[0].le_max > ledger_peak):
- ledger_peak = phys_footprint_entry._le.le_peaks[0].le_max
- if (now - phys_footprint_entry._le.le_peaks[1].le_time <= 1) and (phys_footprint_entry._le.le_peaks[1].le_max > ledger_peak):
- ledger_peak = phys_footprint_entry._le.le_peaks[1].le_max
+ ledger_peak = long(phys_footprint_entry.le_credit) - long(phys_footprint_entry.le_debit)
+ if hasattr(phys_footprint_entry._le._le_max, 'le_interval_max') and (long(phys_footprint_entry._le._le_max.le_interval_max) > ledger_peak):
+ ledger_peak = long(phys_footprint_entry._le._le_max.le_interval_max)
return ledger_peak
-@header("{: >8s} {: >22s} {: >22s} {: >11s} {: >11s} {: >12s} {: >10s} {: >13s} {: ^10s} {: >8s} {: <20s}\n".format(
-'pid', 'effective priority', 'requested priority', 'state', 'user_data', 'physical', 'iokit', 'footprint',
-'spike', 'limit', 'command'))
+@header("{: >8s} {: >12s} {: >12s} {: >10s} {: >10s} {: >12s} {: >14s} {: >10s} {: >12s} {: >10s} {: >10s} {: >10s} {: <32s}\n".format(
+'pid', 'effective', 'requested', 'state', 'relaunch', 'user_data', 'physical', 'iokit', 'footprint',
+'recent peak', 'lifemax', 'limit', 'command'))
def GetMemoryStatusNode(proc_val):
""" Internal function to get memorystatus information from the given proc
params: proc - value representing struct proc *
task_iokit_footprint_ledger_entry = task_ledgerp.l_entries[kern.globals.task_ledgers.iokit_mapped]
task_phys_footprint_ledger_entry = task_ledgerp.l_entries[kern.globals.task_ledgers.phys_footprint]
page_size = kern.globals.page_size
-
- phys_mem_footprint = (task_physmem_footprint_ledger_entry.le_credit - task_physmem_footprint_ledger_entry.le_debit) / page_size
- iokit_footprint = (task_iokit_footprint_ledger_entry.le_credit - task_iokit_footprint_ledger_entry.le_debit) / page_size
- phys_footprint = (task_phys_footprint_ledger_entry.le_credit - task_phys_footprint_ledger_entry.le_debit) / page_size
- phys_footprint_limit = task_phys_footprint_ledger_entry.le_limit / page_size
+
+ phys_mem_footprint = (long(task_physmem_footprint_ledger_entry.le_credit) - long(task_physmem_footprint_ledger_entry.le_debit)) / page_size
+ iokit_footprint = (long(task_iokit_footprint_ledger_entry.le_credit) - long(task_iokit_footprint_ledger_entry.le_debit)) / page_size
+ phys_footprint = (long(task_phys_footprint_ledger_entry.le_credit) - long(task_phys_footprint_ledger_entry.le_debit)) / page_size
+ phys_footprint_limit = long(task_phys_footprint_ledger_entry.le_limit) / page_size
ledger_peak = CalculateLedgerPeak(task_phys_footprint_ledger_entry)
phys_footprint_spike = ledger_peak / page_size
+ phys_footprint_lifetime_max = long(task_phys_footprint_ledger_entry._le._le_max.le_lifetime_max) / page_size
- format_string = '{0: >8d} {1: >22d} {2: >22d} {3: #011x} {4: #011x} {5: >12d} {6: >10d} {7: >13d}'
+ format_string = '{0: >8d} {1: >12d} {2: >12d} {3: #011x} {4: >10d} {5: #011x} {6: >12d} {7: >10d} {8: >13d}'
out_str += format_string.format(proc_val.p_pid, proc_val.p_memstat_effectivepriority,
- proc_val.p_memstat_requestedpriority, proc_val.p_memstat_state, proc_val.p_memstat_userdata,
- phys_mem_footprint, iokit_footprint, phys_footprint)
+ proc_val.p_memstat_requestedpriority, proc_val.p_memstat_state, proc_val.p_memstat_relaunch_flags,
+ proc_val.p_memstat_userdata, phys_mem_footprint, iokit_footprint, phys_footprint)
if phys_footprint != phys_footprint_spike:
- out_str += "{: ^12d}".format(phys_footprint_spike)
+ out_str += "{: >12d}".format(phys_footprint_spike)
else:
- out_str += "{: ^12s}".format('-')
- out_str += "{: 8d} {: <20s}\n".format(phys_footprint_limit, proc_val.p_comm)
- return out_str
+ out_str += "{: >12s}".format('-')
+
+ out_str += "{: >10d} ".format(phys_footprint_lifetime_max)
+ out_str += "{: >10d} {: <32s}\n".format(phys_footprint_limit, GetProcName(proc_val))
+ return out_str
@lldb_command('showmemorystatus')
def ShowMemoryStatus(cmd_args=None):
bucket_index = 0
bucket_count = 20
print GetMemoryStatusNode.header
- print "{: >91s} {: >10s} {: >13s} {: ^10s} {: >8s}\n".format("(pages)", "(pages)", "(pages)",
- "(pages)", "(pages)")
+ print "{: >21s} {: >12s} {: >38s} {: >10s} {: >12s} {: >10s} {: >10s}\n".format("priority", "priority", "(pages)", "(pages)", "(pages)",
+ "(pages)", "(pages)", "(pages)")
while bucket_index < bucket_count:
current_bucket = kern.globals.memstat_bucket[bucket_index]
current_list = current_bucket.list
bucket_index += 1
print "\n\n"
Memstats()
-
+
# EndMacro: showmemorystatus
+class ZoneMeta(object):
+ """
+ Helper class that helpers walking metadata
+ """
+
+ @classmethod
+ def _looksForeign(cls, addr):
+ if addr & (kern.globals.page_size - 1):
+ return False
+ try:
+ meta = kern.GetValueFromAddress(addr, "struct zone_page_metadata *")
+ return meta.zm_foreign_cookie[0] == 0x123456789abcdef
+ except:
+ return False
+
+ def __init__(self, addr, isPageIndex = False):
+ global kern
+ pagesize = kern.globals.page_size
+ zone_info = kern.GetGlobalVariable('zone_info')
+
+ self.zone_map_min = unsigned(zone_info.zi_map_range.min_address)
+ self.zone_map_max = unsigned(zone_info.zi_map_range.max_address)
+ self.zone_meta_min = unsigned(zone_info.zi_meta_range.min_address)
+ self.zone_meta_max = unsigned(zone_info.zi_meta_range.max_address)
+
+ addr = unsigned(addr)
+ if isPageIndex:
+ # sign extend
+ addr = value(pagesize.GetSBValue().CreateValueFromExpression(None,
+ '(long)(int)%d * %d' %(addr, pagesize)))
+ addr = unsigned(addr)
+
+ self.address = addr
+
+ if self.zone_meta_min <= addr and addr < self.zone_meta_max:
+ self.kind = 'Metadata'
+ addr -= (addr - self.zone_meta_min) % sizeof('struct zone_page_metadata')
+ self.meta_addr = addr
+ self.meta = kern.GetValueFromAddress(addr, "struct zone_page_metadata *")
+
+ self.page_addr = self.zone_map_min + ((addr - self.zone_meta_min) / sizeof('struct zone_page_metadata') * pagesize)
+ self.first_offset = 0
+ elif self.zone_map_min <= addr and addr < self.zone_map_max:
+ addr &= ~(pagesize - 1)
+ page_idx = (addr - self.zone_map_min) / pagesize
+
+ self.kind = 'Element'
+ self.page_addr = addr
+ self.meta_addr = self.zone_meta_min + page_idx * sizeof('struct zone_page_metadata')
+ self.meta = kern.GetValueFromAddress(self.meta_addr, "struct zone_page_metadata *")
+ self.first_offset = 0
+ elif ZoneMeta._looksForeign(addr):
+ self.kind = 'Element (F)'
+ addr &= ~(pagesize - 1)
+ self.page_addr = addr
+ self.meta_addr = addr
+ self.meta = kern.GetValueFromAddress(addr, "struct zone_page_metadata *")
+ self.first_offset = 32 # ZONE_FOREIGN_PAGE_FIRST_OFFSET in zalloc.c
+ else:
+ self.kind = 'Unknown'
+ self.meta = None
+ self.page_addr = 0
+ self.meta_addr = 0
+ self.first_offset = 0
+
+ def isSecondaryPage(self):
+ return self.meta and self.meta.zm_secondary_page
+
+ def getPageCount(self):
+ return self.meta and self.meta.zm_page_count or 0
+
+ def getAllocCount(self):
+ return self.meta and self.meta.zm_alloc_count or 0
+
+ def getReal(self):
+ if self.isSecondaryPage():
+ return ZoneMeta(self.meta - self.meta.zm_page_count)
+
+ return self
+
+ def getFreeList(self):
+ if self.meta and self.meta.zm_freelist_offs != unsigned(0xffff):
+ return kern.GetValueFromAddress(self.page_addr + self.meta.zm_freelist_offs, 'vm_offset_t *')
+ return 0
+
+ def iterateFreeList(self):
+ cur = self.getFreeList()
+ while cur:
+ yield cur
+
+ cur = dereference(cast(cur, 'vm_offset_t *'))
+ cur = unsigned(cur) ^ unsigned(kern.globals.zp_nopoison_cookie)
+ cur = kern.GetValueFromAddress(cur, 'vm_offset_t *')
+
+ def iterateElements(self):
+ if self.meta is None:
+ return
+ esize = self.getZone().z_elem_size
+ offs = self.first_offset
+ end = kern.globals.page_size
+ if not self.meta.zm_percpu:
+ end *= self.meta.zm_page_count
+
+ while offs + esize <= end:
+ yield kern.GetValueFromAddress(self.page_addr + offs, 'void *')
+ offs += esize
+
+ def getZone(self):
+ if self.meta:
+ return kern.globals.zone_array[self.meta.zm_index]
+ return None
+
+@lldb_type_summary(['zone_page_metadata'])
+@header("{:<18s} {:<18s} {:>8s} {:>8s} {:<18s} {:<20s}".format('ZONE_METADATA', 'FREELIST', 'PG_CNT', 'ALLOC_CNT', 'ZONE', 'NAME'))
+def GetZoneMetadataSummary(meta):
+ """ Summarize a zone metadata object
+ params: meta - obj representing zone metadata in the kernel
+ returns: str - summary of the zone metadata
+ """
+
+ if type(meta) != ZoneMeta:
+ meta = ZoneMeta(meta)
+
+ out_str = 'Metadata Description:\n' + GetZoneMetadataSummary.header + '\n'
+ if meta.isSecondaryPage():
+ out_str += "{:#018x} {:#018x} {:8d} {:8d} {:#018x} {:s}\n".format(
+ meta.meta_addr, 0, 0, 0, 0, '(fake multipage meta)')
+ meta = meta.getReal()
+ zinfo = meta.getZone()
+ out_str += "{:#018x} {:#018x} {:8d} {:8d} {:#018x} {:s}".format(
+ meta.meta_addr, meta.getFreeList(), meta.getPageCount(), meta.getAllocCount(),
+ addressof(zinfo), ZoneName(zinfo))
+ return out_str
+
+@header("{:<18s} {:>10s} {:>18s} {:>18s} {:<10s}".format(
+ 'ADDRESS', 'TYPE', 'METADATA', 'PAGE_ADDR', 'OFFSET'))
+def WhatIs(addr):
+ """ Information about kernel pointer
+ """
+ global kern
+
+ meta = ZoneMeta(addr)
+
+ if meta.meta is None:
+ out_str = "Address {:#018x} is outside of any zone map ({:#018x}-{:#018x})\n".format(
+ addr, meta.zone_map_min, meta.zone_map_max)
+ else:
+ if meta.kind[0] == 'E': # element
+ page_offset_str = "{:d}/{:d}K".format(
+ addr - meta.page_addr, kern.globals.page_size / 1024)
+ else:
+ page_offset_str = "-"
+ out_str = WhatIs.header + '\n'
+ out_str += "{meta.address:#018x} {meta.kind:>10s} {meta.meta_addr:#018x} {meta.page_addr:#018x} {:<10s}\n\n".format(
+ page_offset_str, meta=meta)
+ out_str += GetZoneMetadataSummary(meta) + '\n\n'
+
+ print out_str
+
+ if meta.kind[0] == 'E':
+ print "Hexdump:\n"
+
+ meta = meta.getReal()
+ esize = meta.getZone().z_elem_size
+ start = meta.page_addr
+
+ estart = addr - (start - meta.first_offset)
+ estart = start + estart - (estart % esize)
+
+ try:
+ if estart > start:
+ data_array = kern.GetValueFromAddress(estart - 16, "uint8_t *")
+ print_hex_data(data_array[0:16], estart - 16, "")
+ print "------------------------------------------------------------------"
+ except:
+ pass
+
+ try:
+ data_array = kern.GetValueFromAddress(estart, "uint8_t *")
+ print_hex_data(data_array[0:esize], estart, "")
+ except:
+ pass
+
+ try:
+ print "------------------------------------------------------------------"
+ data_array = kern.GetValueFromAddress(estart + esize, "uint8_t *")
+ print_hex_data(data_array[0:16], estart + esize, "")
+ except:
+ pass
+
+@lldb_command('whatis')
+def WhatIsHelper(cmd_args=None):
+ """ Routine to show information about a kernel pointer
+ Usage: whatis <address>
+ """
+ if not cmd_args:
+ raise ArgumentError("No arguments passed")
+ WhatIs(kern.GetValueFromAddress(cmd_args[0], 'void *'))
+
+# Macro: showzcache
+
+@lldb_type_summary(['zone','zone_t'])
+@header("{:<18s} {:>5s} {:>10s} {:>12s} {:>12s} {:>9s} {:>9s} {:>9s} {:>9s} {:>9s} {:<20s}".format(
+'ZONE', 'ELTS', 'D FULL/EMPTY', 'ALLOCS', 'FREES', 'D_SWAP', 'D_FILL', 'D_DRAIN', 'D_GC', 'D_FAIL', 'NAME'))
+
+def GetZoneCacheSummary(zone, O):
+ """ Summarize a zone's cache with important information.
+ params:
+ zone: value - obj representing a zone in kernel
+ returns:
+ str - summary of the zone's cache contents
+ """
+ format_string = '{:#018x} {:>5d} {:>4d} / {:>4d} {:>12,d} {:>12,d} {:>9,d} {:>9,d} {:>9,d} {:>9,d} {:>9,d} {:<20s}'
+ mag_capacity = kern.GetGlobalVariable('magazine_element_count')
+ depot_capacity = kern.GetGlobalVariable('depot_element_count')
+
+ cache_elem_count = 0
+ allocs = 0
+ frees = 0
+
+ if zone.__getattr__('cpu_cache_enabled') :
+ for cache in IterateZPerCPU(zone.zcache.zcc_pcpu, 'struct zcc_per_cpu_cache *'):
+ cache_elem_count += cache.current.zcc_magazine_index
+ cache_elem_count += cache.previous.zcc_magazine_index
+ allocs += cache.zcc_allocs
+ frees += cache.zcc_frees
+
+ depot = zone.zcache.zcc_depot
+ cache_elem_count += depot.zcc_depot_index * mag_capacity
+ print O.format(format_string, zone, cache_elem_count,
+ depot.zcc_depot_index, depot_capacity - depot.zcc_depot_index,
+ allocs, frees, depot.zcc_swap, depot.zcc_fill, depot.zcc_drain,
+ depot.zcc_gc, depot.zcc_fail, ZoneName(zone))
+
+@lldb_command('showzcache', fancy=True)
+def ZcachePrint(cmd_args=None, cmd_options={}, O=None):
+ """ Routine to print a summary listing of all the kernel zones cache contents
+ All columns are printed in decimal
+ """
+ global kern
+ with O.table(GetZoneCacheSummary.header):
+ for zval in kern.zones:
+ if zval.__getattr__('cpu_cache_enabled') :
+ GetZoneCacheSummary(zval, O)
+
+# EndMacro: showzcache
+
+# Macro: showzcachecpu
+
+@lldb_type_summary(['zone','zone_t'])
+@header("{:18s} {:32s} {:<10s} {:<10s}".format(
+'ZONE', 'NAME', 'CACHE_ELTS', 'CPU_INFO'))
+
+def GetZoneCacheCPUSummary(zone, O):
+ """ Summarize a zone's cache broken up per cpu
+ params:
+ zone: value - obj representing a zone in kernel
+ returns:
+ str - summary of the zone's per CPU cache contents
+ """
+ format_string = '{:#018x} {:32s} {:10d} {cpuinfo:s}'
+ cache_elem_count = 0
+ cpu_info = ""
+ per_cpu_count = 0
+ mag_capacity = kern.GetGlobalVariable('magazine_element_count')
+ depot_capacity = kern.GetGlobalVariable('depot_element_count')
+
+ if zone.__getattr__('cpu_cache_enabled') :
+ i = 0
+ for cache in IterateZPerCPU(zone.zcache.zcc_pcpu, 'struct zcc_per_cpu_cache *'):
+ if i is not 0:
+ cpu_info += ", "
+ per_cpu_count = cache.current.zcc_magazine_index
+ per_cpu_count += cache.previous.zcc_magazine_index
+ cache_elem_count += per_cpu_count
+ cpu_info += "CPU {:d}: {:5}".format(i,per_cpu_count)
+ i += 1
+ cache_elem_count += zone.zcache.zcc_depot.zcc_depot_index * mag_capacity
+
+ print O.format(format_string, zone, ZoneName(zone), cache_elem_count,cpuinfo = cpu_info)
+
+@lldb_command('showzcachecpu', fancy=True)
+def ZcacheCPUPrint(cmd_args=None, cmd_options={}, O=None):
+ """ Routine to print a summary listing of all the kernel zones cache contents
+ All columns are printed in decimal
+ """
+ global kern
+ with O.table(GetZoneCacheCPUSummary.header):
+ for zval in kern.zones:
+ if zval.__getattr__('cpu_cache_enabled'):
+ GetZoneCacheCPUSummary(zval, O)
+
+# EndMacro: showzcachecpu
+
# Macro: zprint
+def GetZone(zone_val, marks):
+ """ Internal function which gets a phython dictionary containing important zone information.
+ params:
+ zone_val: value - obj representing a zone in kernel
+ returns:
+ zone - python dictionary with zone stats
+ """
+ pagesize = kern.globals.page_size
+ zone = {}
+ zone["free_size"] = zone_val.countfree * zone_val.pcpu_elem_size
+ mag_capacity = kern.GetGlobalVariable('magazine_element_count')
+ zone["page_count"] = unsigned(zone_val.page_count)
+ zone["allfree_page_count"] = unsigned(zone_val.allfree_page_count)
+
+ zone["size"] = zone_val.page_count * pagesize
+ zone["used_size"] = zone["size"] - zone["free_size"]
+ zone["element_count"] = zone_val.countavail - zone_val.countfree
+
+ if zone_val.percpu:
+ zone["allocation_size"] = unsigned(pagesize)
+ zone["allocation_ncpu"] = unsigned(zone_val.alloc_pages)
+ else:
+ zone["allocation_size"] = unsigned(zone_val.alloc_pages * pagesize)
+ zone["allocation_ncpu"] = 1
+ zone["allocation_count"] = zone["allocation_size"] / zone_val.z_elem_size
+ zone["allocation_waste"] = (zone["allocation_size"] % zone_val.z_elem_size) * zone["allocation_ncpu"]
+
+ if not zone_val.__getattr__("z_self") :
+ zone["destroyed"] = True
+ else:
+ zone["destroyed"] = False
+
+ for mark in marks:
+ if zone_val.__getattr__(mark[0]):
+ zone[mark[0]] = True
+ else:
+ zone[mark[0]] = False
+
+ cache_elem_count = 0
+ if zone_val.__getattr__('cpu_cache_enabled') :
+ for cache in IterateZPerCPU(zone_val.zcache.zcc_pcpu, 'struct zcc_per_cpu_cache *'):
+ cache_elem_count += cache.current.zcc_magazine_index
+ cache_elem_count += cache.previous.zcc_magazine_index
+ cache_elem_count += zone_val.zcache.zcc_depot.zcc_depot_index * mag_capacity
+ zone["cache_element_count"] = cache_elem_count
+ zone["name"] = ZoneName(zone_val)
+ if zone_val.exhaustible:
+ zone["exhaustible"] = True
+ else:
+ zone["exhaustible"] = False
+
+ zone["sequester_page_count"] = unsigned(zone_val.sequester_page_count)
+ zone["page_count_max"] = unsigned(zone_val.page_count_max)
+
+ return zone
+
+
@lldb_type_summary(['zone','zone_t'])
-@header("{:^18s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s}({:>6s} {:>6s} {:>6s}) {:^15s} {:<20s}".format(
-'ZONE', 'TOT_SZ', 'PAGE_COUNT', 'ALLOC_ELTS', 'FREE_ELTS', 'FREE_SZ', 'ELT_SZ', 'ALLOC', 'ELTS', 'PGS', 'WASTE', 'FLAGS', 'NAME'))
-def GetZoneSummary(zone):
+@header(("{:<18s} {:_^35s} {:_^24s} {:_^13s} {:_^28s}\n"+
+"{:<18s} {:>11s} {:>11s} {:>11s} {:>8s} {:>7s} {:>7s} {:>6s} {:>6s} {:>8s} {:>6s} {:>5s} {:>7s} {:<18s} {:<20s}").format(
+'', 'SIZE (bytes)', 'ELEMENTS (#)', 'PAGES', 'ALLOC CHUNK CONFIG',
+'ZONE', 'TOTAL', 'ALLOC', 'FREE', 'ALLOC', 'FREE', 'CACHE', 'COUNT', 'FREE', 'SIZE (P)', 'ELTS', 'WASTE', 'ELT_SZ', 'FLAGS', 'NAME'))
+def GetZoneSummary(zone_val, marks, stats):
""" Summarize a zone with important information. See help zprint for description of each field
- params:
- zone: value - obj representing a zone in kernel
- returns:
+ params:
+ zone_val: value - obj representing a zone in kernel
+ returns:
str - summary of the zone
"""
- out_string = ""
- format_string = '{:#018x} {:10d} {:10d} {:10d} {:10d} {:10d} {:10d} {:10d} {:6d} {:6d} {:6d} {markings} {name:s} '
pagesize = kern.globals.page_size
-
- free_elements = zone.countfree
- free_size = free_elements * zone.elem_size
-
- alloc_pages = zone.alloc_size / pagesize
- if zone.use_page_list :
- metadata_size = sizeof('struct zone_page_metadata')
- metadata_offset = metadata_size
- if ((metadata_size % zone.elem_size) != 0) :
- metadata_offset += zone.elem_size - (metadata_size % zone.elem_size)
- alloc_count = ((pagesize - metadata_offset) / zone.elem_size) * alloc_pages
- alloc_waste = metadata_offset * alloc_pages
- else :
- alloc_count = zone.alloc_size / zone.elem_size
- alloc_waste = zone.alloc_size % zone.elem_size
+ out_string = ""
+ zone = GetZone(zone_val, marks)
+
+ format_string = '{zone:#018x} {cur_size:11,d} {used_size:11,d} {free_size:11,d} '
+ format_string += '{count_elts:8,d} {zone.countfree:7,d} {cache_elem_count:7,d} '
+ format_string += '{zone.page_count:6,d} {zone.allfree_page_count:6,d} '
+ format_string += '{alloc_size_kb:3,d}K ({zone.alloc_pages:d}) {alloc_count:6,d} {alloc_waste:5,d} {zone.pcpu_elem_size:7,d} '
+ format_string += '{markings:<18s} {zone_name:<20s}'
- marks = [
- ["collectable", "C"],
- ["expandable", "X"],
- ["noencrypt", "$"],
- ["caller_acct", "@"],
- ["exhaustible", "H"],
- ["allows_foreign", "F"],
- ["async_prio_refill", "R"],
- ["no_callout", "O"],
- ["zleak_on", "L"],
- ["doing_alloc_without_vm_priv", "A"],
- ["doing_alloc_with_vm_priv", "S"],
- ["waiting", "W"],
- ["doing_gc", "G"],
- ["use_page_list", "P"]
- ]
- if kern.arch == 'x86_64':
- marks.append(["gzalloc_exempt", "M"])
- marks.append(["alignment_required", "N"])
-
markings=""
+ if zone["destroyed"]:
+ markings+="I"
+
for mark in marks:
- if zone.__getattr__(mark[0]) :
- markings+=mark[1]
+ if zone[mark[0]]:
+ markings += mark[1]
else:
markings+=" "
- out_string += format_string.format(zone, zone.cur_size, zone.page_count,
- zone.count, free_elements, free_size,
- zone.elem_size, zone.alloc_size, alloc_count,
- alloc_pages, alloc_waste, name = zone.zone_name, markings=markings)
-
- if zone.exhaustible :
- out_string += "(max: {:d})".format(zone.max_size)
-
+
+ alloc_size_kb = zone["allocation_size"] / 1024
+ out_string += format_string.format(zone=zone_val, free_size=zone["free_size"], used_size=zone["used_size"],
+ cur_size=zone["size"], count_elts=zone["element_count"], cache_elem_count=zone["cache_element_count"],
+ alloc_count=zone["allocation_count"], alloc_size_kb=alloc_size_kb, alloc_waste=zone["allocation_waste"],
+ markings=markings, zone_name=zone["name"])
+
+ if zone["exhaustible"] :
+ out_string += " (max: {:d})".format(zone["page_count_max"] * pagesize)
+
+ if zone["sequester_page_count"] != 0 :
+ out_string += " (sequester: {:d})".format(zone["sequester_page_count"])
+
+ stats["cur_size"] += zone["size"]
+ stats["used_size"] += zone["used_size"]
+ stats["free_size"] += zone["free_size"]
+ stats["cur_pages"] += zone["page_count"]
+ stats["free_pages"] += zone["allfree_page_count"]
+ stats["seq_pages"] += zone["sequester_page_count"]
+
return out_string
-@lldb_command('zprint')
-def Zprint(cmd_args=None):
+@lldb_command('zprint', "J", fancy=True)
+def Zprint(cmd_args=None, cmd_options={}, O=None):
""" Routine to print a summary listing of all the kernel zones
+ usage: zprint -J
+ Output json
All columns are printed in decimal
Legend:
C - collectable
+ D - destructible
X - expandable
$ - not encrypted during hibernation
- @ - allocs and frees are accounted to caller process for KPRVT
H - exhaustible
- F - allows foreign memory (memory not allocated from zone_map)
+ F - allows foreign memory (memory not allocated from any zone map)
M - gzalloc will avoid monitoring this zone
R - will be refilled when below low water mark
O - does not allow refill callout to fill zone on noblock allocation
A - currently trying to allocate more backing memory from kernel_memory_allocate without VM priv
S - currently trying to allocate more backing memory from kernel_memory_allocate with VM priv
W - another thread is waiting for more memory
+ E - Per-cpu caching is enabled for this zone
L - zone is being monitored by zleaks
G - currently running GC
- P - uses zone_page_metadata
+ I - zone was destroyed and is no longer valid
"""
global kern
- print GetZoneSummary.header
- for zval in kern.zones:
- print GetZoneSummary(zval)
+
+ marks = [
+ ["collectable", "C"],
+ ["destructible", "D"],
+ ["expandable", "X"],
+ ["noencrypt", "$"],
+ ["exhaustible", "H"],
+ ["allows_foreign", "F"],
+ ["prio_refill_count", "R"],
+ ["no_callout", "O"],
+ ["zleak_on", "L"],
+ ["expanding_no_vm_priv", "A"],
+ ["expanding_vm_priv", "S"],
+ ["waiting", "W"],
+ ["cpu_cache_enabled", "E"],
+ ["gzalloc_exempt", "M"],
+ ["alignment_required", "N"],
+ ["va_sequester", "!"]
+ ]
+ stats = {
+ "cur_size": 0, "used_size": 0, "free_size": 0,
+ "cur_pages": 0, "free_pages": 0, "seq_pages": 0
+ }
+
+ print_json = False
+ if "-J" in cmd_options:
+ print_json = True
+
+ if print_json:
+ zones = []
+ for zval in kern.zones:
+ if zval.z_self:
+ zones.append(GetZone(zval, marks))
+
+ print json.dumps(zones)
+ else:
+ with O.table(GetZoneSummary.header):
+ for zval in kern.zones:
+ if zval.z_self:
+ print GetZoneSummary(zval, marks, stats)
+
+ format_string = '{VT.Bold}{name:19s} {stats[cur_size]:11,d} {stats[used_size]:11,d} {stats[free_size]:11,d} '
+ format_string += ' '
+ format_string += '{stats[cur_pages]:6,d} {stats[free_pages]:6,d}{VT.EndBold} '
+ format_string += '(sequester: {VT.Bold}{stats[seq_pages]:,d}{VT.EndBold})'
+ print O.format(format_string, name="TOTALS", filler="", stats=stats)
+
@xnudebug_test('test_zprint')
def TestZprint(kernel_target, config, lldb_obj, isConnected ):
""" Test the functionality of zprint command
- returns
+ returns
- False on failure
- - True on success
+ - True on success
"""
if not isConnected:
print "Target is not connected. Cannot test memstats"
result = res.GetOutput()
if len(result.split("\n")) > 2:
return True
- else:
+ else:
return False
returns:
None
"""
-
+
scaled_factor = (unsigned(kern.globals.zp_factor) +
- (unsigned(zone.elem_size) >> unsigned(kern.globals.zp_scale)))
+ (unsigned(zone.z_elem_size) >> unsigned(kern.globals.zp_scale)))
out_str = ""
out_str += "{0: <9s} {1: <12s} {2: <18s} {3: <18s} {4: <6s}\n".format('ELEM_SIZE', 'COUNT', 'NCOOKIE', 'PCOOKIE', 'FACTOR')
out_str += "{0: <9d} {1: <12d} 0x{2:0>16x} 0x{3:0>16x} {4: <2d}/{5: <2d}\n\n".format(
- zone.elem_size, zone.count, kern.globals.zp_nopoison_cookie, kern.globals.zp_poisoned_cookie, zone.zp_count, scaled_factor)
+ zone.z_elem_size, zone.countavail - zone.countfree, kern.globals.zp_nopoison_cookie, kern.globals.zp_poisoned_cookie, zone.zp_count, scaled_factor)
out_str += "{0: <7s} {1: <18s} {2: <18s} {3: <18s} {4: <18s} {5: <18s} {6: <14s}\n".format(
'NUM', 'ELEM', 'NEXT', 'BACKUP', '^ NCOOKIE', '^ PCOOKIE', 'POISON (PREV)')
print out_str
while ShowZfreeList.elts_found < zlimit:
ShowZfreeList.elts_found += 1
znext = dereference(Cast(current, 'vm_offset_t *'))
- backup_ptr = kern.GetValueFromAddress((unsigned(Cast(current, 'vm_offset_t')) + unsigned(zone.elem_size) - sizeof('vm_offset_t')), 'vm_offset_t *')
+ znext = (unsigned(znext) ^ unsigned(kern.globals.zp_nopoison_cookie))
+ znext = kern.GetValueFromAddress(znext, 'vm_offset_t *')
+ backup_ptr = kern.GetValueFromAddress((unsigned(Cast(current, 'vm_offset_t')) + unsigned(zone.z_elem_size) - sizeof('vm_offset_t')), 'vm_offset_t *')
backup_val = dereference(backup_ptr)
n_unobfuscated = (unsigned(backup_val) ^ unsigned(kern.globals.zp_nopoison_cookie))
p_unobfuscated = (unsigned(backup_val) ^ unsigned(kern.globals.zp_poisoned_cookie))
if n_unobfuscated != unsigned(znext):
poison_str = "INVALID"
print "{0: <7d} 0x{1:0>16x} 0x{2:0>16x} 0x{3:0>16x} 0x{4:0>16x} 0x{5:0>16x} {6: <14s}\n".format(
- ShowZfreeList.elts_found, unsigned(current), unsigned(znext), unsigned(backup_val), n_unobfuscated, p_unobfuscated, poison_str)
+ ShowZfreeList.elts_found, unsigned(current), unsigned(znext),
+ unsigned(backup_val), n_unobfuscated, p_unobfuscated, poison_str)
if unsigned(znext) == 0:
break
current = Cast(znext, 'void *')
+def ZoneIteratePageQueue(page):
+ while page.packed_address:
+ meta = ZoneMeta(page.packed_address, isPageIndex=True)
+ yield meta
+ page = meta.meta.zm_page_next
+
@static_var('elts_found',0)
@static_var('last_poisoned',0)
@lldb_command('showzfreelist')
zlimit = ArgumentStringToInt(cmd_args[1])
ShowZfreeListHeader(zone)
- if unsigned(zone.use_page_list) == 1:
- if unsigned(zone.allows_foreign) == 1:
- for free_page_meta in IterateQueue(zone.pages.any_free_foreign, 'struct zone_page_metadata *', 'pages'):
- if ShowZfreeList.elts_found == zlimit:
- break
- zfirst = Cast(free_page_meta.elements, 'void *')
- if unsigned(zfirst) != 0:
- ShowZfreeListChain(zone, zfirst, zlimit)
- for free_page_meta in IterateQueue(zone.pages.intermediate, 'struct zone_page_metadata *', 'pages'):
+ for head in [zone.pages_any_free_foreign, zone.pages_intermediate, zone.pages_all_free]:
+ for free_page_meta in ZoneIteratePageQueue(head):
if ShowZfreeList.elts_found == zlimit:
break
- zfirst = Cast(free_page_meta.elements, 'void *')
- if unsigned(zfirst) != 0:
+ zfirst = free_page_meta.getFreeList()
+ if zfirst != 0:
ShowZfreeListChain(zone, zfirst, zlimit)
- for free_page_meta in IterateQueue(zone.pages.all_free, 'struct zone_page_metadata *', 'pages'):
- if ShowZfreeList.elts_found == zlimit:
- break
- zfirst = Cast(free_page_meta.elements, 'void *')
- if unsigned(zfirst) != 0:
- ShowZfreeListChain(zone, zfirst, zlimit)
- else:
- zfirst = Cast(zone.free_elements, 'void *')
- if unsigned(zfirst) != 0:
- ShowZfreeListChain(zone, zfirst, zlimit)
-
+
if ShowZfreeList.elts_found == zlimit:
print "Stopped at {0: <d} elements!".format(zlimit)
else:
# EndMacro: showzfreelist
+# Macro: zstack_showzonesbeinglogged
+
+@lldb_command('zstack_showzonesbeinglogged')
+def ZstackShowZonesBeingLogged(cmd_args=None):
+ """ Show all zones which have BTLog enabled.
+ """
+ global kern
+ for zval in kern.zones:
+ if zval.zlog_btlog:
+ print "Zone: %s with its BTLog at: 0x%lx" % (ZoneName(zval), zval.zlog_btlog)
+
+# EndMacro: zstack_showzonesbeinglogged
+
# Macro: zstack
@lldb_command('zstack')
def Zstack(cmd_args=None):
- """ Zone leak debugging: Print the stack trace of log element at <index>. If a <count> is supplied, it prints <count> log elements starting at <index>.
- Usage: zstack <index> [<count>]
+ """ Zone leak debugging: Print the stack trace logged at <index> in the stacks list. If a <count> is supplied, it prints <count> stacks starting at <index>.
+ Usage: zstack <btlog addr> <index> [<count>]
- The suggested usage is to look at indexes below zcurrent and look for common stack traces.
- The stack trace that occurs the most is probably the cause of the leak. Find the pc of the
- function calling into zalloc and use the countpcs command to find out how often that pc occurs in the log.
- The pc occuring in a high percentage of records is most likely the source of the leak.
-
- The findoldest command is also useful for leak debugging since it identifies the oldest record
- in the log, which may indicate the leaker.
+ The suggested usage is to look at stacks with high percentage of refs (maybe > 25%).
+ The stack trace that occurs the most is probably the cause of the leak. Use zstack_findleak for that.
"""
if not cmd_args:
print Zstack.__doc__
if int(kern.globals.log_records) == 0:
print "Zone logging not enabled. Add 'zlog=<zone name>' to boot-args."
return
- if int(kern.globals.zlog_btlog) == 0:
- print "Zone logging enabled, but zone has not been initialized yet."
- return
+ btlog_ptr = kern.GetValueFromAddress(cmd_args[0], 'btlog_t *')
+ btrecords_total_size = unsigned(btlog_ptr.btlog_buffersize)
+ btrecord_size = unsigned(btlog_ptr.btrecord_size)
+ btrecords = unsigned(btlog_ptr.btrecords)
+ btlog_size = unsigned(sizeof('struct btlog'))
+ depth = unsigned(btlog_ptr.btrecord_btdepth)
+ zstack_index = ArgumentStringToInt(cmd_args[1])
count = 1
- if len(cmd_args) >= 2:
- count = ArgumentStringToInt(cmd_args[1])
- zstack_index = unsigned(cmd_args[0])
+ if len(cmd_args) >= 3:
+ count = ArgumentStringToInt(cmd_args[2])
+
+ max_count = ((btrecords_total_size - btlog_size)/btrecord_size)
+
+ if (zstack_index + count) > max_count:
+ count = max_count - zstack_index
+
while count and (zstack_index != 0xffffff):
- zstack_record_offset = zstack_index * unsigned(kern.globals.zlog_btlog.btrecord_size)
- zstack_record = kern.GetValueFromAddress(unsigned(kern.globals.zlog_btlog.btrecords) + zstack_record_offset, 'btlog_record_t *')
- ShowZStackRecord(zstack_record, zstack_index)
- zstack_index = zstack_record.next
+ zstack_record_offset = zstack_index * btrecord_size
+ zstack_record = kern.GetValueFromAddress(btrecords + zstack_record_offset, 'btlog_record_t *')
+ if int(zstack_record.ref_count)!=0:
+ ShowZStackRecord(zstack_record, zstack_index, depth, unsigned(btlog_ptr.active_element_count))
+ zstack_index += 1
count -= 1
# EndMacro : zstack
-# Macro: findoldest
+# Macro: zstack_inorder
-@lldb_command('findoldest')
-def FindOldest(cmd_args=None):
- """ Zone leak debugging: find and print the oldest record in the log.
-
- Once it prints a stack trace, find the pc of the caller above all the zalloc, kalloc and
- IOKit layers. Then use the countpcs command to see how often this caller has allocated
- memory. A caller with a high percentage of records in the log is probably the leaker.
+@lldb_command('zstack_inorder')
+def ZstackInOrder(cmd_args=None):
+ """ Zone leak debugging: Print the stack traces starting from head to the tail.
+ Usage: zstack_inorder <btlog addr>
"""
- if int(kern.globals.log_records) == 0:
- print FindOldest.__doc__
+ if not cmd_args:
+ print "Zone leak debugging: Print the stack traces starting from head to the tail. \nUsage: zstack_inorder <btlog addr>"
return
- if int(kern.globals.zlog_btlog) == 0:
- print "Zone logging enabled, but zone has not been initialized yet."
+ if int(kern.globals.log_records) == 0:
+ print "Zone logging not enabled. Add 'zlog=<zone name>' to boot-args."
return
- index = kern.globals.zlog_btlog.head
- if unsigned(index) != 0xffffff:
- print "Oldest record is at log index: {0: <d}".format(index)
- Zstack([index])
- else:
- print "No Records Present"
+ btlog_ptr = kern.GetValueFromAddress(cmd_args[0], 'btlog_t *')
+ btrecords_total_size = unsigned(btlog_ptr.btlog_buffersize)
+ btrecord_size = unsigned(btlog_ptr.btrecord_size)
+ btrecords = unsigned(btlog_ptr.btrecords)
+ btlog_size = unsigned(sizeof('struct btlog'))
+ depth = unsigned(btlog_ptr.btrecord_btdepth)
+ zstack_head = unsigned(btlog_ptr.head)
+ zstack_index = zstack_head
+ zstack_tail = unsigned(btlog_ptr.tail)
+ count = ((btrecords_total_size - btlog_size)/btrecord_size)
+
+ while count and (zstack_index != 0xffffff):
+ zstack_record_offset = zstack_index * btrecord_size
+ zstack_record = kern.GetValueFromAddress(btrecords + zstack_record_offset, 'btlog_record_t *')
+ ShowZStackRecord(zstack_record, zstack_index, depth, unsigned(btlog_ptr.active_element_count))
+ zstack_index = zstack_record.next
+ count -= 1
+
+# EndMacro : zstack_inorder
+
+# Macro: findoldest
+
+@lldb_command('findoldest')
+def FindOldest(cmd_args=None):
+ """
+ """
+ print "***** DEPRECATED ***** use 'zstack_findleak' macro instead."
+ return
# EndMacro : findoldest
-# Macro: countpcs
+# Macro : zstack_findleak
-@lldb_command('countpcs')
-def Countpcs(cmd_args=None):
- """ Zone leak debugging: search the log and print a count of all log entries that contain the given <pc>
+@lldb_command('zstack_findleak')
+def zstack_findleak(cmd_args=None):
+ """ Zone leak debugging: search the log and print the stack with the most active references
in the stack trace.
- Usage: countpcs <pc>
+ Usage: zstack_findleak <btlog address>
- This is useful for verifying a suspected <pc> as being the source of
- the leak. If a high percentage of the log entries contain the given <pc>, then it's most
- likely the source of the leak. Note that this command can take several minutes to run.
+ This is useful for verifying a suspected stack as being the source of
+ the leak.
"""
- if not cmd_args:
- print Countpcs.__doc__
- return
- if int(kern.globals.log_records) == 0:
- print "Zone logging not enabled. Add 'zlog=<zone name>' to boot-args."
- return
- if int(kern.globals.zlog_btlog) == 0:
- print "Zone logging enabled, but zone has not been initialized yet."
- return
-
- cpcs_index = unsigned(kern.globals.zlog_btlog.head)
- target_pc = unsigned(kern.GetValueFromAddress(cmd_args[0], 'void *'))
- found = 0
- depth = unsigned(kern.globals.zlog_btlog.btrecord_btdepth)
+ btlog_ptr = kern.GetValueFromAddress(cmd_args[0], 'btlog_t *')
+ btrecord_size = unsigned(btlog_ptr.btrecord_size)
+ btrecords = unsigned(btlog_ptr.btrecords)
+
+ cpcs_index = unsigned(btlog_ptr.head)
+ depth = unsigned(btlog_ptr.btrecord_btdepth)
+ highref = 0
+ highref_index = 0
+ highref_record = 0
while cpcs_index != 0xffffff:
- cpcs_record_offset = cpcs_index * unsigned(kern.globals.zlog_btlog.btrecord_size)
- cpcs_record = kern.GetValueFromAddress(unsigned(kern.globals.zlog_btlog.btrecords) + cpcs_record_offset, 'btlog_record_t *')
- frame = 0
- while frame < depth:
- frame_pc = unsigned(cpcs_record.bt[frame])
- if frame_pc == target_pc:
- found += 1
- break
- frame += 1
+ cpcs_record_offset = cpcs_index * btrecord_size
+ cpcs_record = kern.GetValueFromAddress(btrecords + cpcs_record_offset, 'btlog_record_t *')
+ if cpcs_record.ref_count > highref:
+ highref_record = cpcs_record
+ highref = cpcs_record.ref_count
+ highref_index = cpcs_index
cpcs_index = cpcs_record.next
- print "Occured {0: <d} times in log ({1: <d}{2: <s} of records)".format(found, (found * 100)/unsigned(kern.globals.zlog_btlog.activecount), '%')
+ ShowZStackRecord(highref_record, highref_index, depth, unsigned(btlog_ptr.active_element_count))
-# EndMacro: countpcs
+# EndMacro: zstack_findleak
# Macro: findelem
@lldb_command('findelem')
def FindElem(cmd_args=None):
- """ Zone corruption debugging: search the log and print out the stack traces for all log entries that
- refer to the given zone element.
- Usage: findelem <elem addr>
+ """
+ """
+ print "***** DEPRECATED ***** use 'zstack_findelem' macro instead."
+ return
+# EndMacro: findelem
+
+@lldb_command('zstack_findelem')
+def ZStackFindElem(cmd_args=None):
+ """ Zone corruption debugging: search the zone log and print out the stack traces for all log entries that
+ refer to the given zone element.
+ Usage: zstack_findelem <btlog addr> <elem addr>
When the kernel panics due to a corrupted zone element, get the
element address and use this command. This will show you the stack traces of all logged zalloc and
double-frees readily apparent.
"""
if not cmd_args:
- print FindElem.__doc__
- return
- if int(kern.globals.log_records) == 0:
- print "Zone logging not enabled. Add 'zlog=<zone name>' to boot-args."
+ print ZStackFindElem.__doc__
return
- if int(kern.globals.zlog_btlog) == 0:
- print "Zone logging enabled, but zone has not been initialized yet."
+ if int(kern.globals.log_records) == 0 or unsigned(kern.globals.corruption_debug_flag) == 0:
+ print "Zone logging with corruption detection not enabled. Add '-zc zlog=<zone name>' to boot-args."
return
-
- target_element = unsigned(kern.GetValueFromAddress(cmd_args[0], 'void *'))
- index = unsigned(kern.globals.zlog_btlog.head)
- prev_op = -1
- while index != 0xffffff:
- findelem_record_offset = index * unsigned(kern.globals.zlog_btlog.btrecord_size)
- findelem_record = kern.GetValueFromAddress(unsigned(kern.globals.zlog_btlog.btrecords) + findelem_record_offset, 'btlog_record_t *')
- if unsigned(findelem_record.element) == target_element:
- Zstack([index])
- if int(findelem_record.operation) == prev_op:
+ btlog_ptr = kern.GetValueFromAddress(cmd_args[0], 'btlog_t *')
+ target_element = unsigned(kern.GetValueFromAddress(cmd_args[1], 'void *'))
+
+ btrecord_size = unsigned(btlog_ptr.btrecord_size)
+ btrecords = unsigned(btlog_ptr.btrecords)
+ depth = unsigned(btlog_ptr.btrecord_btdepth)
+
+ prev_op = -1
+ scan_items = 0
+ hashelem = cast(btlog_ptr.elem_linkage_un.element_hash_queue.tqh_first, 'btlog_element_t *')
+ if (target_element >> 32) != 0:
+ target_element = target_element ^ 0xFFFFFFFFFFFFFFFF
+ else:
+ target_element = target_element ^ 0xFFFFFFFF
+ while hashelem != 0:
+ if unsigned(hashelem.elem) == target_element:
+ recindex = hashelem.recindex
+ recoffset = recindex * btrecord_size
+ record = kern.GetValueFromAddress(btrecords + recoffset, 'btlog_record_t *')
+ out_str = ('-' * 8)
+ if record.operation == 1:
+ out_str += "OP: ALLOC. "
+ else:
+ out_str += "OP: FREE. "
+ out_str += "Stack Index {0: <d} {1: <s}\n".format(recindex, ('-' * 8))
+ print out_str
+ print GetBtlogBacktrace(depth, record)
+ print " \n"
+ if int(record.operation) == prev_op:
print "{0: <s} DOUBLE OP! {1: <s}".format(('*' * 8), ('*' * 8))
- prev_op = int(findelem_record.operation)
- index = findelem_record.next
+ return
+ prev_op = int(record.operation)
+ scan_items = 0
+ hashelem = cast(hashelem.element_hash_link.tqe_next, 'btlog_element_t *')
+ scan_items += 1
+ if scan_items % 100 == 0:
+ print "Scanning is ongoing. {0: <d} items scanned since last check." .format(scan_items)
+
+# EndMacro: zstack_findelem
+
+@lldb_command('zstack_findtop', 'N:')
+def ShowZstackTop(cmd_args=None, cmd_options={}):
+ """ Zone leak debugging: search the log and print the stacks with the most active references
+ in the stack trace.
-# EndMacro: findelem
+ Usage: zstack_findtop [-N <n-stacks>] <btlog-addr>
+ """
+
+ if not cmd_args:
+ raise ArgumentError('Missing required btlog address argument')
+
+ n = 5
+ if '-N' in cmd_options:
+ n = int(cmd_options['-N'])
+
+ btlog_ptr = kern.GetValueFromAddress(cmd_args[0], 'btlog_t *')
+ btrecord_size = unsigned(btlog_ptr.btrecord_size)
+ btrecords = unsigned(btlog_ptr.btrecords)
+
+ cpcs_index = unsigned(btlog_ptr.head)
+ depth = unsigned(btlog_ptr.btrecord_btdepth)
+
+ records = []
+ while cpcs_index != 0xffffff:
+ cpcs_record_offset = cpcs_index * btrecord_size
+ cpcs_record = kern.GetValueFromAddress(btrecords + cpcs_record_offset, 'btlog_record_t *')
+ cpcs_record.index = cpcs_index
+ records.append(cpcs_record)
+ cpcs_index = cpcs_record.next
+
+ recs = sorted(records, key=lambda x: x.ref_count, reverse=True)
+
+ for rec in recs[:n]:
+ ShowZStackRecord(rec, rec.index, depth, unsigned(btlog_ptr.active_element_count))
+
+# EndMacro: zstack_findtop
# Macro: btlog_find
@lldb_command('btlog_find', "AS")
def BtlogFind(cmd_args=None, cmd_options={}):
- """ Search the btlog_t for entries corresponding to the given element.
- Use -A flag to print all entries.
- Use -S flag to summarize the count of records
- Usage: btlog_find <btlog_t> <element>
- Usage: btlog_find <btlog_t> -A
- Note: Backtraces will be in chronological order, with oldest entries aged out in FIFO order as needed.
"""
- if not cmd_args:
- raise ArgumentError("Need a btlog_t parameter")
- btlog = kern.GetValueFromAddress(cmd_args[0], 'btlog_t *')
- printall = False
- summarize = False
- summary_cache = {}
- target_elem = 0xffffffff
-
- if "-A" in cmd_options:
- printall = True
- else:
- if not printall and len(cmd_args) < 2:
- raise ArgumentError("<element> is missing in args. Need a search pointer.")
- target_elem = unsigned(kern.GetValueFromAddress(cmd_args[1], 'void *'))
-
- if "-S" in cmd_options:
- summarize = True
-
- index = unsigned(btlog.head)
- progress = 0
- record_size = unsigned(btlog.btrecord_size)
- try:
- while index != 0xffffff:
- record_offset = index * record_size
- record = kern.GetValueFromAddress(unsigned(btlog.btrecords) + record_offset, 'btlog_record_t *')
- if printall or unsigned(record.element) == target_elem:
- _s = '{0: <s} {2: <#0x} OP {1: <d} {3: <s}'.format(('-' * 8), record.operation, unsigned(record.element), ('-' * 8))
- _s += GetBtlogBacktrace(btlog.btrecord_btdepth, record)
- if summarize:
- if _s not in summary_cache:
- summary_cache[_s] = 1
- else:
- summary_cache[_s] += 1
- else :
- print _s
- index = record.next
- progress += 1
- if (progress % 1000) == 0: print '{0: <d} entries searched!\n'.format(progress)
- except ValueError, e:
- pass
-
- if summarize:
- print "=================== SUMMARY =================="
- for (k,v) in summary_cache.iteritems():
- print "Count: %d %s \n " % (v, k)
+ """
+ print "***** DEPRECATED ***** use 'zstack_findelem' macro instead."
return
#EndMacro: btlog_find
if len(cmd_args) >= 2:
trace_size = ArgumentStringToInt(cmd_args[1])
ShowZstackTraceHelper(trace, trace_size)
-
+
#EndMacro: showzstacktrace
def ShowZstackTraceHelper(stack, depth):
""" Helper routine for printing a zstack.
params:
stack: void *[] - An array of pointers representing the Zstack
- depth: int - The depth of the ztrace stack
+ depth: int - The depth of the ztrace stack
returns:
None
"""
@lldb_command('showtopztrace')
def ShowTopZtrace(cmd_args=None):
- """ Shows the ztrace with the biggest size.
+ """ Shows the ztrace with the biggest size.
(According to top_ztrace, not by iterating through the hash table)
"""
top_trace = kern.globals.top_ztrace
if unsigned(kern.globals.zallocations) == 0:
print "zallocations array not initialized!"
return
- print '{0: <5s} {1: <18s} {2: <5s} {3: <15s}'.format('INDEX','ADDRESS','TRACE','SIZE')
+ print '{0: <5s} {1: <18s} {2: <5s} {3: <15s}'.format('INDEX','ADDRESS','TRACE','SIZE')
current_index = 0
max_zallocation = unsigned(kern.globals.zleak_alloc_buckets)
allocation_count = 0
if not cmd_args:
print ShowZallocsForTrace.__doc__
return
- print '{0: <5s} {1: <18s} {2: <15s}'.format('INDEX','ADDRESS','SIZE')
+ print '{0: <5s} {1: <18s} {2: <15s}'.format('INDEX','ADDRESS','SIZE')
target_index = ArgumentStringToInt(cmd_args[0])
current_index = 0
max_zallocation = unsigned(kern.globals.zleak_alloc_buckets)
#EndMacro: showzstats
+#Macro: showpcpu
+
+@lldb_command('showpcpu', "N:V", fancy=True)
+def ShowPCPU(cmd_args=None, cmd_options={}, O=None):
+ """ Show per-cpu variables
+ usage: showpcpu [-N <cpu>] [-V] <variable name>
+
+ Use -N <cpu> to only dump the value for a given CPU number
+ Use -V to dump the values of the variables after their addresses
+ """
+
+ if not cmd_args:
+ raise ArgumentError("No arguments passed")
+
+ cpu = None
+ ncpu = kern.globals.zpercpu_early_count
+ pcpu_base = kern.globals.percpu_base
+
+ if "-N" in cmd_options:
+ cpu = unsigned(int(cmd_options["-N"]))
+ if cpu >= unsigned(ncpu):
+ raise ArgumentError("Invalid cpu {d}".format(cpu))
+
+ var = addressof(kern.GetGlobalVariable('percpu_slot_' + cmd_args[0]))
+ ty = var.GetSBValue().GetTypeName()
+
+ r = range(0, ncpu)
+ if cpu:
+ r = range(cpu, cpu + 1)
+
+ def PCPUSlot(pcpu_var, i):
+ if i == 0:
+ return pcpu_var
+ addr = unsigned(pcpu_var) + unsigned(pcpu_base.start) + (i - 1) * unsigned(pcpu_base.size)
+ return kern.GetValueFromAddress(addr, pcpu_var)
+
+ with O.table("{:<4s} {:<20s}".format("CPU", "address")):
+ for i in r:
+ print O.format("{:<4d} ({:s}){:#x}", i, ty, PCPUSlot(var, i))
+
+ if not "-V" in cmd_options:
+ return
+
+ for i in r:
+ with O.table("CPU {:d}".format(i)):
+ print dereference(PCPUSlot(var, i))
+
+#EndMacro: showpcpu
+
def GetBtlogBacktrace(depth, zstack_record):
""" Helper routine for getting a BT Log record backtrace stack.
params:
frame = 0
if not zstack_record:
return "Zstack record none!"
-
+
depth_val = unsigned(depth)
while frame < depth_val:
frame_pc = zstack_record.bt[frame]
frame += 1
return out_str
-def ShowZStackRecord(zstack_record, zstack_index):
+def ShowZStackRecord(zstack_record, zstack_index, btrecord_btdepth, elements_count):
""" Helper routine for printing a single zstack record
params:
zstack_record:btlog_record_t * - A BTLog record
"""
out_str = ('-' * 8)
if zstack_record.operation == 1:
- out_str += "ALLOC "
+ out_str += "ALLOC. "
else:
- out_str += "FREE "
- out_str += "{0: <#0x} : Index {1: <d} {2: <s}\n".format(zstack_record.element, zstack_index, ('-' * 8))
+ out_str += "FREE. "
+ out_str += "Stack Index {0: <d} with active refs {1: <d} of {2: <d} {3: <s}\n".format(zstack_index, zstack_record.ref_count, elements_count, ('-' * 8))
print out_str
- print GetBtlogBacktrace(kern.globals.zlog_btlog.btrecord_btdepth, zstack_record)
+ print GetBtlogBacktrace(btrecord_btdepth, zstack_record)
+ print " \n"
# Macro: showioalloc
print "Container allocation = {0: <#0x} = {1: d}K".format(kern.globals.debug_container_malloc_size, (kern.globals.debug_container_malloc_size / 1024))
print "IOMalloc allocation = {0: <#0x} = {1: d}K".format(kern.globals.debug_iomalloc_size, (kern.globals.debug_iomalloc_size / 1024))
print "Container allocation = {0: <#0x} = {1: d}K".format(kern.globals.debug_iomallocpageable_size, (kern.globals.debug_iomallocpageable_size / 1024))
-
-
-# EndMacro: showioalloc
+
+
+# EndMacro: showioalloc
# Macro: showselectmem
print '-'*40
print "Total: {:d} bytes ({:d} kbytes)".format(selmem, selmem/1024)
# Endmacro: showselectmem
-
-
+
+
# Macro: showtaskvme
@lldb_command('showtaskvme', "PS")
def ShowTaskVmeHelper(cmd_args=None, cmd_options={}):
vmstats.compressed_lifetime = 0
vmstats.error = ''
- hdr_format = "{0: >10s} {1: <20s} {2: >6s} {3: >10s} {4: >10s} {5: >10s} {6: >10s} {7: >10s} {8: >10s} {9: >10s} {10: >10s} {11: >10s} {12: >10s} {13: >10s} {14:}"
- print hdr_format.format('pid', 'command', '#ents', 'wired', 'vsize', 'rsize', 'NEW RSIZE', 'max rsize', 'internal', 'external', 'reusable', 'compressed', 'compressed', 'compressed', '')
- print hdr_format.format('', '', '', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(current)', '(peak)', '(lifetime)', '')
- entry_format = "{p.p_pid: >10d} {p.p_comm: <20s} {m.hdr.nentries: >6d} {s.wired_count: >10d} {vsize: >10d} {s.resident_count: >10d} {s.new_resident_count: >10d} {s.resident_max: >10d} {s.internal: >10d} {s.external: >10d} {s.reusable: >10d} {s.compressed: >10d} {s.compressed_peak: >10d} {s.compressed_lifetime: >10d} {s.error}"
+ hdr_format = "{:>6s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:>10s} {:<20s} {:1s}"
+ print hdr_format.format('#ents', 'wired', 'vsize', 'rsize', 'NEW RSIZE', 'max rsize', 'internal', 'external', 'reusable', 'compressed', 'compressed', 'compressed', 'pid', 'command', '')
+ print hdr_format.format('', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(current)', '(peak)', '(lifetime)', '', '', '')
+ entry_format = "{m.hdr.nentries: >6d} {s.wired_count: >10d} {vsize: >10d} {s.resident_count: >10d} {s.new_resident_count: >10d} {s.resident_max: >10d} {s.internal: >10d} {s.external: >10d} {s.reusable: >10d} {s.compressed: >10d} {s.compressed_peak: >10d} {s.compressed_lifetime: >10d} {p.p_pid: >10d} {0: <32s} {s.error}"
for task in kern.tasks:
proc = Cast(task.bsd_info, 'proc *')
if vmstats.new_resident_count +vmstats.reusable != vmstats.resident_count:
vmstats.error += '*'
- print entry_format.format(p=proc, m=vmmap, vsize=(unsigned(vmmap.size) / page_size), t=task, s=vmstats)
-
+ print entry_format.format(GetProcName(proc), p=proc, m=vmmap, vsize=(unsigned(vmmap.size) / page_size), t=task, s=vmstats)
+
def ShowTaskVMEntries(task, show_pager_info, show_all_shadows):
""" Routine to print out a summary listing of all the entries in a vm_map
- params:
+ params:
task - core.value : a object of type 'task *'
returns:
None
usage: showmapvme <vm_map>
"""
if cmd_args == None or len(cmd_args) < 1:
- print "Invalid argument.", ShowMap.__doc__
+ print "Invalid argument.", ShowMapVME.__doc__
return
map_val = kern.GetValueFromAddress(cmd_args[0], 'vm_map_t')
print GetVMMapSummary.header
return None
@lldb_type_summary(['_vm_map *', 'vm_map_t'])
-@header("{0: <20s} {1: <20s} {2: <20s} {3: >5s} {4: >5s} {5: <20s} {6: <20s}".format("vm_map", "pmap", "vm_size", "#ents", "rpage", "hint", "first_free"))
+@header("{0: <20s} {1: <20s} {2: <20s} {3: >5s} {4: >5s} {5: <20s} {6: <20s} {7: <7s}".format("vm_map", "pmap", "vm_size", "#ents", "rpage", "hint", "first_free", "pgshift"))
def GetVMMapSummary(vmmap):
""" Display interesting bits from vm_map struct """
out_string = ""
- format_string = "{0: <#020x} {1: <#020x} {2: <#020x} {3: >5d} {4: >5d} {5: <#020x} {6: <#020x}"
+ format_string = "{0: <#020x} {1: <#020x} {2: <#020x} {3: >5d} {4: >5d} {5: <#020x} {6: <#020x} {7: >7d}"
vm_size = uint64_t(vmmap.size).value
resident_pages = 0
if vmmap.pmap != 0: resident_pages = int(vmmap.pmap.stats.resident_count)
first_free = 0
- if int(vmmap.holelistenabled) == 0: first_free = vmmap.f_s.first_free
- out_string += format_string.format(vmmap, vmmap.pmap, vm_size, vmmap.hdr.nentries, resident_pages, vmmap.hint, first_free)
+ if int(vmmap.holelistenabled) == 0: first_free = vmmap.f_s._first_free
+ out_string += format_string.format(vmmap, vmmap.pmap, vm_size, vmmap.hdr.nentries, resident_pages, vmmap.hint, first_free, vmmap.hdr.page_shift)
return out_string
@lldb_type_summary(['vm_map_entry'])
vme_protection = int(vme.protection)
vme_max_protection = int(vme.max_protection)
vme_extra_info_str ="SC-Ds"[int(vme.inheritance)]
- if int(vme.is_sub_map) != 0 :
+ if int(vme.is_sub_map) != 0 :
vme_extra_info_str +="s"
elif int(vme.needs_copy) != 0 :
vme_extra_info_str +="n"
print "Invalid argument", ShowMapWired.__doc__
return
map_val = kern.GetValueFromAddress(cmd_args[0], 'vm_map_t')
-
+
@lldb_type_summary(['kmod_info_t *'])
-@header("{0: <20s} {1: <20s} {2: <20s} {3: >3s} {4: >5s} {5: >20s} {6: <30s}".format('kmod_info', 'address', 'size', 'id', 'refs', 'version', 'name'))
+@header("{0: <20s} {1: <20s} {2: <20s} {3: >3s} {4: >5s} {5: <20s} {6: <20s} {7: >20s} {8: <30s}".format('kmod_info', 'address', 'size', 'id', 'refs', 'TEXT exec', 'size', 'version', 'name'))
def GetKextSummary(kmod):
- """ returns a string representation of kext information
+ """ returns a string representation of kext information
"""
out_string = ""
- format_string = "{0: <#020x} {1: <#020x} {2: <#020x} {3: >3d} {4: >5d} {5: >20s} {6: <30s}"
- out_string += format_string.format(kmod, kmod.address, kmod.size, kmod.id, kmod.reference_count, kmod.version, kmod.name)
+ format_string = "{0: <#020x} {1: <#020x} {2: <#020x} {3: >3d} {4: >5d} {5: <#020x} {6: <#020x} {7: >20s} {8: <30s}"
+ segments, sections = GetAllSegmentsAndSectionsFromDataInMemory(unsigned(kmod.address), unsigned(kmod.size))
+ text_segment = macho.get_text_segment(segments)
+ if not text_segment:
+ text_segment = segments[0]
+ out_string += format_string.format(kmod, kmod.address, kmod.size, kmod.id, kmod.reference_count, text_segment.vmaddr, text_segment.vmsize, kmod.version, kmod.name)
return out_string
@lldb_type_summary(['uuid_t'])
-@header("")
+@header("")
def GetUUIDSummary(uuid):
""" returns a string representation like CA50DA4C-CA10-3246-B8DC-93542489AA26
"""
"""Display a summary listing of all loaded kexts (alias: showallkmods)
"""
kmod_val = kern.globals.kmod
+ kextuuidinfo = GetKextLoadInformation(show_progress=(config['verbosity'] > vHUMAN))
print "{: <36s} ".format("UUID") + GetKextSummary.header
- kextuuidinfo = GetKextLoadInformation()
for kval in IterateLinkedList(kmod_val, 'next'):
uuid = "........-....-....-....-............"
kaddr = unsigned(kval.address)
+ found_kext_summary = None
for l in kextuuidinfo :
- if kaddr == int(l[1],16):
+ if kaddr == int(l[3],16):
uuid = l[0]
+ found_kext_summary = l
break
- print uuid + " " + GetKextSummary(kval)
+ if found_kext_summary:
+ _ksummary = GetKextSummary(found_kext_summary[7])
+ else:
+ _ksummary = GetKextSummary(kval)
+ print uuid + " " + _ksummary
+
+def GetKmodWithAddr(addr):
+ """ Go through kmod list and find one with begin_addr as addr
+ returns: None if not found. else a cvalue of type kmod
+ """
+ kmod_val = kern.globals.kmod
+ for kval in IterateLinkedList(kmod_val, 'next'):
+ if addr == unsigned(kval.address):
+ return kval
+ return None
-def GetKextLoadInformation(addr=0):
+def GetAllSegmentsAndSectionsFromDataInMemory(address, size):
+ """ reads memory at address and parses mach_header to get segment and section information
+ returns: Tuple of (segments_list, sections_list) like ([MachOSegment,...], [MachOSegment, ...])
+ where MachOSegment has fields like 'name vmaddr vmsize fileoff filesize'
+ if TEXT segment is not found a dummy segment & section with address, size is returned.
+ """
+ cache_hash = "kern.kexts.segments.{}.{}".format(address, size)
+ cached_result = caching.GetDynamicCacheData(cache_hash,())
+ if cached_result:
+ return cached_result
+
+ defval = macho.MachOSegment('__TEXT', address, size, 0, size)
+ if address == 0 or size == 0:
+ return ([defval], [defval])
+
+ ## if int(kern.globals.gLoadedKextSummaries.version) <= 2:
+ # until we have separate version. we will pay penalty only on arm64 devices
+ if not kern.arch.startswith('arm64'):
+ return ([defval], [defval])
+
+ restrict_size_to_read = 1536
+ machoObject = None
+ while machoObject is None:
+ err = lldb.SBError()
+ size_to_read = min(size, restrict_size_to_read)
+ data = LazyTarget.GetProcess().ReadMemory(address, size_to_read, err)
+ if not err.Success():
+ print "Failed to read memory at {} and size {}".format(address, size_to_read)
+ return ([defval], [defval])
+ try:
+ m = macho.MemMacho(data, len(data))
+ machoObject = m
+ except Exception as e:
+ if str(e.message).find('unpack requires a string argument') >= 0:
+ # this may be due to short read of memory. Lets do double read size.
+ restrict_size_to_read *= 2
+ debuglog("Bumping mach header read size to {}".format(restrict_size_to_read))
+ continue
+ else:
+ print "Failed to read MachO for address {} errormessage: {}".format(address, e.message)
+ return ([defval], [defval])
+ # end of while loop. We have machoObject defined
+ segments = machoObject.get_segments_with_name('')
+ sections = machoObject.get_sections_with_name('')
+ rval = (segments, sections)
+ caching.SaveDynamicCacheData(cache_hash, rval)
+ return rval
+
+def GetKextLoadInformation(addr=0, show_progress=False):
""" Extract the kext uuid and load address information from the kernel data structure.
params:
addr - int - optional integer that is the address to search for.
- returns:
- [] - array with each entry of format ( 'UUID', 'Hex Load Address')
+ returns:
+ [] - array with each entry of format
+ ( 'UUID', 'Hex Load Address of __TEXT or __TEXT_EXEC section', 'name',
+ 'addr of macho header', [macho.MachOSegment,..], [MachoSection,...], kext, kmod_obj)
"""
- # because of <rdar://problem/12683084>, we can't find summaries directly
+ cached_result = caching.GetDynamicCacheData("kern.kexts.loadinformation", [])
+ ## if specific addr is provided then ignore caching
+ if cached_result and not addr:
+ return cached_result
+
+ # because of <rdar://problem/12683084>, we can't find summaries directly
#addr = hex(addressof(kern.globals.gLoadedKextSummaries.summaries))
baseaddr = unsigned(kern.globals.gLoadedKextSummaries) + 0x10
summaries_begin = kern.GetValueFromAddress(baseaddr, 'OSKextLoadedKextSummary *')
entry_size = int(kern.globals.gLoadedKextSummaries.entry_size)
retval = []
for i in range(total_summaries):
+ if show_progress:
+ print "progress: {}/{}".format(i, total_summaries)
tmpaddress = unsigned(summaries_begin) + (i * entry_size)
current_kext = kern.GetValueFromAddress(tmpaddress, 'OSKextLoadedKextSummary *')
+ # code to extract macho information
+ segments, sections = GetAllSegmentsAndSectionsFromDataInMemory(unsigned(current_kext.address), unsigned(current_kext.size))
+ seginfo = macho.get_text_segment(segments)
+ if not seginfo:
+ seginfo = segments[0]
+ kmod_obj = GetKmodWithAddr(unsigned(current_kext.address))
if addr != 0 :
- if addr == unsigned(current_kext.address):
- retval.append((GetUUIDSummary(current_kext.uuid) , hex(current_kext.address), str(current_kext.name) ))
- else:
- retval.append((GetUUIDSummary(current_kext.uuid) , hex(current_kext.address), str(current_kext.name) ))
-
+ if addr == unsigned(current_kext.address) or addr == seginfo.vmaddr:
+ return [(GetUUIDSummary(current_kext.uuid) , hex(seginfo.vmaddr).rstrip('L'), str(current_kext.name), hex(current_kext.address), segments, seginfo, current_kext, kmod_obj)]
+ retval.append((GetUUIDSummary(current_kext.uuid) , hex(seginfo.vmaddr).rstrip('L'), str(current_kext.name), hex(current_kext.address), segments, seginfo, current_kext, kmod_obj))
+
+ if not addr:
+ caching.SaveDynamicCacheData("kern.kexts.loadinformation", retval)
return retval
lldb_alias('showallkexts', 'showallkmods')
def GetOSKextVersion(version_num):
""" returns a string of format 1.2.3x from the version_num
params: version_num - int
- return: str
+ return: str
"""
if version_num == -1 :
return "invalid"
(MAJ_MULT, MIN_MULT, REV_MULT,STAGE_MULT) = (100000000, 1000000, 10000, 1000)
version = version_num
-
+
vers_major = version / MAJ_MULT
version = version - (vers_major * MAJ_MULT)
-
+
vers_minor = version / MIN_MULT
version = version - (vers_minor * MIN_MULT)
-
+
vers_revision = version / REV_MULT
version = version - (vers_revision * REV_MULT)
-
+
vers_stage = version / STAGE_MULT
version = version - (vers_stage * STAGE_MULT)
-
- vers_stage_level = version
-
+
+ vers_stage_level = version
+
out_str = "%d.%d" % (vers_major, vers_minor)
if vers_revision > 0: out_str += ".%d" % vers_revision
if vers_stage == 1 : out_str += "d%d" % vers_stage_level
if vers_stage == 3 : out_str += "a%d" % vers_stage_level
if vers_stage == 5 : out_str += "b%d" % vers_stage_level
if vers_stage == 6 : out_str += "fc%d" % vers_stage_level
-
+
return out_str
@lldb_command('showallknownkmods')
print "%d kexts in sKextsByID:" % kext_count
print "{0: <20s} {1: <20s} {2: >5s} {3: >20s} {4: <30s}".format('OSKEXT *', 'load_addr', 'id', 'version', 'name')
format_string = "{0: <#020x} {1: <20s} {2: >5s} {3: >20s} {4: <30s}"
-
+
while index < kext_count:
kext_dict = GetObjectAtIndexFromArray(kext_dictionary, index)
kext_name = str(kext_dict.key.string)
version = GetOSKextVersion(version_num)
print format_string.format(osk, load_addr, id, version, kext_name)
index += 1
-
+
return
+def FindKmodNameForAddr(addr):
+ """ Given an address, return the name of the kext containing that address
+ """
+ addr = unsigned(addr)
+ all_kexts_info = GetKextLoadInformation()
+ for kinfo in all_kexts_info:
+ segment = macho.get_segment_with_addr(kinfo[4], addr)
+ if segment:
+ return kinfo[7].name
+ return None
+
+
+@lldb_command('addkextaddr')
+def AddKextAddr(cmd_args=[]):
+ """ Given an address, load the kext which contains that address
+ Syntax: (lldb) addkextaddr <addr>
+ """
+ if len(cmd_args) < 1:
+ raise ArgumentError("Insufficient arguments")
+
+ addr = ArgumentStringToInt(cmd_args[0])
+ all_kexts_info = GetKextLoadInformation()
+ kernel_uuid = str(kern.globals.kernel_uuid_string).lower()
+ found_kinfo = None
+ found_segment = None
+ for kinfo in all_kexts_info:
+ segment = macho.get_segment_with_addr(kinfo[4], addr)
+ if segment:
+ print GetKextSummary.header
+ print GetKextSummary(kinfo[7]) + " segment: {} offset = {:#0x}".format(segment.name, (addr - segment.vmaddr))
+ cur_uuid = kinfo[0].lower()
+ if (kernel_uuid == cur_uuid):
+ print "(builtin)"
+ else:
+ print "Fetching dSYM for %s" % cur_uuid
+ info = dsymForUUID(cur_uuid)
+ if info and 'DBGSymbolRichExecutable' in info:
+ print "Adding dSYM (%s) for %s" % (cur_uuid, info['DBGSymbolRichExecutable'])
+ addDSYM(cur_uuid, info)
+ loadDSYM(cur_uuid, int(kinfo[1],16), kinfo[4])
+ else:
+ print "Failed to get symbol info for %s" % cur_uuid
+ return
+
+
@lldb_command('showkmodaddr')
def ShowKmodAddr(cmd_args=[]):
- """ Given an address, print the offset and name for the kmod containing it
+ """ Given an address, print the offset and name for the kmod containing it
Syntax: (lldb) showkmodaddr <addr>
"""
if len(cmd_args) < 1:
raise ArgumentError("Insufficient arguments")
addr = ArgumentStringToInt(cmd_args[0])
- kmod_val = kern.globals.kmod
- for kval in IterateLinkedList(kmod_val, 'next'):
- if addr >= unsigned(kval.address) and addr <= (unsigned(kval.address) + unsigned(kval.size)):
- print GetKextSummary.header
- print GetKextSummary(kval) + " offset = {0: #0x}".format((addr - unsigned(kval.address)))
- return True
+ all_kexts_info = GetKextLoadInformation()
+ found_kinfo = None
+ found_segment = None
+ for kinfo in all_kexts_info:
+ s = macho.get_segment_with_addr(kinfo[4], addr)
+ if s:
+ found_segment = s
+ found_kinfo = kinfo
+ break
+ if found_kinfo:
+ print GetKextSummary.header
+ print GetKextSummary(found_kinfo[7]) + " segment: {} offset = {:#0x}".format(found_segment.name, (addr - found_segment.vmaddr))
+ return True
return False
+
@lldb_command('addkext','AF:N:')
def AddKextSyms(cmd_args=[], cmd_options={}):
""" Add kext symbols into lldb.
This command finds symbols for a uuid and load the required executable
- Usage:
+ Usage:
addkext <uuid> : Load one kext based on uuid. eg. (lldb)addkext 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
addkext -F <abs/path/to/executable> <load_address> : Load kext executable at specified load address
addkext -N <name> : Load one kext that matches the name provided. eg. (lldb) addkext -N corecrypto
addkext -N <name> -A: Load all kext that matches the name provided. eg. to load all kext with Apple in name do (lldb) addkext -N Apple -A
- addkext all : Will load all the kext symbols - SLOW
+ addkext all : Will load all the kext symbols - SLOW
"""
-
+
if "-F" in cmd_options:
exec_path = cmd_options["-F"]
raise ArgumentError("Path is {:s} not a filepath. \nPlease check that path points to executable.\
\nFor ex. path/to/Symbols/IOUSBFamily.kext/Contents/PlugIns/AppleUSBHub.kext/Contents/MacOS/AppleUSBHub.\
\nNote: LLDB does not support adding kext based on directory paths like gdb used to.".format(exec_path))
- if not os.access(exec_full_path, os.X_OK):
- raise ArgumentError("Path is {:s} not an executable file".format(exec_path))
slide_value = None
+ sections = None
if cmd_args:
slide_value = cmd_args[0]
debuglog("loading slide value from user input %s" % cmd_args[0])
debuglog(k[0])
if k[0].lower() == uuid_str.lower():
slide_value = k[1]
+ sections = k[4]
debuglog("found the slide %s for uuid %s" % (k[1], k[0]))
-
if slide_value is None:
raise ArgumentError("Unable to find load address for module described at %s " % exec_full_path)
- load_cmd = "target modules load --file %s --slide %s" % (exec_full_path, str(slide_value))
- print load_cmd
- print lldb_run_command(load_cmd)
+
+ if not sections:
+ cmd_str = "target modules load --file %s --slide %s" % ( exec_full_path, str(slide_value))
+ debuglog(cmd_str)
+ else:
+ cmd_str = "target modules load --file {} ".format(exec_full_path)
+ sections_str = ""
+ for s in sections:
+ sections_str += " {} {:#0x} ".format(s.name, s.vmaddr)
+ cmd_str += sections_str
+ debuglog(cmd_str)
+
+ lldb.debugger.HandleCommand(cmd_str)
+
kern.symbolicator = None
return True
all_kexts_info = GetKextLoadInformation()
-
+ kernel_uuid = str(kern.globals.kernel_uuid_string).lower()
+
if "-N" in cmd_options:
kext_name = cmd_options["-N"]
kext_name_matches = GetLongestMatchOption(kext_name, [str(x[2]) for x in all_kexts_info], True)
for x in all_kexts_info:
if cur_knm == x[2]:
cur_uuid = x[0].lower()
- print "Fetching dSYM for {:s}".format(cur_uuid)
- info = dsymForUUID(cur_uuid)
- if info and 'DBGSymbolRichExecutable' in info:
- print "Adding dSYM ({0:s}) for {1:s}".format(cur_uuid, info['DBGSymbolRichExecutable'])
- addDSYM(cur_uuid, info)
- loadDSYM(cur_uuid, int(x[1],16))
+ if (kernel_uuid == cur_uuid):
+ print "(builtin)"
else:
- print "Failed to get symbol info for {:s}".format(cur_uuid)
+ print "Fetching dSYM for {:s}".format(cur_uuid)
+ info = dsymForUUID(cur_uuid)
+ if info and 'DBGSymbolRichExecutable' in info:
+ print "Adding dSYM ({0:s}) for {1:s}".format(cur_uuid, info['DBGSymbolRichExecutable'])
+ addDSYM(cur_uuid, info)
+ loadDSYM(cur_uuid, int(x[1],16), x[4])
+ else:
+ print "Failed to get symbol info for {:s}".format(cur_uuid)
break
kern.symbolicator = None
return
load_all_kexts = False
if uuid == "all":
load_all_kexts = True
-
+
if not load_all_kexts and len(uuid_regex.findall(uuid)) == 0:
raise ArgumentError("Unknown argument {:s}".format(uuid))
for k_info in all_kexts_info:
cur_uuid = k_info[0].lower()
if load_all_kexts or (uuid == cur_uuid):
- print "Fetching dSYM for %s" % cur_uuid
- info = dsymForUUID(cur_uuid)
- if info and 'DBGSymbolRichExecutable' in info:
- print "Adding dSYM (%s) for %s" % (cur_uuid, info['DBGSymbolRichExecutable'])
- addDSYM(cur_uuid, info)
- loadDSYM(cur_uuid, int(k_info[1],16))
- else:
- print "Failed to get symbol info for %s" % cur_uuid
+ if (kernel_uuid != cur_uuid):
+ print "Fetching dSYM for %s" % cur_uuid
+ info = dsymForUUID(cur_uuid)
+ if info and 'DBGSymbolRichExecutable' in info:
+ print "Adding dSYM (%s) for %s" % (cur_uuid, info['DBGSymbolRichExecutable'])
+ addDSYM(cur_uuid, info)
+ loadDSYM(cur_uuid, int(k_info[1],16), k_info[4])
+ else:
+ print "Failed to get symbol info for %s" % cur_uuid
#end of for loop
kern.symbolicator = None
return True
-
+
lldb_alias('showkmod', 'showkmodaddr')
lldb_alias('showkext', 'showkmodaddr')
@lldb_type_summary(['mount *'])
@header("{0: <20s} {1: <20s} {2: <20s} {3: <12s} {4: <12s} {5: <12s} {6: >6s} {7: <30s} {8: <35s} {9: <30s}".format('volume(mp)', 'mnt_data', 'mnt_devvp', 'flag', 'kern_flag', 'lflag', 'type', 'mnton', 'mntfrom', 'iosched supported'))
def GetMountSummary(mount):
- """ Display a summary of mount on the system
+ """ Display a summary of mount on the system
"""
out_string = ("{mnt: <#020x} {mnt.mnt_data: <#020x} {mnt.mnt_devvp: <#020x} {mnt.mnt_flag: <#012x} " +
"{mnt.mnt_kern_flag: <#012x} {mnt.mnt_lflag: <#012x} {vfs.f_fstypename: >6s} " +
_GetVnodePathName(vnode.v_mount.mnt_vnodecovered, str(vnode.v_mount.mnt_vnodecovered.v_name) )
else:
_GetVnodePathName(vnode.v_parent, str(vnode.v_parent.v_name))
- _GetVnodePathName.output += "/%s" % vnodename
+ _GetVnodePathName.output += "/%s" % vnodename
def GetVnodePath(vnode):
""" Get string representation of the vnode
devnode_major = (devnode_dev >> 24) & 0xff
devnode_minor = devnode_dev & 0x00ffffff
- # boilerplate device information for a vnode
+ # boilerplate device information for a vnode
vnodedev_output += "Device Info:\n\t vnode:\t\t{:#x}".format(vnode)
vnodedev_output += "\n\t type:\t\t"
if (vnode.v_type == vblk_type):
vnode_lock_output += ("PID {: <18d}").format(lockf_proc.p_pid)
else:
vnode_lock_output += ("ID {: <#019x}").format(int(lockf.lf_id))
-
+
# lock type
if lockf_type == 1:
vnode_lock_output += ("{: <12s}").format('shared')
vnode_lock_output += ("{: <12s}").format('unlock')
else:
vnode_lock_output += ("{: <12s}").format('unknown')
-
+
# start and stop values
vnode_lock_output += ("{: #018x} ..").format(lockf.lf_start)
vnode_lock_output += ("{: #018x}\n").format(lockf.lf_end)
while lockf_blocker:
out_str += ("{: <4s}").format('>')
out_str += GetVnodeLock(lockf_blocker)
- lockf_blocker = lockf_blocker.lf_block.tqe_next
+ lockf_blocker = lockf_blocker.lf_block.tqe_next
return out_str
@lldb_command('showvnodelocks')
# EndMacro: showvnodelocks
# Macro: showproclocks
-
+
@lldb_command('showproclocks')
def ShowProcLocks(cmd_args=None):
""" Routine to display list of advisory record locks for the given process
seen = 0
while count <= fd_lastfile:
if fd_ofiles[count]:
- fglob = fd_ofiles[count].f_fglob
+ fglob = fd_ofiles[count].fp_glob
fo_type = fglob.fg_ops.fo_type
if fo_type == 1:
fg_data = fglob.fg_data
# EndMacro: showproclocks
@lldb_type_summary(['vnode_t', 'vnode *'])
-@header("{0: <20s} {1: >8s} {2: >8s} {3: <20s} {4: <6s} {5: <20s} {6: <6s} {7: <6s} {8: <35s}".format('vnode', 'usecount', 'iocount', 'v_data', 'vtype', 'parent', 'mapped', 'cs_version', 'name'))
+@header("{0: <20s} {1: >8s} {2: >9s} {3: >8s} {4: <20s} {5: <6s} {6: <20s} {7: <6s} {8: <6s} {9: <35s}".format('vnode', 'usecount', 'kusecount', 'iocount', 'v_data', 'vtype', 'parent', 'mapped', 'cs_version', 'name'))
def GetVnodeSummary(vnode):
""" Get a summary of important information out of vnode
"""
out_str = ''
- format_string = "{0: <#020x} {1: >8d} {2: >8d} {3: <#020x} {4: <6s} {5: <#020x} {6: <6s} {7: <6s} {8: <35s}"
+ format_string = "{0: <#020x} {1: >8d} {2: >8d} {3: >8d} {4: <#020x} {5: <6s} {6: <#020x} {7: <6s} {8: <6s} {9: <35s}"
usecount = int(vnode.v_usecount)
+ kusecount = int(vnode.v_kusecount)
iocount = int(vnode.v_iocount)
v_data_ptr = int(hex(vnode.v_data), 16)
vtype = int(vnode.v_type)
csblob_version = '-'
if (vtype == 1) and (vnode.v_un.vu_ubcinfo != 0):
csblob_version = '{: <6d}'.format(vnode.v_un.vu_ubcinfo.cs_add_gen)
- # Check to see if vnode is mapped/unmapped
+ # Check to see if vnode is mapped/unmapped
if (vnode.v_un.vu_ubcinfo.ui_flags & 0x8) != 0:
mapped = '1'
else:
mapped = '0'
- out_str += format_string.format(vnode, usecount, iocount, v_data_ptr, vtype_str, parent_ptr, mapped, csblob_version, name)
+ out_str += format_string.format(vnode, usecount, kusecount, iocount, v_data_ptr, vtype_str, parent_ptr, mapped, csblob_version, name)
return out_str
@lldb_command('showallvnodes')
vnodeval = kern.GetValueFromAddress(cmd_args[0],'vnode *')
print GetVnodeSummary.header
print GetVnodeSummary(vnodeval)
-
+
@lldb_command('showvolvnodes')
def ShowVolVnodes(cmd_args=None):
""" Display info about all vnodes of a given mount_t
if int(fdptr.fd_rdir) != 0:
print '{0: <25s}\n{1: <s}\n{2: <s}'.format('Current Root Directory:', GetVnodeSummary.header, GetVnodeSummary(fdptr.fd_rdir))
count = 0
- print '\n' + '{0: <5s} {1: <7s}'.format('fd', 'flags') + GetVnodeSummary.header
+ print '\n' + '{0: <5s} {1: <7s} {2: <20s} '.format('fd', 'flags', 'fileglob') + GetVnodeSummary.header
# Hack to get around <rdar://problem/12879494> llb fails to cast addresses to double pointers
- fpptr = Cast(fdptr.fd_ofiles, 'fileproc *')
+ fpptr = Cast(fdptr.fd_ofiles, 'uint64_t *')
while count < fdptr.fd_nfiles:
fpp = dereference(fpptr)
- fproc = Cast(fpp, 'fileproc *')
+ fproc = kern.GetValueFromAddress(int(fpp), 'fileproc *')
if int(fproc) != 0:
- fglob = dereference(fproc).f_fglob
+ fglob = dereference(fproc).fp_glob
flags = ""
if (int(fglob) != 0) and (int(fglob.fg_ops.fo_type) == 1):
if (fdptr.fd_ofileflags[count] & 1): flags += 'E'
if (fdptr.fd_ofileflags[count] & 2): flags += 'F'
if (fdptr.fd_ofileflags[count] & 4): flags += 'R'
if (fdptr.fd_ofileflags[count] & 8): flags += 'C'
- print '{0: <5d} {1: <7s}'.format(count, flags) + GetVnodeSummary(Cast(fglob.fg_data, 'vnode *'))
+ print '{0: <5d} {1: <7s} {2: <#020x} '.format(count, flags, fglob) + GetVnodeSummary(Cast(fglob.fg_data, 'vnode *'))
count += 1
- fpptr = kern.GetValueFromAddress(int(fpptr) + kern.ptrsize,'fileproc *')
+ fpptr = kern.GetValueFromAddress(int(fpptr) + kern.ptrsize,'uint64_t *')
@lldb_command('showallprocvnodes')
def ShowAllProcVnodes(cmd_args=None):
@xnudebug_test('test_vnode')
def TestShowAllVnodes(kernel_target, config, lldb_obj, isConnected ):
""" Test the functionality of vnode related commands
- returns
+ returns
- False on failure
- - True on success
+ - True on success
"""
if not isConnected:
print "Target is not connected. Cannot test memstats"
result = res.GetOutput()
if len(result.split("\n")) > 2 and result.find('VREG') != -1 and len(result.splitlines()[2].split()) > 5:
return True
- else:
+ else:
return False
# Macro: showallmtx
hdr_format = '{:<18s} {:>10s} {:>10s} {:>10s} {:>10s} {:<30s} '
else:
hdr_format = '{:<10s} {:>10s} {:>10s} {:>10s} {:>10s} {:<30s} '
-
- print hdr_format.format('LCK GROUP', 'CNT', 'UTIL', 'MISS', 'WAIT', 'NAME')
+
+ print hdr_format.format('LCK GROUP', 'CNT', 'UTIL', 'MISS', 'WAIT', 'NAME')
mtxgrp_queue_head = kern.globals.lck_grp_queue
- mtxgrp_ptr_type = GetType('_lck_grp_ *')
-
- for mtxgrp_ptr in IterateQueue(mtxgrp_queue_head, mtxgrp_ptr_type, "lck_grp_link"):
+ mtxgrp_ptr_type = GetType('_lck_grp_ *')
+
+ for mtxgrp_ptr in IterateQueue(mtxgrp_queue_head, mtxgrp_ptr_type, "lck_grp_link"):
print GetMutexEntry(mtxgrp_ptr)
return
# EndMacro: showallmtx
return "Invalid lock value: 0x0"
if kern.arch == "x86_64":
- out_str = "Lock Type\t\t: MUTEX\n"
- mtxd = mtx.lck_mtx_sw.lck_mtxd
- out_str += "Owner Thread\t\t: {:#x}\n".format(mtxd.lck_mtxd_owner)
- cmd_str = "p/d ((lck_mtx_t*){:#x})->lck_mtx_sw.lck_mtxd.".format(mtx)
- cmd_out = lldb_run_command(cmd_str + "lck_mtxd_waiters")
- out_str += "Number of Waiters\t: {:s}\n".format(cmd_out.split()[-1])
- cmd_out = lldb_run_command(cmd_str + "lck_mtxd_ilocked")
- out_str += "ILocked\t\t\t: {:s}\n".format(cmd_out.split()[-1])
- cmd_out = lldb_run_command(cmd_str + "lck_mtxd_mlocked")
- out_str += "MLocked\t\t\t: {:s}\n".format(cmd_out.split()[-1])
- cmd_out = lldb_run_command(cmd_str + "lck_mtxd_promoted")
- out_str += "Promoted\t\t: {:s}\n".format(cmd_out.split()[-1])
- cmd_out = lldb_run_command(cmd_str + "lck_mtxd_spin")
- out_str += "Spin\t\t\t: {:s}\n".format(cmd_out.split()[-1])
+ out_str = "Lock Type : MUTEX\n"
+ if mtx.lck_mtx_tag == 0x07ff1007 :
+ out_str += "Tagged as indirect, printing ext lock at: {:#x}\n".format(mtx.lck_mtx_ptr)
+ mtx = Cast(mtx.lck_mtx_ptr, 'lck_mtx_t *')
+
+ if mtx.lck_mtx_tag == 0x07fe2007 :
+ out_str += "*** Tagged as DESTROYED ({:#x}) ***\n".format(mtx.lck_mtx_tag)
+
+ out_str += "Owner Thread : {mtx.lck_mtx_owner:#x}\n".format(mtx=mtx)
+ out_str += "Number of Waiters : {mtx.lck_mtx_waiters:#x}\n".format(mtx=mtx)
+ out_str += "ILocked : {mtx.lck_mtx_ilocked:#x}\n".format(mtx=mtx)
+ out_str += "MLocked : {mtx.lck_mtx_mlocked:#x}\n".format(mtx=mtx)
+ out_str += "Promoted : {mtx.lck_mtx_promoted:#x}\n".format(mtx=mtx)
+ out_str += "Pri : {mtx.lck_mtx_pri:#x}\n".format(mtx=mtx)
+ out_str += "Spin : {mtx.lck_mtx_spin:#x}\n".format(mtx=mtx)
+ out_str += "Ext : {mtx.lck_mtx_is_ext:#x}\n".format(mtx=mtx)
+ if mtx.lck_mtx_pad32 == 0xFFFFFFFF :
+ out_str += "Canary (valid) : {mtx.lck_mtx_pad32:#x}\n".format(mtx=mtx)
+ else:
+ out_str += "Canary (INVALID) : {mtx.lck_mtx_pad32:#x}\n".format(mtx=mtx)
return out_str
out_str = "Lock Type\t\t: MUTEX\n"
- out_str += "Owner Thread\t\t: {:#x}\n".format(mtx.lck_mtx_hdr.lck_mtxd_data & ~0x3)
- out_str += "Number of Waiters\t: {:d}\n".format(mtx.lck_mtx_sw.lck_mtxd.lck_mtxd_waiters)
+ out_str += "Owner Thread\t\t: {:#x}".format(mtx.lck_mtx_data & ~0x3)
+ if (mtx.lck_mtx_data & ~0x3) == 0xfffffff0:
+ out_str += " Held as spinlock"
+ out_str += "\nNumber of Waiters\t: {:d}\n".format(mtx.lck_mtx_waiters)
out_str += "Flags\t\t\t: "
- if mtx.lck_mtx_hdr.lck_mtxd_data & 0x1:
+ if mtx.lck_mtx_data & 0x1:
out_str += "[Interlock Locked] "
- if mtx.lck_mtx_hdr.lck_mtxd_data & 0x2:
+ if mtx.lck_mtx_data & 0x2:
out_str += "[Wait Flag]"
- if (mtx.lck_mtx_hdr.lck_mtxd_data & 0x3) == 0:
- out_str += "None"
return out_str
@lldb_type_summary(['lck_spin_t *'])
out_str += "Interlock\t\t: {:#x}\n".format(spinlock.interlock)
return out_str
- out_str += "Owner Thread\t\t: {:#x}\n".format(spinlock.lck_spin_data & ~0x3)
- out_str += "Flags\t\t\t: "
- if spinlock.lck_spin_data & 0x1:
- out_str += "[Interlock Locked] "
- if spinlock.lck_spin_data & 0x2:
- out_str += "[Wait Flag]"
- if (spinlock.lck_spin_data & 0x3) == 0:
- out_str += "None"
+ lock_data = spinlock.hwlock.lock_data
+ if lock_data == 1:
+ out_str += "Invalid state: interlock is locked but no owner\n"
+ return out_str
+ out_str += "Owner Thread\t\t: "
+ if lock_data == 0:
+ out_str += "None\n"
+ else:
+ out_str += "{:#x}\n".format(lock_data & ~0x1)
+ if (lock_data & 1) == 0:
+ out_str += "Invalid state: owned but interlock bit is not set\n"
return out_str
@lldb_command('showlock', 'MS')
return
summary_str = ""
- lock = kern.GetValueFromAddress(cmd_args[0], 'uintptr_t*')
-
- if kern.arch == "x86_64" and lock:
+ addr = cmd_args[0]
+ # from osfmk/arm/locks.h
+ LCK_SPIN_TYPE = 0x11
+ LCK_MTX_TYPE = 0x22
+ if kern.arch == "x86_64":
if "-M" in cmd_options:
- lock_mtx = Cast(lock, 'lck_mtx_t *')
+ lock_mtx = kern.GetValueFromAddress(addr, 'lck_mtx_t *')
summary_str = GetMutexLockSummary(lock_mtx)
elif "-S" in cmd_options:
- lock_spin = Cast(lock, 'lck_spin_t *')
+ lock_spin = kern.GetValueFromAddress(addr, 'lck_spin_t *')
summary_str = GetSpinLockSummary(lock_spin)
else:
summary_str = "Please specify supported lock option(-M/-S)"
print summary_str
- return
-
- if lock:
- lock_mtx = Cast(lock, 'lck_mtx_t*')
- if lock_mtx.lck_mtx_type == 0x22:
- summary_str = GetMutexLockSummary(lock_mtx)
-
- lock_spin = Cast(lock, 'lck_spin_t*')
- if lock_spin.lck_spin_type == 0x11:
- summary_str = GetSpinLockSummary(lock_spin)
-
- if summary_str == "":
- summary_str = "Lock Type\t\t: INVALID LOCK"
- print summary_str
+ else:
+ lock = kern.GetValueFromAddress(addr, 'uintptr_t *')
+ if lock:
+ lock_mtx = Cast(lock, 'lck_mtx_t*')
+ if lock_mtx.lck_mtx_type == LCK_MTX_TYPE:
+ summary_str = GetMutexLockSummary(lock_mtx)
+
+ lock_spin = Cast(lock, 'lck_spin_t*')
+ if lock_spin.type == LCK_SPIN_TYPE:
+ summary_str = GetSpinLockSummary(lock_spin)
+ if summary_str == "":
+ summary_str = "Lock Type\t\t: INVALID LOCK"
+ print summary_str
#EndMacro: showlock
""" Prints out the phys memory map from kernelBootArgs
Supported only on x86_64
"""
- if kern.arch == 'x86_64':
- voffset = unsigned(0xFFFFFF8000000000)
- else:
+ if kern.arch != 'x86_64':
print "showbootermemorymap not supported on this architecture"
return
-
+
out_string = ""
# Memory type map
i = 0
while i < mcount:
- mptr = kern.GetValueFromAddress(unsigned(boot_args.MemoryMap) + voffset + unsigned(i*msize), 'EfiMemoryRange *')
+ mptr = kern.GetValueFromAddress(unsigned(boot_args.MemoryMap) + kern.VM_MIN_KERNEL_ADDRESS + unsigned(i*msize), 'EfiMemoryRange *')
mtype = unsigned(mptr.Type)
if mtype in memtype_dict:
out_string += "{0: <12s}".format(memtype_dict[mtype])
queue_len = kern.globals.purgeable_nonvolatile_count
queue_head = kern.globals.purgeable_nonvolatile_queue
- print 'purgeable_nonvolatile_queue:{:#018x} purgeable_volatile_count:{:d}\n'.format(kern.GetLoadAddressForSymbol('purgeable_nonvolatile_queue'),queue_len)
+ print 'purgeable_nonvolatile_queue:{: <#018x} purgeable_volatile_count:{:d}\n'.format(kern.GetLoadAddressForSymbol('purgeable_nonvolatile_queue'),queue_len)
print 'N:non-volatile V:volatile E:empty D:deny\n'
- print '{:>6s} {:<6s} {:18s} {:1s} {:>6s} {:>16s} {:>10s} {:>10s} {:>10s} {:18s} {:>6s} {:<20s}\n'.format("#","#","object","P","refcnt","size (pages)","resid","wired","compressed","owner","pid","process")
+ print '{:>6s} {:<6s} {:18s} {:1s} {:>6s} {:>16s} {:>10s} {:>10s} {:>10s} {:>3s} {:18s} {:>6s} {:<20s}\n'.format("#","#","object","P","refcnt","size (pages)","resid","wired","compressed","tag","owner","pid","process")
idx = 0
for object in IterateQueue(queue_head, 'struct vm_object *', 'objq'):
idx += 1
compressor_pager = Cast(object.pager, 'compressor_pager *')
compressed_count = compressor_pager.cpgr_num_slots_occupied
- print "{:>6d}/{:<6d} {:#018x} {:1s} {:>6d} {:>16d} {:>10d} {:>10d} {:>10d} {:#018x} {:>6d} {:<20s}\n".format(idx,queue_len,object,purgable,object.ref_count,object.vo_un1.vou_size/page_size,object.resident_page_count,object.wired_page_count,compressed_count, object.vo_un2.vou_purgeable_owner,GetProcPIDForTask(object.vo_un2.vou_purgeable_owner),GetProcNameForTask(object.vo_un2.vou_purgeable_owner))
+ print "{:>6d}/{:<6d} {: <#018x} {:1s} {:>6d} {:>16d} {:>10d} {:>10d} {:>10d} {:>3d} {: <#018x} {:>6d} {:<20s}\n".format(idx,queue_len,object,purgable,object.ref_count,object.vo_un1.vou_size/page_size,object.resident_page_count,object.wired_page_count,compressed_count, object.vo_ledger_tag, object.vo_un2.vou_owner,GetProcPIDForObjectOwner(object.vo_un2.vou_owner),GetProcNameForObjectOwner(object.vo_un2.vou_owner))
nonvolatile_total.objects += 1
nonvolatile_total.vsize += object.vo_un1.vou_size/page_size
nonvolatile_total.rsize += object.resident_page_count
nonvolatile_total.wsize += object.wired_page_count
nonvolatile_total.csize += compressed_count
- if object.vo_un2.vou_purgeable_owner == 0:
+ if object.vo_un2.vou_owner == 0:
nonvolatile_total.disowned_objects += 1
nonvolatile_total.disowned_vsize += object.vo_un1.vou_size/page_size
nonvolatile_total.disowned_rsize += object.resident_page_count
for object in IterateQueue(qhead, 'struct vm_object *', 'objq'):
if idx == 0:
# print "{:>6s} {:18s} {:1s} {:>6s} {:>16s} {:>10s} {:>10s} {:>10s} {:18s} {:>6s} {:<20s} {:18s} {:>6s} {:<20s} {:s}\n".format("#","object","P","refcnt","size (pages)","resid","wired","compressed","owner","pid","process","volatilizer","pid","process","")
- print "{:>6s} {:18s} {:1s} {:>6s} {:>16s} {:>10s} {:>10s} {:>10s} {:18s} {:>6s} {:<20s}\n".format("#","object","P","refcnt","size (pages)","resid","wired","compressed","owner","pid","process")
+ print "{:>6s} {:18s} {:1s} {:>6s} {:>16s} {:>10s} {:>10s} {:>10s} {:>3s} {:18s} {:>6s} {:<20s}\n".format("#","object","P","refcnt","size (pages)","resid","wired","compressed","tag","owner","pid","process")
idx += 1
ShowPurgeableVolatileVmObject(object, idx, volatile_total)
returns:
None
"""
-# if int(object.vo_un2.vou_purgeable_owner) != int(object.vo_purgeable_volatilizer):
+## if int(object.vo_un2.vou_owner) != int(object.vo_purgeable_volatilizer):
# diff=" !="
-# else:
+## else:
# diff=" "
page_size = kern.globals.page_size
if object.purgable == 0:
else:
compressor_pager = Cast(object.pager, 'compressor_pager *')
compressed_count = compressor_pager.cpgr_num_slots_occupied
-# print "{:>6d} {:#018x} {:1s} {:>6d} {:>16d} {:>10d} {:>10d} {:>10d} {:#018x} {:>6d} {:<20s} {:#018x} {:>6d} {:<20s} {:s}\n".format(idx,object,purgable,object.ref_count,object.vo_un1.vou_size/page_size,object.resident_page_count,object.wired_page_count,compressed_count,object.vo_un2.vou_purgeable_owner,GetProcPIDForTask(object.vo_un2.vou_purgeable_owner),GetProcNameForTask(object.vo_un2.vou_purgeable_owner),object.vo_purgeable_volatilizer,GetProcPIDForTask(object.vo_purgeable_volatilizer),GetProcNameForTask(object.vo_purgeable_volatilizer),diff)
- print "{:>6d} {:#018x} {:1s} {:>6d} {:>16d} {:>10d} {:>10d} {:>10d} {:#018x} {:>6d} {:<20s}\n".format(idx,object,purgable,object.ref_count,object.vo_un1.vou_size/page_size,object.resident_page_count,object.wired_page_count,compressed_count, object.vo_un2.vou_purgeable_owner,GetProcPIDForTask(object.vo_un2.vou_purgeable_owner),GetProcNameForTask(object.vo_un2.vou_purgeable_owner))
+# print "{:>6d} {: <#018x} {:1s} {:>6d} {:>16d} {:>10d} {:>10d} {:>10d} {: <#018x} {:>6d} {:<20s} {: <#018x} {:>6d} {:<20s} {:s}\n".format(idx,object,purgable,object.ref_count,object.vo_un1.vou_size/page_size,object.resident_page_count,object.wired_page_count,compressed_count,object.vo_un2.vou_owner,GetProcPIDForObjectOwner(object.vo_un2.vou_owner),GetProcNameForObjectOwner(object.vo_un2.vou_owner),object.vo_purgeable_volatilizer,GetProcPIDForObjectOwner(object.vo_purgeable_volatilizer),GetProcNameForObjectOwner(object.vo_purgeable_volatilizer),diff)
+ print "{:>6d} {: <#018x} {:1s} {:>6d} {:>16d} {:>10d} {:>10d} {:>10d} {:>3d} {: <#018x} {:>6d} {:<20s}\n".format(idx,object,purgable,object.ref_count,object.vo_un1.vou_size/page_size,object.resident_page_count,object.wired_page_count,compressed_count, object.vo_ledger_tag, object.vo_un2.vou_owner,GetProcPIDForObjectOwner(object.vo_un2.vou_owner),GetProcNameForObjectOwner(object.vo_un2.vou_owner))
volatile_total.objects += 1
volatile_total.vsize += object.vo_un1.vou_size/page_size
volatile_total.rsize += object.resident_page_count
volatile_total.wsize += object.wired_page_count
volatile_total.csize += compressed_count
- if object.vo_un2.vou_purgeable_owner == 0:
+ if object.vo_un2.vou_owner == 0:
volatile_total.disowned_objects += 1
volatile_total.disowned_vsize += object.vo_un1.vou_size/page_size
volatile_total.disowned_rsize += object.resident_page_count
"""
pager = Cast(obj.pager, 'compressor_pager_t')
return pager.cpgr_num_slots_occupied
-# if pager.cpgr_num_slots > 128:
-# slots_arr = pager.cpgr_slots.cpgr_islots
-# num_indirect_slot_ptr = (pager.cpgr_num_slots + 127) / 128
-# index = 0
-# compressor_slot = 0
-# compressed_pages = 0
-# while index < num_indirect_slot_ptr:
-# compressor_slot = 0
-# if slots_arr[index]:
-# while compressor_slot < 128:
-# if slots_arr[index][compressor_slot]:
-# compressed_pages += 1
-# compressor_slot += 1
-# index += 1
-# else:
-# slots_arr = pager.cpgr_slots.cpgr_dslots
-# compressor_slot = 0
-# compressed_pages = 0
-# while compressor_slot < pager.cpgr_num_slots:
-# if slots_arr[compressor_slot]:
-# compressed_pages += 1
-# compressor_slot += 1
-# return compressed_pages
+ """ # commented code below
+ if pager.cpgr_num_slots > 128:
+ slots_arr = pager.cpgr_slots.cpgr_islots
+ num_indirect_slot_ptr = (pager.cpgr_num_slots + 127) / 128
+ index = 0
+ compressor_slot = 0
+ compressed_pages = 0
+ while index < num_indirect_slot_ptr:
+ compressor_slot = 0
+ if slots_arr[index]:
+ while compressor_slot < 128:
+ if slots_arr[index][compressor_slot]:
+ compressed_pages += 1
+ compressor_slot += 1
+ index += 1
+ else:
+ slots_arr = pager.cpgr_slots.cpgr_dslots
+ compressor_slot = 0
+ compressed_pages = 0
+ while compressor_slot < pager.cpgr_num_slots:
+ if slots_arr[compressor_slot]:
+ compressed_pages += 1
+ compressor_slot += 1
+ return compressed_pages
+ """
def ShowTaskVMEntries(task, show_pager_info, show_all_shadows):
""" Routine to print out a summary listing of all the entries in a vm_map
if not task.map:
print "Task {0: <#020x} has map = 0x0"
return None
- showmapvme(task.map, show_pager_info, show_all_shadows)
+ showmapvme(task.map, 0, 0, show_pager_info, show_all_shadows, False)
-@lldb_command("showmapvme", "PS")
+@lldb_command("showmapvme", "A:B:F:PRST")
def ShowMapVME(cmd_args=None, cmd_options={}):
"""Routine to print out info about the specified vm_map and its vm entries
- usage: showmapvme <vm_map>
+ usage: showmapvme <vm_map> [-A start] [-B end] [-S] [-P]
+ Use -A <start> flag to start at virtual address <start>
+ Use -B <end> flag to end at virtual address <end>
+ Use -F <virtaddr> flag to find just the VME containing the given VA
Use -S flag to show VM object shadow chains
Use -P flag to show pager info (mapped file, compressed pages, ...)
+ Use -R flag to reverse order
+ Use -T to show red-black tree pointers
"""
if cmd_args == None or len(cmd_args) < 1:
- print "Invalid argument.", ShowMap.__doc__
+ print "Invalid argument.", ShowMapVME.__doc__
return
show_pager_info = False
show_all_shadows = False
+ show_rb_tree = False
+ start_vaddr = 0
+ end_vaddr = 0
+ reverse_order = False
+ if "-A" in cmd_options:
+ start_vaddr = unsigned(int(cmd_options['-A'], 16))
+ if "-B" in cmd_options:
+ end_vaddr = unsigned(int(cmd_options['-B'], 16))
+ if "-F" in cmd_options:
+ start_vaddr = unsigned(int(cmd_options['-F'], 16))
+ end_vaddr = start_vaddr
if "-P" in cmd_options:
show_pager_info = True
if "-S" in cmd_options:
show_all_shadows = True
+ if "-R" in cmd_options:
+ reverse_order = True
+ if "-T" in cmd_options:
+ show_rb_tree = True
map = kern.GetValueFromAddress(cmd_args[0], 'vm_map_t')
- showmapvme(map, show_pager_info, show_all_shadows)
+ showmapvme(map, start_vaddr, end_vaddr, show_pager_info, show_all_shadows, reverse_order, show_rb_tree)
+
+@lldb_command("showmapcopyvme", "A:B:F:PRST")
+def ShowMapCopyVME(cmd_args=None, cmd_options={}):
+ """Routine to print out info about the specified vm_map_copy and its vm entries
+ usage: showmapcopyvme <vm_map_copy> [-A start] [-B end] [-S] [-P]
+ Use -A <start> flag to start at virtual address <start>
+ Use -B <end> flag to end at virtual address <end>
+ Use -F <virtaddr> flag to find just the VME containing the given VA
+ Use -S flag to show VM object shadow chains
+ Use -P flag to show pager info (mapped file, compressed pages, ...)
+ Use -R flag to reverse order
+ Use -T to show red-black tree pointers
+ """
+ if cmd_args == None or len(cmd_args) < 1:
+ print "Invalid argument.", ShowMapVME.__doc__
+ return
+ show_pager_info = False
+ show_all_shadows = False
+ show_rb_tree = False
+ start_vaddr = 0
+ end_vaddr = 0
+ reverse_order = False
+ if "-A" in cmd_options:
+ start_vaddr = unsigned(int(cmd_options['-A'], 16))
+ if "-B" in cmd_options:
+ end_vaddr = unsigned(int(cmd_options['-B'], 16))
+ if "-F" in cmd_options:
+ start_vaddr = unsigned(int(cmd_options['-F'], 16))
+ end_vaddr = start_vaddr
+ if "-P" in cmd_options:
+ show_pager_info = True
+ if "-S" in cmd_options:
+ show_all_shadows = True
+ if "-R" in cmd_options:
+ reverse_order = True
+ if "-T" in cmd_options:
+ show_rb_tree = True
+ map = kern.GetValueFromAddress(cmd_args[0], 'vm_map_copy_t')
+ showmapcopyvme(map, start_vaddr, end_vaddr, show_pager_info, show_all_shadows, reverse_order, show_rb_tree)
+
+@lldb_command("showvmobject", "A:B:PRST")
+def ShowVMObject(cmd_args=None, cmd_options={}):
+ """Routine to print out a VM object and its shadow chain
+ usage: showvmobject <vm_object> [-S] [-P]
+ -S: show VM object shadow chain
+ -P: show pager info (mapped file, compressed pages, ...)
+ """
+ if cmd_args == None or len(cmd_args) < 1:
+ print "Invalid argument.", ShowMapVME.__doc__
+ return
+ show_pager_info = False
+ show_all_shadows = False
+ if "-P" in cmd_options:
+ show_pager_info = True
+ if "-S" in cmd_options:
+ show_all_shadows = True
+ object = kern.GetValueFromAddress(cmd_args[0], 'vm_object_t')
+ showvmobject(object, 0, 0, show_pager_info, show_all_shadows)
-def showmapvme(map, show_pager_info, show_all_shadows):
+def showvmobject(object, offset=0, size=0, show_pager_info=False, show_all_shadows=False):
page_size = kern.globals.page_size
vnode_pager_ops = kern.globals.vnode_pager_ops
vnode_pager_ops_addr = unsigned(addressof(vnode_pager_ops))
+ depth = 0
+ if size == 0 and object != 0 and object.internal:
+ size = object.vo_un1.vou_size
+ while object != 0:
+ depth += 1
+ if show_all_shadows == False and depth != 1 and object.shadow != 0:
+ offset += unsigned(object.vo_un2.vou_shadow_offset)
+ object = object.shadow
+ continue
+ if object.copy_strategy == 0:
+ copy_strategy="N"
+ elif object.copy_strategy == 2:
+ copy_strategy="D"
+ elif object.copy_strategy == 4:
+ copy_strategy="S"
+
+ else:
+ copy_strategy=str(object.copy_strategy)
+ if object.internal:
+ internal = "internal"
+ else:
+ internal = "external"
+ purgeable = "NVED"[int(object.purgable)]
+ pager_string = ""
+ if object.phys_contiguous:
+ pager_string = pager_string + "phys_contig {:#018x}:{:#018x} ".format(unsigned(object.vo_un2.vou_shadow_offset), unsigned(object.vo_un1.vou_size))
+ pager = object.pager
+ if show_pager_info and pager != 0:
+ if object.internal:
+ pager_string = pager_string + "-> compressed:{:d}".format(GetCompressedPagesForObject(object))
+ elif unsigned(pager.mo_pager_ops) == vnode_pager_ops_addr:
+ vnode_pager = Cast(pager,'vnode_pager *')
+ pager_string = pager_string + "-> " + GetVnodePath(vnode_pager.vnode_handle)
+ else:
+ pager_string = pager_string + "-> {:s}:{: <#018x}".format(pager.mo_pager_ops.memory_object_pager_name, pager)
+ print "{:>18d} {:#018x}:{:#018x} {: <#018x} ref:{:<6d} ts:{:1d} strat:{:1s} purg:{:1s} {:s} wtag:{:d} ({:d} {:d} {:d}) {:s}".format(depth,offset,offset+size,object,object.ref_count,object.true_share,copy_strategy,purgeable,internal,object.wire_tag,unsigned(object.vo_un1.vou_size)/page_size,object.resident_page_count,object.wired_page_count,pager_string)
+# print " #{:<5d} obj {: <#018x} ref:{:<6d} ts:{:1d} strat:{:1s} {:s} size:{:<10d} wired:{:<10d} resident:{:<10d} reusable:{:<10d}".format(depth,object,object.ref_count,object.true_share,copy_strategy,internal,object.vo_un1.vou_size/page_size,object.wired_page_count,object.resident_page_count,object.reusable_page_count)
+ offset += unsigned(object.vo_un2.vou_shadow_offset)
+ object = object.shadow
+
+def showmapvme(map, start_vaddr, end_vaddr, show_pager_info, show_all_shadows, reverse_order=False, show_rb_tree=False):
rsize = 0
if map.pmap != 0:
rsize = int(map.pmap.stats.resident_count)
- print "{:<18s} {:<18s} {:<18s} {:>10s} {:>18s} {:>18s}:{:<18s}".format("vm_map","pmap","size","#ents","rsize","start","end")
- print "{:#018x} {:#018x} {:#018x} {:>10d} {:>18d} {:#018x}:{:#018x}".format(map,map.pmap,unsigned(map.size),map.hdr.nentries,rsize,map.hdr.links.start,map.hdr.links.end)
- vme_list_head = map.hdr.links
+ print "{:<18s} {:<18s} {:<18s} {:>10s} {:>18s} {:>18s}:{:<18s} {:<7s}".format("vm_map","pmap","size","#ents","rsize","start","end","pgshift")
+ print "{: <#018x} {: <#018x} {:#018x} {:>10d} {:>18d} {:#018x}:{:#018x} {:>7d}".format(map,map.pmap,unsigned(map.size),map.hdr.nentries,rsize,map.hdr.links.start,map.hdr.links.end,map.hdr.page_shift)
+ showmaphdrvme(map.hdr, map.pmap, start_vaddr, end_vaddr, show_pager_info, show_all_shadows, reverse_order, show_rb_tree)
+
+def showmapcopyvme(mapcopy, start_vaddr=0, end_vaddr=0, show_pager_info=True, show_all_shadows=True, reverse_order=False, show_rb_tree=False):
+ print "{:<18s} {:<18s} {:<18s} {:>10s} {:>18s} {:>18s}:{:<18s} {:<7s}".format("vm_map_copy","offset","size","#ents","rsize","start","end","pgshift")
+ print "{: <#018x} {:#018x} {:#018x} {:>10d} {:>18d} {:#018x}:{:#018x} {:>7d}".format(mapcopy,mapcopy.offset,mapcopy.size,mapcopy.c_u.hdr.nentries,0,mapcopy.c_u.hdr.links.start,mapcopy.c_u.hdr.links.end,mapcopy.c_u.hdr.page_shift)
+ showmaphdrvme(mapcopy.c_u.hdr, 0, start_vaddr, end_vaddr, show_pager_info, show_all_shadows, reverse_order, show_rb_tree)
+
+def showmaphdrvme(maphdr, pmap, start_vaddr, end_vaddr, show_pager_info, show_all_shadows, reverse_order, show_rb_tree):
+ page_size = kern.globals.page_size
+ vnode_pager_ops = kern.globals.vnode_pager_ops
+ vnode_pager_ops_addr = unsigned(addressof(vnode_pager_ops))
+ if hasattr(kern.globals, 'compressor_object'):
+ compressor_object = kern.globals.compressor_object
+ else:
+ compressor_object = -1;
+ vme_list_head = maphdr.links
vme_ptr_type = GetType('vm_map_entry *')
- print "{:<18s} {:>18s}:{:<18s} {:>10s} {:<8s} {:<10s} {:<18s} {:<18s}".format("entry","start","end","#pgs","tag.kmod","prot&flags","object","offset")
- last_end = unsigned(map.hdr.links.start)
- for vme in IterateQueue(vme_list_head, vme_ptr_type, "links"):
+ print "{:<18s} {:>18s}:{:<18s} {:>10s} {:<8s} {:<16s} {:<18s} {:<18s}".format("entry","start","end","#pgs","tag.kmod","prot&flags","object","offset")
+ last_end = unsigned(maphdr.links.start)
+ skipped_entries = 0
+ for vme in IterateQueue(vme_list_head, vme_ptr_type, "links", reverse_order):
+ if start_vaddr != 0 and end_vaddr != 0:
+ if unsigned(vme.links.start) > end_vaddr:
+ break
+ if unsigned(vme.links.end) <= start_vaddr:
+ last_end = unsigned(vme.links.end)
+ skipped_entries = skipped_entries + 1
+ continue
+ if skipped_entries != 0:
+ print "... skipped {:d} entries ...".format(skipped_entries)
+ skipped_entries = 0
if unsigned(vme.links.start) != last_end:
print "{:18s} {:#018x}:{:#018x} {:>10d}".format("------------------",last_end,vme.links.start,(unsigned(vme.links.start) - last_end)/page_size)
last_end = unsigned(vme.links.end)
size = unsigned(vme.links.end) - unsigned(vme.links.start)
object = vme.vme_object.vmo_object
if object == 0:
- object_str = "{:<#018x}".format(object)
+ object_str = "{: <#018x}".format(object)
elif vme.is_sub_map:
if object == kern.globals.bufferhdr_map:
object_str = "BUFFERHDR_MAP"
object_str = "IPC_KERNEL_COPY_MAP"
elif object == kern.globals.kalloc_map:
object_str = "KALLOC_MAP"
- elif object == kern.globals.zone_map:
- object_str = "ZONE_MAP"
+ elif hasattr(kern.globals, 'compressor_map') and object == kern.globals.compressor_map:
+ object_str = "COMPRESSOR_MAP"
elif hasattr(kern.globals, 'gzalloc_map') and object == kern.globals.gzalloc_map:
object_str = "GZALLOC_MAP"
elif hasattr(kern.globals, 'g_kext_map') and object == kern.globals.g_kext_map:
elif hasattr(kern.globals, 'vector_upl_submap') and object == kern.globals.vector_upl_submap:
object_str = "VECTOR_UPL_SUBMAP"
else:
- object_str = "submap:{:<#018x}".format(object)
+ object_str = "submap:{: <#018x}".format(object)
else:
if object == kern.globals.kernel_object:
object_str = "KERNEL_OBJECT"
elif object == kern.globals.vm_submap_object:
object_str = "VM_SUBMAP_OBJECT"
- elif object == kern.globals.compressor_object:
+ elif object == compressor_object:
object_str = "COMPRESSOR_OBJECT"
else:
- object_str = "{:<#018x}".format(object)
+ object_str = "{: <#018x}".format(object)
offset = unsigned(vme.vme_offset) & ~0xFFF
tag = unsigned(vme.vme_offset & 0xFFF)
+ protection = ""
+ if vme.protection & 0x1:
+ protection +="r"
+ else:
+ protection += "-"
+ if vme.protection & 0x2:
+ protection += "w"
+ else:
+ protection += "-"
+ if vme.protection & 0x4:
+ protection += "x"
+ else:
+ protection += "-"
+ max_protection = ""
+ if vme.max_protection & 0x1:
+ max_protection +="r"
+ else:
+ max_protection += "-"
+ if vme.max_protection & 0x2:
+ max_protection += "w"
+ else:
+ max_protection += "-"
+ if vme.max_protection & 0x4:
+ max_protection += "x"
+ else:
+ max_protection += "-"
vme_flags = ""
if vme.is_sub_map:
vme_flags += "s"
if vme.needs_copy:
vme_flags += "n"
- if vme.is_sub_map and vme.use_pmap:
+ if vme.use_pmap:
vme_flags += "p"
+ if vme.wired_count:
+ vme_flags += "w"
+ if vme.used_for_jit:
+ vme_flags += "j"
tagstr = ""
- if map.pmap == kern.globals.kernel_pmap:
+ if pmap == kern.globals.kernel_pmap:
xsite = Cast(kern.globals.vm_allocation_sites[tag],'OSKextAccount *')
- if xsite and xsite.site.flags & 2:
+ if xsite and xsite.site.flags & 0x0200:
tagstr = ".{:<3d}".format(xsite.loadTag)
- print "{:#018x} {:#018x}:{:#018x} {:>10d} {:>3d}{:<4s} {:1d}{:1d}{:<8s} {:<18s} {:<#18x}".format(vme,vme.links.start,vme.links.end,(unsigned(vme.links.end)-unsigned(vme.links.start))/page_size,tag,tagstr,vme.protection,vme.max_protection,vme_flags,object_str,offset)
+ rb_info = ""
+ if show_rb_tree:
+ rb_info = "l={: <#018x} r={: <#018x} p={: <#018x}".format(vme.store.entry.rbe_left, vme.store.entry.rbe_right, vme.store.entry.rbe_parent)
+ print "{: <#018x} {:#018x}:{:#018x} {:>10d} {:>3d}{:<4s} {:3s}/{:3s}/{:<8s} {:<18s} {:<#18x} {:s}".format(vme,vme.links.start,vme.links.end,(unsigned(vme.links.end)-unsigned(vme.links.start))/page_size,tag,tagstr,protection,max_protection,vme_flags,object_str,offset, rb_info)
if (show_pager_info or show_all_shadows) and vme.is_sub_map == 0 and vme.vme_object.vmo_object != 0:
object = vme.vme_object.vmo_object
else:
object = 0
- depth = 0
- while object != 0:
- depth += 1
- if show_all_shadows == False and depth != 1 and object.shadow != 0:
- offset += unsigned(object.vo_un2.vou_shadow_offset)
- object = object.shadow
- continue
- if object.copy_strategy == 0:
- copy_strategy="N"
- elif object.copy_strategy == 2:
- copy_strategy="D"
- elif object.copy_strategy == 4:
- copy_strategy="S"
- else:
- copy_strategy=str(object.copy_strategy)
- if object.internal:
- internal = "internal"
- else:
- internal = "external"
- pager_string = ""
- pager = object.pager
- if show_pager_info and pager != 0:
- if object.internal:
- pager_string = "-> compressed:{:d}".format(GetCompressedPagesForObject(object))
- elif unsigned(pager.mo_pager_ops) == vnode_pager_ops_addr:
- vnode_pager = Cast(pager,'vnode_pager *')
- pager_string = "-> " + GetVnodePath(vnode_pager.vnode_handle)
- else:
- pager_string = "-> {:s}:{:#018x}".format(pager.mo_pager_ops.memory_object_pager_name, pager.mo_pager_ops)
- print "{:>18d} {:#018x}:{:#018x} {:#018x} ref:{:<6d} ts:{:1d} strat:{:1s} {:s} ({:d} {:d} {:d}) {:s}".format(depth,offset,offset+size,object,object.ref_count,object.true_share,copy_strategy,internal,unsigned(object.vo_un1.vou_size)/page_size,object.resident_page_count,object.wired_page_count,pager_string)
-# print " #{:<5d} obj {:#018x} ref:{:<6d} ts:{:1d} strat:{:1s} {:s} size:{:<10d} wired:{:<10d} resident:{:<10d} reusable:{:<10d}".format(depth,object,object.ref_count,object.true_share,copy_strategy,internal,object.vo_un1.vou_size/page_size,object.wired_page_count,object.resident_page_count,object.reusable_page_count)
- offset += unsigned(object.vo_un2.vou_shadow_offset)
- object = object.shadow
- if unsigned(map.hdr.links.end) > last_end:
- print "{:18s} {:#018x}:{:#018x} {:>10d}".format("------------------",last_end,map.hdr.links.end,(unsigned(map.hdr.links.end) - last_end)/page_size)
+ showvmobject(object, offset, size, show_pager_info, show_all_shadows)
+ if start_vaddr != 0 or end_vaddr != 0:
+ print "..."
+ elif unsigned(maphdr.links.end) > last_end:
+ print "{:18s} {:#018x}:{:#018x} {:>10d}".format("------------------",last_end,maphdr.links.end,(unsigned(maphdr.links.end) - last_end)/page_size)
return None
def CountMapTags(map, tagcounts, slow):
page = _vm_page_unpack_ptr(page_list)
while (page != 0):
vmpage = kern.GetValueFromAddress(page, 'vm_page_t')
- if (addr == unsigned(vmpage.offset)) and (object == vmpage.object):
- if (not vmpage.local) and (vmpage.wire_count > 0):
+ if (addr == unsigned(vmpage.vmp_offset)) and (object == vm_object_t(_vm_page_unpack_ptr(vmpage.vmp_object))):
+ if (not vmpage.vmp_local) and (vmpage.vmp_wire_count > 0):
count += 1
break
- page = _vm_page_unpack_ptr(vmpage.next_m)
+ page = _vm_page_unpack_ptr(vmpage.vmp_next_m)
addr += page_size
tagcounts[tag] += count
elif vme.is_sub_map:
tagcounts[unsigned(object.wire_tag)] += object.wired_page_count
return None
-def CountWiredPurgeableGroup(qhead, tagcounts):
- for object in IterateQueue(qhead, 'struct vm_object *', 'objq'):
- CountWiredObject(object, tagcounts)
- return None
-
-def CountWiredPurgeableQueue(qhead, tagcounts):
- CountWiredPurgeableGroup(qhead.objq[0], tagcounts)
- CountWiredPurgeableGroup(qhead.objq[1], tagcounts)
- CountWiredPurgeableGroup(qhead.objq[2], tagcounts)
- CountWiredPurgeableGroup(qhead.objq[3], tagcounts)
- CountWiredPurgeableGroup(qhead.objq[4], tagcounts)
- CountWiredPurgeableGroup(qhead.objq[5], tagcounts)
- CountWiredPurgeableGroup(qhead.objq[6], tagcounts)
- CountWiredPurgeableGroup(qhead.objq[7], tagcounts)
-
def GetKmodIDName(kmod_id):
kmod_val = kern.globals.kmod
for kmod in IterateLinkedList(kmod_val, 'next'):
return "{:<50s}".format(kmod.name)
return "??"
-def GetVMKernName(tag):
- if 1 == tag:
- return "VM_KERN_MEMORY_OSFMK"
- elif 2 == tag:
- return "VM_KERN_MEMORY_BSD"
- elif 3 == tag:
- return "VM_KERN_MEMORY_IOKIT"
- elif 4 == tag:
- return "VM_KERN_MEMORY_LIBKERN"
- elif 5 == tag:
- return "VM_KERN_MEMORY_OSKEXT"
- elif 6 == tag:
- return "VM_KERN_MEMORY_KEXT"
- elif 7 == tag:
- return "VM_KERN_MEMORY_IPC"
- elif 8 == tag:
- return "VM_KERN_MEMORY_STACK"
- elif 9 == tag:
- return "VM_KERN_MEMORY_CPU"
- elif 10 == tag:
- return "VM_KERN_MEMORY_PMAP"
- elif 11 == tag:
- return "VM_KERN_MEMORY_PTE"
- elif 12 == tag:
- return "VM_KERN_MEMORY_ZONE"
- elif 13 == tag:
- return "VM_KERN_MEMORY_KALLOC"
- elif 14 == tag:
- return "VM_KERN_MEMORY_COMPRESSOR"
- elif 15 == tag:
- return "VM_KERN_MEMORY_COMPRESSED_DATA"
- elif 16 == tag:
- return "VM_KERN_MEMORY_PHANTOM_CACHE"
- elif 17 == tag:
- return "VM_KERN_MEMORY_WAITQ"
- elif 18 == tag:
- return "VM_KERN_MEMORY_DIAG"
- elif 19 == tag:
- return "VM_KERN_MEMORY_LOG"
- elif 20 == tag:
- return "VM_KERN_MEMORY_FILE"
- elif 21 == tag:
- return "VM_KERN_MEMORY_MBUF"
- elif 22 == tag:
- return "VM_KERN_MEMORY_UBC"
- elif 23 == tag:
- return "VM_KERN_MEMORY_SECURITY"
- elif 24 == tag:
- return "VM_KERN_MEMORY_MLOCK"
- return "??"
+FixedTags = {
+ 0: "VM_KERN_MEMORY_NONE",
+ 1: "VM_KERN_MEMORY_OSFMK",
+ 2: "VM_KERN_MEMORY_BSD",
+ 3: "VM_KERN_MEMORY_IOKIT",
+ 4: "VM_KERN_MEMORY_LIBKERN",
+ 5: "VM_KERN_MEMORY_OSKEXT",
+ 6: "VM_KERN_MEMORY_KEXT",
+ 7: "VM_KERN_MEMORY_IPC",
+ 8: "VM_KERN_MEMORY_STACK",
+ 9: "VM_KERN_MEMORY_CPU",
+ 10: "VM_KERN_MEMORY_PMAP",
+ 11: "VM_KERN_MEMORY_PTE",
+ 12: "VM_KERN_MEMORY_ZONE",
+ 13: "VM_KERN_MEMORY_KALLOC",
+ 14: "VM_KERN_MEMORY_COMPRESSOR",
+ 15: "VM_KERN_MEMORY_COMPRESSED_DATA",
+ 16: "VM_KERN_MEMORY_PHANTOM_CACHE",
+ 17: "VM_KERN_MEMORY_WAITQ",
+ 18: "VM_KERN_MEMORY_DIAG",
+ 19: "VM_KERN_MEMORY_LOG",
+ 20: "VM_KERN_MEMORY_FILE",
+ 21: "VM_KERN_MEMORY_MBUF",
+ 22: "VM_KERN_MEMORY_UBC",
+ 23: "VM_KERN_MEMORY_SECURITY",
+ 24: "VM_KERN_MEMORY_MLOCK",
+ 25: "VM_KERN_MEMORY_REASON",
+ 26: "VM_KERN_MEMORY_SKYWALK",
+ 27: "VM_KERN_MEMORY_LTABLE",
+ 28: "VM_KERN_MEMORY_HV",
+ 255:"VM_KERN_MEMORY_ANY",
+}
+def GetVMKernName(tag):
+ """ returns the formatted name for a vmtag and
+ the sub-tag for kmod tags.
+ """
+ if ((tag <= 27) or (tag == 255)):
+ return (FixedTags[tag], "")
+ site = kern.globals.vm_allocation_sites[tag]
+ if site:
+ if site.flags & 0x007F:
+ cstr = addressof(site.subtotals[site.subtotalscount])
+ return ("{:<50s}".format(str(Cast(cstr, 'char *'))), "")
+ else:
+ if site.flags & 0x0200:
+ xsite = Cast(site,'OSKextAccount *')
+ tagstr = ".{:<3d}".format(xsite.loadTag)
+ return (GetKmodIDName(xsite.loadTag), tagstr);
+ else:
+ return (kern.Symbolicate(site), "")
+ return ("", "")
-@lldb_command("showvmtags", "S")
+@lldb_command("showvmtags", "ASJ")
def showvmtags(cmd_args=None, cmd_options={}):
"""Routine to print out info about kernel wired page allocations
usage: showvmtags
iterates kernel map and vm objects totaling allocations by tag.
usage: showvmtags -S
also iterates kernel object pages individually - slow.
+ usage: showvmtags -A
+ show all tags, even tags that have no wired count
+ usage: showvmtags -J
+ Output json
"""
slow = False
+ print_json = False
if "-S" in cmd_options:
slow = True
- page_size = unsigned(kern.globals.page_size)
- tagcounts = []
- for tag in range(256):
- tagcounts.append(0)
-
- queue_head = kern.globals.vm_objects_wired
- for object in IterateQueue(queue_head, 'struct vm_object *', 'objq'):
- CountWiredObject(object, tagcounts)
-
- queue_head = kern.globals.purgeable_nonvolatile_queue
- for object in IterateQueue(queue_head, 'struct vm_object *', 'objq'):
- CountWiredObject(object, tagcounts)
+ all_tags = False
+ if "-A" in cmd_options:
+ all_tags = True
+ if "-J" in cmd_options:
+ print_json = True
- purgeable_queues = kern.globals.purgeable_queues
- CountWiredPurgeableQueue(purgeable_queues[0], tagcounts)
- CountWiredPurgeableQueue(purgeable_queues[1], tagcounts)
- CountWiredPurgeableQueue(purgeable_queues[2], tagcounts)
+ page_size = unsigned(kern.globals.page_size)
+ nsites = unsigned(kern.globals.vm_allocation_tag_highest) + 1
+ tagcounts = [0] * nsites
+ tagpeaks = [0] * nsites
+ tagmapped = [0] * nsites
+
+ if kern.globals.vm_tag_active_update:
+ for tag in range(nsites):
+ site = kern.globals.vm_allocation_sites[tag]
+ if site:
+ tagcounts[tag] = unsigned(site.total)
+ tagmapped[tag] = unsigned(site.mapped)
+ tagpeaks[tag] = unsigned(site.peak)
+ else:
+ queue_head = kern.globals.vm_objects_wired
+ for object in IterateQueue(queue_head, 'struct vm_object *', 'wired_objq'):
+ if object != kern.globals.kernel_object:
+ CountWiredObject(object, tagcounts)
- CountMapTags(kern.globals.kernel_map, tagcounts, slow)
+ CountMapTags(kern.globals.kernel_map, tagcounts, slow)
total = 0
- print " {:<8s} {:>7s} {:<50s}".format("tag.kmod","size","name")
- for tag in range(256):
- if tagcounts[tag]:
+ totalmapped = 0
+ tags = []
+ for tag in range(nsites):
+ if all_tags or tagcounts[tag] or tagmapped[tag]:
+ current = {}
total += tagcounts[tag]
- tagstr = ""
- sitestr = ""
- if (tag <= 24):
- sitestr = GetVMKernName(tag)
- else:
- site = kern.globals.vm_allocation_sites[tag]
- if site:
- if site.flags & 2:
- xsite = Cast(site,'OSKextAccount *')
- tagstr = ".{:<3d}".format(xsite.loadTag)
- sitestr = GetKmodIDName(xsite.loadTag)
- else:
- sitestr = kern.Symbolicate(site)
- print " {:>3d}{:<4s} {:>7d}K {:<50s}".format(tag,tagstr,tagcounts[tag]*page_size / 1024,sitestr)
- print "Total: {:>7d}K".format(total*page_size / 1024)
+ totalmapped += tagmapped[tag]
+ (sitestr, tagstr) = GetVMKernName(tag)
+ current["name"] = sitestr
+ current["size"] = tagcounts[tag]
+ current["mapped"] = tagmapped[tag]
+ current["peak"] = tagpeaks[tag]
+ current["tag"] = tag
+ current["tagstr"] = tagstr
+ current["subtotals"] = []
+
+ site = kern.globals.vm_allocation_sites[tag]
+ for sub in range(site.subtotalscount):
+ alloctag = unsigned(site.subtotals[sub].tag)
+ amount = unsigned(site.subtotals[sub].total)
+ subsite = kern.globals.vm_allocation_sites[alloctag]
+ if alloctag and subsite:
+ (sitestr, tagstr) = GetVMKernName(alloctag)
+ current["subtotals"].append({
+ "amount": amount,
+ "flags": int(subsite.flags),
+ "tag": alloctag,
+ "tagstr": tagstr,
+ "sitestr": sitestr,
+ })
+ tags.append(current)
+
+ if print_json:
+ print json.dumps(tags)
+ else:
+ print " vm_allocation_tag_highest: {:<7d} ".format(nsites - 1)
+ print " {:<7s} {:>7s} {:>7s} {:>7s} {:<50s}".format("tag.kmod", "peak", "size", "mapped", "name")
+ for tag in tags:
+ if not tagstr:
+ tagstr = ""
+ print " {:>3d}{:<4s} {:>7d}K {:>7d}K {:>7d}K {:<50s}".format(tag["tag"], tag["tagstr"], tag["peak"] / 1024, tag["size"] / 1024, tag["mapped"] / 1024, tag["name"])
+ for sub in tag["subtotals"]:
+ if ((sub["flags"] & 0x007f) == 0):
+ kind_str = "named"
+ else:
+ kind_str = "from"
+
+ print " {:>7s} {:>7s} {:>7s} {:>7d}K {:s} {:>3d}{:<4s} {:<50s}".format(" ", " ", " ", sub["amount"] / 1024, kind_str, sub["tag"], sub["tagstr"], sub["sitestr"])
+
+ print "Total: {:>7d}K {:>7d}K".format(total / 1024, totalmapped / 1024)
return None
def FindVMEntriesForVnode(task, vn):
- """ returns an array of vme that have the vnode set to defined vnode
+ """ returns an array of vme that have the vnode set to defined vnode
each entry in array is of format (vme, start_addr, end_address, protection)
"""
retval = []
pass
else:
vn_pager = Cast(obj.pager, 'vnode_pager *')
- if unsigned(vn_pager.pager_ops) == pager_ops_addr and unsigned(vn_pager.vnode_handle) == unsigned(vn):
+ if unsigned(vn_pager.vn_pgr_hdr.mo_pager_ops) == pager_ops_addr and unsigned(vn_pager.vnode_handle) == unsigned(vn):
retval.append((vme, unsigned(vme.links.start), unsigned(vme.links.end), unsigned(vme.protection)))
obj = obj.shadow
return retval
page = _vm_page_unpack_ptr(page_list)
while (page != 0) :
pg_t = kern.GetValueFromAddress(page, 'vm_page_t')
- print format_string.format(page, pg_t.offset, pg_t.object)
- page = _vm_page_unpack_ptr(pg_t.next_m)
+ print format_string.format(page, pg_t.vmp_offset, _vm_page_unpack_ptr(pg_t.vmp_object))
+ page = _vm_page_unpack_ptr(pg_t.vmp_next_m)
+
+
+
+@lldb_command('vmpage_get_phys_page')
+def VmPageGetPhysPage(cmd_args=None):
+ """ return the physical page for a vm_page_t
+ usage: vm_page_get_phys_page <vm_page_t>
+ """
+ if cmd_args == None or len(cmd_args) < 1:
+ print "Please provide valid vm_page_t. Type help vm_page_get_phys_page for help."
+ return
+
+ page = kern.GetValueFromAddress(cmd_args[0], 'vm_page_t')
+ phys_page = _vm_page_get_phys_page(page)
+ print("phys_page = 0x%x\n" % phys_page)
+
+
+def _vm_page_get_phys_page(page):
+ if kern.arch == 'x86_64':
+ return page.vmp_phys_page
+
+ if page == 0 :
+ return 0
+
+ m = unsigned(page)
+ if m >= unsigned(kern.globals.vm_page_array_beginning_addr) and m < unsigned(kern.globals.vm_page_array_ending_addr) :
+ return (m - unsigned(kern.globals.vm_page_array_beginning_addr)) / sizeof('struct vm_page') + unsigned(kern.globals.vm_first_phys_ppnum)
+
+ page_with_ppnum = Cast(page, 'uint32_t *')
+ ppnum_offset = sizeof('struct vm_page') / sizeof('uint32_t')
+ return page_with_ppnum[ppnum_offset]
+
+
+@lldb_command('vmpage_unpack_ptr')
+def VmPageUnpackPtr(cmd_args=None):
+ """ unpack a pointer
+ usage: vm_page_unpack_ptr <packed_ptr>
+ """
+ if cmd_args == None or len(cmd_args) < 1:
+ print "Please provide valid packed pointer argument. Type help vm_page_unpack_ptr for help."
+ return
+
+ packed = kern.GetValueFromAddress(cmd_args[0],'unsigned long')
+ unpacked = _vm_page_unpack_ptr(packed)
+ print("unpacked pointer = 0x%x\n" % unpacked)
+
def _vm_page_unpack_ptr(page):
if kern.ptrsize == 4 :
if page == 0 :
return page
- min_addr = kern.globals.vm_min_kernel_and_kext_address
- #INTEL - min_addr = 0xffffff7f80000000
- #ARM - min_addr = 0x80000000
- #ARM64 - min_addr = 0xffffff8000000000
- return ((page << 6) + min_addr)
+ params = kern.globals.vm_page_packing_params
+ ptr_shift = params.vmpp_shift
+ ptr_mask = kern.globals.vm_packed_from_vm_pages_array_mask
+
+ # when no mask and shift on 64bit systems, we're working with real/non-packed pointers
+ if ptr_shift == 0 and ptr_mask == 0:
+ return page
+
+ if unsigned(page) & unsigned(ptr_mask):
+ masked_page = (unsigned(page) & ~ptr_mask)
+ # can't use addressof(kern.globals.vm_pages[masked_page]) due to 32 bit limitation in SB bridge
+ vm_pages_addr = unsigned(addressof(kern.globals.vm_pages[0]))
+ element_size = unsigned(addressof(kern.globals.vm_pages[1])) - vm_pages_addr
+ return (vm_pages_addr + masked_page * element_size)
+ return unsigned(vm_unpack_pointer(page, params))
@lldb_command('calcvmpagehash')
def CalcVMPageHash(cmd_args=None):
return hash_id
+#Macro: showallocatedzoneelement
+@lldb_command('showallocatedzoneelement')
+def ShowAllocatedElementsInZone(cmd_args=None, cmd_options={}):
+ """ Show all the allocated elements in a zone
+ usage: showzoneallocelements <address of zone>
+ """
+ if len(cmd_args) < 1:
+ raise ArgumentError("Please specify a zone")
+
+ zone = kern.GetValueFromAddress(cmd_args[0], 'struct zone *')
+ elements = FindAllocatedElementsInZone(zone)
+ i = 1
+ for elem in elements:
+ print "{0: >10d}/{1:<10d} element: {2: <#20x}".format(i, len(elements), elem)
+ i += 1
+
+#EndMacro: showallocatedzoneelement
+
+def FindAllocatedElementsInZone(zone):
+ elements = []
+
+ if not zone.z_self or zone.permanent:
+ return elements
+
+ for head in [zone.pages_any_free_foreign, zone.pages_all_used_foreign,
+ zone.pages_intermediate, zone.pages_all_used]:
+
+ for meta in ZoneIteratePageQueue(head):
+ free_elements = set(meta.iterateFreeList())
+
+ for elem in meta.iterateElements():
+ if elem in free_elements:
+ continue
+
+ if elem not in free_elements:
+ elements.append(elem)
+ elem += zone.z_elem_size
+
+ return elements
+
+def match_vm_page_attributes(page, matching_attributes):
+ page_ptr = addressof(page)
+ unpacked_vm_object = _vm_page_unpack_ptr(page.vmp_object)
+ matched_attributes = 0
+ if "vmp_q_state" in matching_attributes and (page.vmp_q_state == matching_attributes["vmp_q_state"]):
+ matched_attributes += 1
+ if "vm_object" in matching_attributes and (unsigned(unpacked_vm_object) == unsigned(matching_attributes["vm_object"])):
+ matched_attributes += 1
+ if "vmp_offset" in matching_attributes and (unsigned(page.vmp_offset) == unsigned(matching_attributes["vmp_offset"])):
+ matched_attributes += 1
+ if "phys_page" in matching_attributes and (unsigned(_vm_page_get_phys_page(page_ptr)) == unsigned(matching_attributes["phys_page"])):
+ matched_attributes += 1
+ if "bitfield" in matching_attributes and unsigned(page.__getattr__(matching_attributes["bitfield"])) == 1:
+ matched_attributes += 1
+
+ return matched_attributes
+
+#Macro scan_vm_pages
+@header("{0: >26s}{1: >20s}{2: >10s}{3: >20s}{4: >20s}{5: >16s}".format("vm_pages_index/zone", "vm_page", "q_state", "vm_object", "offset", "ppn", "bitfield", "from_zone_map"))
+@lldb_command('scan_vm_pages', 'S:O:F:I:P:B:I:N:ZA')
+def ScanVMPages(cmd_args=None, cmd_options={}):
+ """ Scan the global vm_pages array (-A) and/or vmpages zone (-Z) for pages with matching attributes.
+ usage: scan_vm_pages <matching attribute(s)> [-A start vm_pages index] [-N number of pages to scan] [-Z scan vm_pages zone]
+
+ scan_vm_pages -A: scan vm pages in the global vm_pages array
+ scan_vm_pages -Z: scan vm pages allocated from the vm.pages zone
+ scan_vm_pages <-A/-Z> -S <vm_page_q_state value>: Find vm pages in the specified queue
+ scan_vm_pages <-A/-Z> -O <vm_object>: Find vm pages in the specified vm_object
+ scan_vm_pages <-A/-Z> -F <offset>: Find vm pages with the specified vmp_offset value
+ scan_vm_pages <-A/-Z> -P <phys_page>: Find vm pages with the specified physical page number
+ scan_vm_pages <-A/-Z> -B <bitfield>: Find vm pages with the bitfield set
+ scan_vm_pages <-A> -I <start_index>: Start the scan from start_index
+ scan_vm_pages <-A> -N <npages>: Scan at most npages
+ """
+ if (len(cmd_options) < 1):
+ raise ArgumentError("Please specify at least one matching attribute")
+
+ vm_pages = kern.globals.vm_pages
+ vm_pages_count = kern.globals.vm_pages_count
+
+ start_index = 0
+ npages = vm_pages_count
+ scan_vmpages_array = False
+ scan_vmpages_zone = False
+ attribute_count = 0
+
+ if "-A" in cmd_options:
+ scan_vmpages_array = True
+
+ if "-Z" in cmd_options:
+ scan_vmpages_zone = True
+
+ if scan_vmpages_array == False and scan_vmpages_zone == False:
+ raise ArgumentError("Please specify where to scan (-A: vm_pages array, -Z: vm.pages zone)")
+
+ attribute_values = {}
+ if "-S" in cmd_options:
+ attribute_values["vmp_q_state"] = kern.GetValueFromAddress(cmd_options["-S"], 'int')
+ attribute_count += 1
+
+ if "-O" in cmd_options:
+ attribute_values["vm_object"] = kern.GetValueFromAddress(cmd_options["-O"], 'vm_object_t')
+ attribute_count += 1
+
+ if "-F" in cmd_options:
+ attribute_values["vmp_offset"] = kern.GetValueFromAddress(cmd_options["-F"], 'unsigned long long')
+ attribute_count += 1
+
+ if "-P" in cmd_options:
+ attribute_values["phys_page"] = kern.GetValueFromAddress(cmd_options["-P"], 'unsigned int')
+ attribute_count += 1
+
+ if "-B" in cmd_options:
+ valid_vmp_bitfields = [
+ "vmp_in_background",
+ "vmp_on_backgroundq",
+ "vmp_gobbled",
+ "vmp_laundry",
+ "vmp_no_cache",
+ "vmp_private",
+ "vmp_reference",
+ "vmp_busy",
+ "vmp_wanted",
+ "vmp_tabled",
+ "vmp_hashed",
+ "vmp_fictitious",
+ "vmp_clustered",
+ "vmp_pmapped",
+ "vmp_xpmapped",
+ "vmp_free_when_done",
+ "vmp_absent",
+ "vmp_error",
+ "vmp_dirty",
+ "vmp_cleaning",
+ "vmp_precious",
+ "vmp_overwriting",
+ "vmp_restart",
+ "vmp_unusual",
+ "vmp_cs_validated",
+ "vmp_cs_tainted",
+ "vmp_cs_nx",
+ "vmp_reusable",
+ "vmp_lopage",
+ "vmp_written_by_kernel",
+ "vmp_unused_object_bits"
+ ]
+ attribute_values["bitfield"] = cmd_options["-B"]
+ if attribute_values["bitfield"] in valid_vmp_bitfields:
+ attribute_count += 1
+ else:
+ raise ArgumentError("Unknown bitfield: {0:>20s}".format(bitfield))
+
+ if "-I" in cmd_options:
+ start_index = kern.GetValueFromAddress(cmd_options["-I"], 'int')
+ npages = vm_pages_count - start_index
+
+ if "-N" in cmd_options:
+ npages = kern.GetValueFromAddress(cmd_options["-N"], 'int')
+ if npages == 0:
+ raise ArgumentError("You specified -N 0, nothing to be scanned")
+
+ end_index = start_index + npages - 1
+ if end_index >= vm_pages_count:
+ raise ArgumentError("Index range out of bound. vm_pages_count: {0:d}".format(vm_pages_count))
+
+ header_after_n_lines = 40
+ format_string = "{0: >26s}{1: >#20x}{2: >10d}{3: >#20x}{4: >#20x}{5: >#16x}"
+
+ found_in_array = 0
+ if scan_vmpages_array:
+ print "Scanning vm_pages[{0:d} to {1:d}] for {2:d} matching attribute(s)......".format(start_index, end_index, attribute_count)
+ i = start_index
+ while i <= end_index:
+ page = vm_pages[i]
+ if match_vm_page_attributes(page, attribute_values) == attribute_count:
+ if found_in_array % header_after_n_lines == 0:
+ print ScanVMPages.header
+
+ print format_string.format(str(i), addressof(page), page.vmp_q_state, _vm_page_unpack_ptr(page.vmp_object), page.vmp_offset, _vm_page_get_phys_page(addressof(page)))
+ found_in_array += 1
+
+ i += 1
+
+ found_in_zone = 0
+ if scan_vmpages_zone:
+ page_size = kern.GetGlobalVariable('page_size')
+ num_zones = kern.GetGlobalVariable('num_zones')
+ zone_array = kern.GetGlobalVariable('zone_array')
+ print "Scanning vm.pages zone for {0:d} matching attribute(s)......".format(attribute_count)
+ i = 0
+ while i < num_zones:
+ zone = zone_array[i]
+ if str(zone.z_name) == "vm pages":
+ break;
+ i += 1
+
+ if i == num_zones:
+ print "Cannot find vm_pages zone, skip the scan"
+ else:
+ print "Scanning page queues in the vm_pages zone..."
+ elements = FindAllocatedElementsInZone(zone)
+ for elem in elements:
+ page = kern.GetValueFromAddress(elem, 'vm_page_t')
+
+ if match_vm_page_attributes(page, attribute_values) == attribute_count:
+ if found_in_zone % header_after_n_lines == 0:
+ print ScanVMPages.header
+
+ vm_object = _vm_page_unpack_ptr(page.vmp_object)
+ phys_page = _vm_page_get_phys_page(page)
+ print format_string.format("vm_pages zone", elem, page.vmp_q_state, vm_object, page.vmp_offset, phys_page)
+ found_in_zone += 1
+
+ total = found_in_array + found_in_zone
+ print "Found {0:d} vm pages ({1:d} in array, {2:d} in zone) matching the requested {3:d} attribute(s)".format(total, found_in_array, found_in_zone, attribute_count)
+
+#EndMacro scan_vm_pages
+
+VM_PAGE_IS_WIRED = 1
+
@header("{0: <10s} of {1: <10s} {2: <20s} {3: <20s} {4: <20s} {5: <10s} {6: <5s}\t {7: <28s}\t{8: <50s}".format("index", "total", "vm_page_t", "offset", "next", "phys_page", "wire#", "first bitfield", "second bitfield"))
-@lldb_command('vmobjectwalkpages', 'SBNQP:')
+@lldb_command('vmobjectwalkpages', 'CSBNQP:O:')
def VMObjectWalkPages(cmd_args=None, cmd_options={}):
""" Print the resident pages contained in the provided object. If a vm_page_t is provided as well, we
specifically look for this page, highlighting it in the output or noting if it was not found. For
see and compare this to the object's resident page count field.
Usage:
vmobjectwalkpages <vm_object_t> : Walk and print all the pages for a given object (up to 4K pages by default)
+ vmobjectwalkpages <vm_object_t> -C : list pages in compressor after processing resident pages
vmobjectwalkpages <vm_object_t> -B : Walk and print all the pages for a given object (up to 4K pages by default), traversing the memq backwards
vmobjectwalkpages <vm_object_t> -N : Walk and print all the pages for a given object, ignore the page limit
- vmobjectwalkpages <vm_object_t> -Q : Walk all pages for a given object, looking for known signs of corruption (i.e. inactive and active both being set for a page)
+ vmobjectwalkpages <vm_object_t> -Q : Walk all pages for a given object, looking for known signs of corruption (i.e. q_state == VM_PAGE_IS_WIRED && wire_count == 0)
vmobjectwalkpages <vm_object_t> -P <vm_page_t> : Walk all the pages for a given object, annotate the specified page in the output with ***
vmobjectwalkpages <vm_object_t> -P <vm_page_t> -S : Walk all the pages for a given object, stopping when we find the specified page
+ vmobjectwalkpages <vm_object_t> -O <offset> : Like -P, but looks for given offset
"""
if "-P" in cmd_options:
page = kern.GetValueFromAddress(cmd_options['-P'], 'vm_page_t')
+ off = -1
+ if "-O" in cmd_options:
+ off = kern.GetValueFromAddress(cmd_options['-O'], 'vm_offset_t')
+
stop = 0
if "-S" in cmd_options:
- if page == 0:
- raise ArgumentError("-S can only be passed when a page is specified with -P")
+ if page == 0 and off < 0:
+ raise ArgumentError("-S can only be passed when a page is specified with -P or -O")
stop = 1
walk_backwards = False
if not quiet_mode:
print VMObjectWalkPages.header
format_string = "{0: <#10d} of {1: <#10d} {2: <#020x} {3: <#020x} {4: <#020x} {5: <#010x} {6: <#05d}\t"
- first_bitfield_format_string = "{0: <#1d}:{1: <#1d}:{2: <#1d}:{3: <#1d}:{4: <#1d}:{5: <#1d}:{6: <#1d}:"
- first_bitfield_format_string += "{7: <#1d}:{8: <#1d}:{9: <#1d}:{10: <#1d}:{11: <#1d}:{12: <#1d}"
- second_bitfield_format_string = first_bitfield_format_string
- second_bitfield_format_string += ":{13: <#1d}:{14: <#1d}:{15: <#1d}:{16: <#1d}:{17: <#1d}:{18: <#1d}:{19: <#1d}:"
+ first_bitfield_format_string = "{0: <#2d}:{1: <#1d}:{2: <#1d}:{3: <#1d}:{4: <#1d}:{5: <#1d}:{6: <#1d}:{7: <#1d}\t"
+ second_bitfield_format_string = "{0: <#1d}:{1: <#1d}:{2: <#1d}:{3: <#1d}:{4: <#1d}:{5: <#1d}:{6: <#1d}:"
+ second_bitfield_format_string += "{7: <#1d}:{8: <#1d}:{9: <#1d}:{10: <#1d}:{11: <#1d}:{12: <#1d}:"
+ second_bitfield_format_string += "{13: <#1d}:{14: <#1d}:{15: <#1d}:{16: <#1d}:{17: <#1d}:{18: <#1d}:{19: <#1d}:"
second_bitfield_format_string += "{20: <#1d}:{21: <#1d}:{22: <#1d}:{23: <#1d}:{24: <#1d}:{25: <#1d}:{26: <#1d}\n"
- first_bitfield_format_string += "\t"
limit = 4096 #arbitrary limit of number of pages to walk
ignore_limit = 0
if "-N" in cmd_options:
ignore_limit = 1
+ show_compressed = 0
+ if "-C" in cmd_options:
+ show_compressed = 1
+
page_count = 0
res_page_count = unsigned(obj.resident_page_count)
page_found = False
pages_seen = set()
- for vmp in IterateQueue(obj.memq, "vm_page_t", "listq", walk_backwards):
+ for vmp in IterateQueue(obj.memq, "vm_page_t", "vmp_listq", walk_backwards, unpack_ptr_fn=_vm_page_unpack_ptr):
page_count += 1
out_string = ""
if (page != 0 and not(page_found) and vmp == page):
out_string += "******"
page_found = True
- if page != 0 or quiet_mode:
+ if (off > 0 and not(page_found) and vmp.vmp_offset == off):
+ out_string += "******"
+ page_found = True
+
+ if page != 0 or off > 0 or quiet_mode:
if (page_count % 1000) == 0:
print "traversed %d pages ...\n" % (page_count)
else:
- out_string += format_string.format(page_count, res_page_count, vmp, vmp.offset, vmp.listq.next, vmp.phys_page, vmp.wire_count)
- out_string += first_bitfield_format_string.format(vmp.active, vmp.inactive, vmp.clean_queue, vmp.local, vmp.speculative,
- vmp.throttled, vmp.free, vmp.pageout_queue, vmp.laundry, vmp.reference,
- vmp.gobbled, vmp.private, vmp.no_cache)
-
- out_string += second_bitfield_format_string.format(vmp.busy, vmp.wanted, vmp.tabled, vmp.hashed, vmp.fictitious, vmp.clustered,
- vmp.clustered, vmp.pmapped, vmp.xpmapped, vmp.wpmapped, vmp.pageout, vmp.absent,
- vmp.error, vmp.dirty, vmp.cleaning, vmp.precious, vmp.precious, vmp.overwriting,
- vmp.restart, vmp.unusual, vmp.encrypted, vmp.encrypted, vmp.encrypted_cleaning,
- vmp.cs_validated, vmp.cs_tainted, vmp.cs_nx, vmp.reusable, vmp.lopage, vmp.slid, vmp.compressor,
- vmp.written_by_kernel)
+ out_string += format_string.format(page_count, res_page_count, vmp, vmp.vmp_offset, _vm_page_unpack_ptr(vmp.vmp_listq.next), _vm_page_get_phys_page(vmp), vmp.vmp_wire_count)
+ out_string += first_bitfield_format_string.format(vmp.vmp_q_state, vmp.vmp_in_background, vmp.vmp_on_backgroundq, vmp.vmp_gobbled, vmp.vmp_laundry, vmp.vmp_no_cache,
+ vmp.vmp_private, vmp.vmp_reference)
+
+ if hasattr(vmp,'slid'):
+ vmp_slid = vmp.slid
+ else:
+ vmp_slid = 0
+ out_string += second_bitfield_format_string.format(vmp.vmp_busy, vmp.vmp_wanted, vmp.vmp_tabled, vmp.vmp_hashed, vmp.vmp_fictitious, vmp.vmp_clustered,
+ vmp.vmp_pmapped, vmp.vmp_xpmapped, vmp.vmp_wpmapped, vmp.vmp_free_when_done, vmp.vmp_absent,
+ vmp.vmp_error, vmp.vmp_dirty, vmp.vmp_cleaning, vmp.vmp_precious, vmp.vmp_overwriting,
+ vmp.vmp_restart, vmp.vmp_unusual, 0, 0,
+ vmp.vmp_cs_validated, vmp.vmp_cs_tainted, vmp.vmp_cs_nx, vmp.vmp_reusable, vmp.vmp_lopage, vmp_slid,
+ vmp.vmp_written_by_kernel)
if (vmp in pages_seen):
print out_string + "cycle detected! we've seen vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + " twice. stopping...\n"
return
- if (vmp.object != obj):
- print out_string + " vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + " points to different vm_object_t: " + "{0: <#020x}".format(unsigned(vmp.object))
+ if (_vm_page_unpack_ptr(vmp.vmp_object) != unsigned(obj)):
+ print out_string + " vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + " points to different vm_object_t: " + "{0: <#020x}".format(unsigned(_vm_page_unpack_ptr(vmp.vmp_object)))
return
- if (not vmp.local) and (vmp.wire_count > 0):
- if (vmp.active or vmp.inactive or vmp.speculative or vmp.throttled or vmp.pageout_queue):
- print out_string + " wired page with wrong page queue attributes\n"
- print "vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + " active: %d inactive: %d speculative: %d throttled %d pageout_queue: %d\n" % (vmp.active,
- vmp.inactive, vmp.speculative, vmp.throttled, vmp.pageout_queue)
- print "stopping...\n"
- return
+ if (vmp.vmp_q_state == VM_PAGE_IS_WIRED) and (vmp.vmp_wire_count == 0):
+ print out_string + " page in wired state with wire_count of 0\n"
+ print "vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + "\n"
+ print "stopping...\n"
+ return
- if ((vmp.free + vmp.active + vmp.inactive + vmp.speculative + vmp.throttled + vmp.pageout_queue) > 1):
- print out_string + " more than one pageout queue bit set active\n"
- print "vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + " free: %d active: %d inactive: %d speculative: %d throttled: %d pageout_queue: %d\n" % (vmp.free,
- vmp.active, vmp.inactive, vmp.speculative, vmp.throttled, vmp.pageout_queue)
+ if (hasattr(vmp, 'vmp_unused_page_bits') and (vmp.vmp_unused_page_bits != 0)):
+ print out_string + " unused bits not zero for vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + " unused__pageq_bits: %d\n" % (vmp.vmp_unused_page_bits)
print "stopping...\n"
return
- if ((vmp.__unused_pageq_bits != 0) or (vmp.__unused_object_bits != 0)):
- print out_string + " unused bits not zero for vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + " unused__pageq_bits: %d unused_object_bits : %d\n" % (vmp.__unused_pageq_bits,
- vmp.__unused_object_bits)
+ if (hasattr(vmp, 'vmp_unused_object_bits') and (vmp.vmp_unused_object_bits != 0)):
+ print out_string + " unused bits not zero for vm_page_t: " + "{0: <#020x}".format(unsigned(vmp)) + " unused_object_bits : %d\n" % (vmp.vmp_unused_object_bits)
print "stopping...\n"
return
pages_seen.add(vmp)
if False:
- hash_id = _calc_vm_page_hash(obj, vmp.offset)
+ hash_id = _calc_vm_page_hash(obj, vmp.vmp_offset)
hash_page_list = kern.globals.vm_page_buckets[hash_id].page_list
hash_page = _vm_page_unpack_ptr(hash_page_list)
hash_page_t = 0
hash_page_t = kern.GetValueFromAddress(hash_page, 'vm_page_t')
if hash_page_t == vmp:
break
- hash_page = _vm_page_unpack_ptr(hash_page_t.next_m)
+ hash_page = _vm_page_unpack_ptr(hash_page_t.vmp_next_m)
if (unsigned(vmp) != unsigned(hash_page_t)):
print out_string + "unable to find page: " + "{0: <#020x}".format(unsigned(vmp)) + " from object in kernel page bucket list\n"
- print lldb_run_command("vm_page_info %s 0x%x" % (cmd_args[0], unsigned(vmp.offset)))
+ print lldb_run_command("vm_page_info %s 0x%x" % (cmd_args[0], unsigned(vmp.vmp_offset)))
return
if (page_count >= limit and not(ignore_limit)):
print out_string + "Limit reached (%d pages), stopping..." % (limit)
- return
+ break
print out_string
if (page != 0):
print("page found? : %s\n" % page_found)
+ if (off > 0):
+ print("page found? : %s\n" % page_found)
+
print("Object reports resident page count of %d, we saw %d pages when we walked the resident list.\n" % (unsigned(obj.resident_page_count), unsigned(page_count)))
+ if show_compressed != 0 and obj.pager != 0 and unsigned(obj.pager.mo_pager_ops) == unsigned(addressof(kern.globals.compressor_pager_ops)):
+ pager = Cast(obj.pager, 'compressor_pager *')
+ chunks = pager.cpgr_num_slots / 128
+ pagesize = kern.globals.page_size
+
+ page_idx = 0
+ while page_idx < pager.cpgr_num_slots:
+ if chunks != 0:
+ chunk = pager.cpgr_slots.cpgr_islots[page_idx / 128]
+ slot = chunk[page_idx % 128]
+ elif pager.cpgr_num_slots > 2:
+ slot = pager.cpgr_slots.cpgr_dslots[page_idx]
+ else:
+ slot = pager.cpgr_slots.cpgr_eslots[page_idx]
+
+ if slot != 0:
+ print("compressed page for offset: %x slot %x\n" % ((page_idx * pagesize) - obj.paging_offset, slot))
+ page_idx = page_idx + 1
+
@lldb_command("show_all_apple_protect_pagers")
def ShowAllAppleProtectPagers(cmd_args=None):
usage: show_apple_protect_pager <pager>
"""
if cmd_args == None or len(cmd_args) < 1:
- print "Invalid argument.", ShowMap.__doc__
+ print "Invalid argument.", ShowAppleProtectPager.__doc__
return
- pager = kern.GetValueFromAddress(cmd_ars[0], 'apple_protect_pager_t')
+ pager = kern.GetValueFromAddress(cmd_args[0], 'apple_protect_pager_t')
show_apple_protect_pager(pager, 1, 1)
def show_apple_protect_pager(pager, qcnt, idx):
shadow = object.shadow
vnode_pager = Cast(object.pager,'vnode_pager *')
filename = GetVnodePath(vnode_pager.vnode_handle)
- print "{:>3}/{:<3d} {:#018x} {:>5d} {:>5d} {:>6d} {:#018x} {:#018x} {:#018x} {:#018x} {:#018x} {:#018x}\n\tcrypt_info:{:#018x} <decrypt:{:#018x} end:{:#018x} ops:{:#018x} refs:{:<d}>\n\tvnode:{:#018x} {:s}\n".format(idx, qcnt, pager, pager.ref_count, pager.is_ready, pager.is_mapped, pager.pager_control, pager.backing_object, pager.backing_offset, pager.crypto_backing_offset, pager.crypto_start, pager.crypto_end, pager.crypt_info, pager.crypt_info.page_decrypt, pager.crypt_info.crypt_end, pager.crypt_info.crypt_ops, pager.crypt_info.crypt_refcnt, vnode_pager.vnode_handle, filename)
+ print "{:>3}/{:<3d} {: <#018x} {:>5d} {:>5d} {:>6d} {: <#018x} {: <#018x} {:#018x} {:#018x} {:#018x} {:#018x}\n\tcrypt_info:{: <#018x} <decrypt:{: <#018x} end:{:#018x} ops:{: <#018x} refs:{:<d}>\n\tvnode:{: <#018x} {:s}\n".format(idx, qcnt, pager, pager.ref_count, pager.is_ready, pager.is_mapped, pager.pager_control, pager.backing_object, pager.backing_offset, pager.crypto_backing_offset, pager.crypto_start, pager.crypto_end, pager.crypt_info, pager.crypt_info.page_decrypt, pager.crypt_info.crypt_end, pager.crypt_info.crypt_ops, pager.crypt_info.crypt_refcnt, vnode_pager.vnode_handle, filename)
+
+@lldb_command("show_console_ring")
+def ShowConsoleRingData(cmd_args=None):
+ """ Print console ring buffer stats and data
+ """
+ cr = kern.globals.console_ring
+ print "console_ring = {:#018x} buffer = {:#018x} length = {:<5d} used = {:<5d} read_ptr = {:#018x} write_ptr = {:#018x}".format(addressof(cr), cr.buffer, cr.len, cr.used, cr.read_ptr, cr.write_ptr)
+ pending_data = []
+ for i in range(unsigned(cr.used)):
+ idx = ((unsigned(cr.read_ptr) - unsigned(cr.buffer)) + i) % unsigned(cr.len)
+ pending_data.append("{:c}".format(cr.buffer[idx]))
+
+ if pending_data:
+ print "Data:"
+ print "".join(pending_data)
+
+# Macro: showjetsamsnapshot
+
+@lldb_command("showjetsamsnapshot", "DA")
+def ShowJetsamSnapshot(cmd_args=None, cmd_options={}):
+ """ Dump entries in the jetsam snapshot table
+ usage: showjetsamsnapshot [-D] [-A]
+ Use -D flag to print extra physfootprint details
+ Use -A flag to print all entries (regardless of valid count)
+ """
+
+ # Not shown are uuid, user_data, cpu_time
+
+ global kern
+
+ show_footprint_details = False
+ show_all_entries = False
+
+ if "-D" in cmd_options:
+ show_footprint_details = True
+
+ if "-A" in cmd_options:
+ show_all_entries = True
+
+ valid_count = kern.globals.memorystatus_jetsam_snapshot_count
+ max_count = kern.globals.memorystatus_jetsam_snapshot_max
+
+ if (show_all_entries == True):
+ count = max_count
+ else:
+ count = valid_count
+
+ print "{:s}".format(valid_count)
+ print "{:s}".format(max_count)
+
+ if int(count) == 0:
+ print "The jetsam snapshot is empty."
+ print "Use -A to force dump all entries (regardless of valid count)"
+ return
+
+ # Dumps the snapshot header info
+ print lldb_run_command('p *memorystatus_jetsam_snapshot')
+
+ hdr_format = "{0: >32s} {1: >5s} {2: >4s} {3: >6s} {4: >6s} {5: >20s} {6: >20s} {7: >20s} {8: >5s} {9: >10s} {10: >6s} {11: >6s} {12: >10s} {13: >15s} {14: >15s} {15: >15s}"
+ if (show_footprint_details == True):
+ hdr_format += "{16: >15s} {17: >15s} {18: >12s} {19: >12s} {20: >17s} {21: >10s} {22: >13s} {23: >10s}"
+
+
+ if (show_footprint_details == False):
+ print hdr_format.format('command', 'index', 'pri', 'cid', 'pid', 'starttime', 'killtime', 'idletime', 'kill', '#ents', 'fds', 'gen', 'state', 'footprint', 'purgeable', 'lifetimeMax')
+ print hdr_format.format('', '', '', '', '', '(abs)', '(abs)', '(abs)', 'cause', '', '', 'Count', '', '(pages)', '(pages)', '(pages)')
+ else:
+ print hdr_format.format('command', 'index', 'pri', 'cid', 'pid', 'starttime', 'killtime', 'idletime', 'kill', '#ents', 'fds', 'gen', 'state', 'footprint', 'purgeable', 'lifetimeMax', '|| internal', 'internal_comp', 'iokit_mapped', 'purge_nonvol', 'purge_nonvol_comp', 'alt_acct', 'alt_acct_comp', 'page_table')
+ print hdr_format.format('', '', '', '', '', '(abs)', '(abs)', '(abs)', 'cause', '', '', 'Count', '', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)', '(pages)')
+
+
+ entry_format = "{e.name: >32s} {index: >5d} {e.priority: >4d} {e.jse_coalition_jetsam_id: >6d} {e.pid: >6d} "\
+ "{e.jse_starttime: >20d} {e.jse_killtime: >20d} "\
+ "{e.jse_idle_delta: >20d} {e.killed: >5d} {e.jse_memory_region_count: >10d} "\
+ "{e.fds: >6d} {e.jse_gencount: >6d} {e.state: >10x} {e.pages: >15d} "\
+ "{e.purgeable_pages: >15d} {e.max_pages_lifetime: >15d}"
+
+ if (show_footprint_details == True):
+ entry_format += "{e.jse_internal_pages: >15d} "\
+ "{e.jse_internal_compressed_pages: >15d} "\
+ "{e.jse_iokit_mapped_pages: >12d} "\
+ "{e.jse_purgeable_nonvolatile_pages: >12d} "\
+ "{e.jse_purgeable_nonvolatile_compressed_pages: >17d} "\
+ "{e.jse_alternate_accounting_pages: >10d} "\
+ "{e.jse_alternate_accounting_compressed_pages: >13d} "\
+ "{e.jse_page_table_pages: >10d}"
+
+ snapshot_list = kern.globals.memorystatus_jetsam_snapshot.entries
+ idx = 0
+ while idx < count:
+ current_entry = dereference(Cast(addressof(snapshot_list[idx]), 'jetsam_snapshot_entry *'))
+ print entry_format.format(index=idx, e=current_entry)
+ idx +=1
+ return
+
+# EndMacro: showjetsamsnapshot
+
+# Macro: showvnodecleanblk/showvnodedirtyblk
+
+def _GetBufSummary(buf):
+ """ Get a summary of important information out of a buf_t.
+ """
+ initial = "(struct buf) {0: <#0x} ="
+
+ # List all of the fields in this buf summary.
+ entries = [buf.b_hash, buf.b_vnbufs, buf.b_freelist, buf.b_timestamp, buf.b_whichq,
+ buf.b_flags, buf.b_lflags, buf.b_error, buf.b_bufsize, buf.b_bcount, buf.b_resid,
+ buf.b_dev, buf.b_datap, buf.b_lblkno, buf.b_blkno, buf.b_iodone, buf.b_vp,
+ buf.b_rcred, buf.b_wcred, buf.b_upl, buf.b_real_bp, buf.b_act, buf.b_drvdata,
+ buf.b_fsprivate, buf.b_transaction, buf.b_dirtyoff, buf.b_dirtyend, buf.b_validoff,
+ buf.b_validend, buf.b_redundancy_flags, buf.b_proc, buf.b_attr]
+
+ # Join an (already decent) string representation of each field
+ # with newlines and indent the region.
+ joined_strs = "\n".join([str(i).rstrip() for i in entries]).replace('\n', "\n ")
+
+ # Add the total string representation to our title and return it.
+ out_str = initial.format(int(buf)) + " {\n " + joined_strs + "\n}\n\n"
+ return out_str
+
+def _ShowVnodeBlocks(dirty=True, cmd_args=None):
+ """ Display info about all [dirty|clean] blocks in a vnode.
+ """
+ if cmd_args == None or len(cmd_args) < 1:
+ print "Please provide a valid vnode argument."
+ return
+
+ vnodeval = kern.GetValueFromAddress(cmd_args[0], 'vnode *')
+ list_head = vnodeval.v_cleanblkhd;
+ if dirty:
+ list_head = vnodeval.v_dirtyblkhd
+
+ print "Blocklist for vnode {}:".format(cmd_args[0])
+
+ i = 0
+ for buf in IterateListEntry(list_head, 'struct buf *', 'b_hash'):
+ # For each block (buf_t) in the appropriate list,
+ # ask for a summary and print it.
+ print "---->\nblock {}: ".format(i) + _GetBufSummary(buf)
+ i += 1
+ return
+
+@lldb_command('showvnodecleanblk')
+def ShowVnodeCleanBlocks(cmd_args=None):
+ """ Display info about all clean blocks in a vnode.
+ usage: showvnodecleanblk <address of vnode>
+ """
+ _ShowVnodeBlocks(False, cmd_args)
+
+@lldb_command('showvnodedirtyblk')
+def ShowVnodeDirtyBlocks(cmd_args=None):
+ """ Display info about all dirty blocks in a vnode.
+ usage: showvnodedirtyblk <address of vnode>
+ """
+ _ShowVnodeBlocks(True, cmd_args)
+
+# EndMacro: showvnodecleanblk/showvnodedirtyblk
+
+
+@lldb_command("vm_page_lookup_in_map")
+def VmPageLookupInMap(cmd_args=None):
+ """Lookup up a page at a virtual address in a VM map
+ usage: vm_page_lookup_in_map <map> <vaddr>
+ """
+ if cmd_args == None or len(cmd_args) < 2:
+ print "Invalid argument.", VmPageLookupInMap.__doc__
+ return
+ map = kern.GetValueFromAddress(cmd_args[0], 'vm_map_t')
+ vaddr = kern.GetValueFromAddress(cmd_args[1], 'vm_map_offset_t')
+ print "vaddr {:#018x} in map {: <#018x}".format(vaddr, map)
+ vm_page_lookup_in_map(map, vaddr)
+
+def vm_page_lookup_in_map(map, vaddr):
+ vaddr = unsigned(vaddr)
+ vme_list_head = map.hdr.links
+ vme_ptr_type = GetType('vm_map_entry *')
+ for vme in IterateQueue(vme_list_head, vme_ptr_type, "links"):
+ if unsigned(vme.links.start) > vaddr:
+ break
+ if unsigned(vme.links.end) <= vaddr:
+ continue
+ offset_in_vme = vaddr - unsigned(vme.links.start)
+ print " offset {:#018x} in map entry {: <#018x} [{:#018x}:{:#018x}] object {: <#018x} offset {:#018x}".format(offset_in_vme, vme, unsigned(vme.links.start), unsigned(vme.links.end), vme.vme_object.vmo_object, unsigned(vme.vme_offset) & ~0xFFF)
+ offset_in_object = offset_in_vme + (unsigned(vme.vme_offset) & ~0xFFF)
+ if vme.is_sub_map:
+ print "vaddr {:#018x} in map {: <#018x}".format(offset_in_object, vme.vme_object.vmo_submap)
+ vm_page_lookup_in_map(vme.vme_object.vmo_submap, offset_in_object)
+ else:
+ vm_page_lookup_in_object(vme.vme_object.vmo_object, offset_in_object)
+
+@lldb_command("vm_page_lookup_in_object")
+def VmPageLookupInObject(cmd_args=None):
+ """Lookup up a page at a given offset in a VM object
+ usage: vm_page_lookup_in_object <object> <offset>
+ """
+ if cmd_args == None or len(cmd_args) < 2:
+ print "Invalid argument.", VmPageLookupInObject.__doc__
+ return
+ object = kern.GetValueFromAddress(cmd_args[0], 'vm_object_t')
+ offset = kern.GetValueFromAddress(cmd_args[1], 'vm_object_offset_t')
+ print "offset {:#018x} in object {: <#018x}".format(offset, object)
+ vm_page_lookup_in_object(object, offset)
+
+def vm_page_lookup_in_object(object, offset):
+ offset = unsigned(offset)
+ page_size = kern.globals.page_size
+ trunc_offset = offset & ~(page_size - 1)
+ print " offset {:#018x} in VM object {: <#018x}".format(offset, object)
+ hash_id = _calc_vm_page_hash(object, trunc_offset)
+ page_list = kern.globals.vm_page_buckets[hash_id].page_list
+ page = _vm_page_unpack_ptr(page_list)
+ while page != 0:
+ m = kern.GetValueFromAddress(page, 'vm_page_t')
+ m_object_val = _vm_page_unpack_ptr(m.vmp_object)
+ m_object = kern.GetValueFromAddress(m_object_val, 'vm_object_t')
+ if unsigned(m_object) != unsigned(object) or unsigned(m.vmp_offset) != unsigned(trunc_offset):
+ page = _vm_page_unpack_ptr(m.vmp_next_m)
+ continue
+ print " resident page {: <#018x} phys {:#010x}".format(m, _vm_page_get_phys_page(m))
+ return
+ if object.pager and object.pager_ready:
+ offset_in_pager = trunc_offset + unsigned(object.paging_offset)
+ if not object.internal:
+ print " offset {:#018x} in external '{:s}' {: <#018x}".format(offset_in_pager, object.pager.mo_pager_ops.memory_object_pager_name, object.pager)
+ return
+ pager = Cast(object.pager, 'compressor_pager *')
+ ret = vm_page_lookup_in_compressor_pager(pager, offset_in_pager)
+ if ret:
+ return
+ if object.shadow and not object.phys_contiguous:
+ offset_in_shadow = offset + unsigned(object.vo_un2.vou_shadow_offset)
+ vm_page_lookup_in_object(object.shadow, offset_in_shadow)
+ return
+ print " page is absent and will be zero-filled on demand"
+ return
+
+@lldb_command("vm_page_lookup_in_compressor_pager")
+def VmPageLookupInCompressorPager(cmd_args=None):
+ """Lookup up a page at a given offset in a compressor pager
+ usage: vm_page_lookup_in_compressor_pager <pager> <offset>
+ """
+ if cmd_args == None or len(cmd_args) < 2:
+ print "Invalid argument.", VmPageLookupInCompressorPager.__doc__
+ return
+ pager = kern.GetValueFromAddress(cmd_args[0], 'compressor_pager_t')
+ offset = kern.GetValueFromAddress(cmd_args[1], 'memory_object_offset_t')
+ print "offset {:#018x} in compressor pager {: <#018x}".format(offset, pager)
+ vm_page_lookup_in_compressor_pager(pager, offset)
+
+def vm_page_lookup_in_compressor_pager(pager, offset):
+ offset = unsigned(offset)
+ page_size = unsigned(kern.globals.page_size)
+ page_num = unsigned(offset / page_size)
+ if page_num > pager.cpgr_num_slots:
+ print " *** ERROR: vm_page_lookup_in_compressor_pager({: <#018x},{:#018x}): page_num {:#x} > num_slots {:#x}".format(pager, offset, page_num, pager.cpgr_num_slots)
+ return 0
+ slots_per_chunk = 512 / sizeof ('compressor_slot_t')
+ num_chunks = unsigned((pager.cpgr_num_slots+slots_per_chunk-1) / slots_per_chunk)
+ if num_chunks > 1:
+ chunk_idx = unsigned(page_num / slots_per_chunk)
+ chunk = pager.cpgr_slots.cpgr_islots[chunk_idx]
+ slot_idx = unsigned(page_num % slots_per_chunk)
+ slot = GetObjectAtIndexFromArray(chunk, slot_idx)
+ slot_str = "islots[{:d}][{:d}]".format(chunk_idx, slot_idx)
+ elif pager.cpgr_num_slots > 2:
+ slot_idx = page_num
+ slot = GetObjectAtIndexFromArray(pager.cpgr_slots.cpgr_dslots, slot_idx)
+ slot_str = "dslots[{:d}]".format(slot_idx)
+ else:
+ slot_idx = page_num
+ slot = GetObjectAtIndexFromArray(pager.cpgr_slots.cpgr_eslots, slot_idx)
+ slot_str = "eslots[{:d}]".format(slot_idx)
+ print " offset {:#018x} in compressor pager {: <#018x} {:s} slot {: <#018x}".format(offset, pager, slot_str, slot)
+ if slot == 0:
+ return 0
+ slot_value = dereference(slot)
+ print " value {:#010x}".format(slot_value)
+ vm_page_lookup_in_compressor(Cast(slot, 'c_slot_mapping_t'))
+ return 1
+
+@lldb_command("vm_page_lookup_in_compressor")
+def VmPageLookupInCompressor(cmd_args=None):
+ """Lookup up a page in a given compressor slot
+ usage: vm_page_lookup_in_compressor <slot>
+ """
+ if cmd_args == None or len(cmd_args) < 1:
+ print "Invalid argument.", VmPageLookupInCompressor.__doc__
+ return
+ slot = kern.GetValueFromAddress(cmd_args[0], 'compressor_slot_t *')
+ print "compressor slot {: <#018x}".format(slot)
+ vm_page_lookup_in_compressor(slot)
+
+C_SV_CSEG_ID = ((1 << 22) - 1)
+
+def vm_page_lookup_in_compressor(slot_ptr):
+ slot_ptr = Cast(slot_ptr, 'compressor_slot_t *')
+ slot_value = dereference(slot_ptr)
+ slot = Cast(slot_value, 'c_slot_mapping')
+ print slot
+ print "compressor slot {: <#018x} -> {:#010x} cseg {:d} cindx {:d}".format(unsigned(slot_ptr), unsigned(slot_value), slot.s_cseg, slot.s_cindx)
+ if slot_ptr == 0:
+ return
+ if slot.s_cseg == C_SV_CSEG_ID:
+ sv = kern.globals.c_segment_sv_hash_table
+ print "single value[{:#d}]: ref {:d} value {:#010x}".format(slot.s_cindx, sv[slot.s_cindx].c_sv_he_un.c_sv_he.c_sv_he_ref, sv[slot.s_cindx].c_sv_he_un.c_sv_he.c_sv_he_data)
+ return
+ if slot.s_cseg == 0 or unsigned(slot.s_cseg) > unsigned(kern.globals.c_segments_available):
+ print "*** ERROR: s_cseg {:d} is out of bounds (1 - {:d})".format(slot.s_cseg, unsigned(kern.globals.c_segments_available))
+ return
+ c_segments = kern.globals.c_segments
+ c_segments_elt = GetObjectAtIndexFromArray(c_segments, slot.s_cseg-1)
+ c_seg = c_segments_elt.c_seg
+ c_no_data = 0
+ if hasattr(c_seg, 'c_state'):
+ c_state = c_seg.c_state
+ if c_state == 0:
+ c_state_str = "C_IS_EMPTY"
+ c_no_data = 1
+ elif c_state == 1:
+ c_state_str = "C_IS_FREE"
+ c_no_data = 1
+ elif c_state == 2:
+ c_state_str = "C_IS_FILLING"
+ elif c_state == 3:
+ c_state_str = "C_ON_AGE_Q"
+ elif c_state == 4:
+ c_state_str = "C_ON_SWAPOUT_Q"
+ elif c_state == 5:
+ c_state_str = "C_ON_SWAPPEDOUT_Q"
+ c_no_data = 1
+ elif c_state == 6:
+ c_state_str = "C_ON_SWAPPEDOUTSPARSE_Q"
+ c_no_data = 1
+ elif c_state == 7:
+ c_state_str = "C_ON_SWAPPEDIN_Q"
+ elif c_state == 8:
+ c_state_str = "C_ON_MAJORCOMPACT_Q"
+ elif c_state == 9:
+ c_state_str = "C_ON_BAD_Q"
+ c_no_data = 1
+ else:
+ c_state_str = "<unknown>"
+ else:
+ c_state = -1
+ c_state_str = "<no c_state field>"
+ print "c_segments[{:d}] {: <#018x} c_seg {: <#018x} c_state {:#x}={:s}".format(slot.s_cseg-1, c_segments_elt, c_seg, c_state, c_state_str)
+ c_indx = unsigned(slot.s_cindx)
+ if hasattr(c_seg, 'c_slot_var_array'):
+ c_seg_fixed_array_len = kern.globals.c_seg_fixed_array_len
+ if c_indx < c_seg_fixed_array_len:
+ cs = c_seg.c_slot_fixed_array[c_indx]
+ else:
+ cs = GetObjectAtIndexFromArray(c_seg.c_slot_var_array, c_indx - c_seg_fixed_array_len)
+ else:
+ C_SEG_SLOT_ARRAY_SIZE = 64
+ C_SEG_SLOT_ARRAY_MASK = C_SEG_SLOT_ARRAY_SIZE - 1
+ cs = GetObjectAtIndexFromArray(c_seg.c_slots[c_indx / C_SEG_SLOT_ARRAY_SIZE], c_indx & C_SEG_SLOT_ARRAY_MASK)
+ print cs
+ c_slot_unpacked_ptr = vm_unpack_ptr(cs.c_packed_ptr, kern.globals.c_slot_packing_params)
+ print "c_slot {: <#018x} c_offset {:#x} c_size {:#x} c_packed_ptr {:#x} (unpacked: {: <#018x})".format(cs, cs.c_offset, cs.c_size, cs.c_packed_ptr, unsigned(c_slot_unpacked_ptr))
+ if unsigned(slot_ptr) != unsigned(c_slot_unpacked_ptr):
+ print "*** ERROR: compressor slot {: <#018x} points back to {: <#018x} instead of itself".format(slot_ptr, c_slot_unpacked_ptr)
+ if c_no_data == 0:
+ c_data = c_seg.c_store.c_buffer + (4 * cs.c_offset)
+ c_size = cs.c_size
+ cmd = "memory read {: <#018x} {: <#018x} --force".format(c_data, c_data + c_size)
+ print cmd
+ print lldb_run_command(cmd)
+ else:
+ print "<no compressed data>"
+
+def print_hex_data(data, begin_offset=0, desc=""):
+ """ print on stdout "hexdump -C < data" like output
+ params:
+ data - bytearray or array of int where each int < 255
+ begin_offset - int offset that should be printed in left column
+ desc - str optional description to print on the first line to describe data
+ """
+ if desc:
+ print "{}:".format(desc)
+ index = 0
+ total_len = len(data)
+ hex_buf = ""
+ char_buf = ""
+ while index < total_len:
+ hex_buf += " {:02x}".format(data[index])
+ if data[index] < 0x20 or data[index] > 0x7e:
+ char_buf += "."
+ else:
+ char_buf += "{:c}".format(data[index])
+ index += 1
+ if index and index % 8 == 0:
+ hex_buf += " "
+ if index > 1 and (index % 16) == 0:
+ print "{:08x} {: <50s} |{: <16s}|".format(begin_offset + index - 16, hex_buf, char_buf)
+ hex_buf = ""
+ char_buf = ""
+ if index % 16 != 0:
+ print "{:08x} {: <50s} |{: <16s}|".format(begin_offset + index - 16, hex_buf, char_buf)
+ return
+
+@lldb_command('vm_scan_all_pages')
+def VMScanAllPages(cmd_args=None):
+ """Scans the vm_pages[] array
+ """
+ vm_pages_count = kern.globals.vm_pages_count
+ vm_pages = kern.globals.vm_pages
+
+ free_count = 0
+ local_free_count = 0
+ active_count = 0
+ local_active_count = 0
+ inactive_count = 0
+ speculative_count = 0
+ throttled_count = 0
+ wired_count = 0
+ compressor_count = 0
+ pageable_internal_count = 0
+ pageable_external_count = 0
+ secluded_count = 0
+ secluded_free_count = 0
+ secluded_inuse_count = 0
+
+ i = 0
+ while i < vm_pages_count:
+
+ if i % 10000 == 0:
+ print "{:d}/{:d}...\n".format(i,vm_pages_count)
+
+ m = vm_pages[i]
+
+ internal = 0
+ external = 0
+ m_object_val = _vm_page_unpack_ptr(m.vmp_object)
+
+ if m_object:
+ if m_object.internal:
+ internal = 1
+ else:
+ external = 1
+
+ if m.vmp_wire_count != 0 and m.vmp_local == 0:
+ wired_count = wired_count + 1
+ pageable = 0
+ elif m.vmp_throttled:
+ throttled_count = throttled_count + 1
+ pageable = 0
+ elif m.vmp_active:
+ active_count = active_count + 1
+ pageable = 1
+ elif m.vmp_local:
+ local_active_count = local_active_count + 1
+ pageable = 0
+ elif m.vmp_inactive:
+ inactive_count = inactive_count + 1
+ pageable = 1
+ elif m.vmp_speculative:
+ speculative_count = speculative_count + 1
+ pageable = 0
+ elif m.vmp_free:
+ free_count = free_count + 1
+ pageable = 0
+ elif m.vmp_secluded:
+ secluded_count = secluded_count + 1
+ if m_object == 0:
+ secluded_free_count = secluded_free_count + 1
+ else:
+ secluded_inuse_count = secluded_inuse_count + 1
+ pageable = 0
+ elif m_object == 0 and m.vmp_busy:
+ local_free_count = local_free_count + 1
+ pageable = 0
+ elif m.vmp_compressor:
+ compressor_count = compressor_count + 1
+ pageable = 0
+ else:
+ print "weird page vm_pages[{:d}]?\n".format(i)
+ pageable = 0
+
+ if pageable:
+ if internal:
+ pageable_internal_count = pageable_internal_count + 1
+ else:
+ pageable_external_count = pageable_external_count + 1
+ i = i + 1
+
+ print "vm_pages_count = {:d}\n".format(vm_pages_count)
+
+ print "wired_count = {:d}\n".format(wired_count)
+ print "throttled_count = {:d}\n".format(throttled_count)
+ print "active_count = {:d}\n".format(active_count)
+ print "local_active_count = {:d}\n".format(local_active_count)
+ print "inactive_count = {:d}\n".format(inactive_count)
+ print "speculative_count = {:d}\n".format(speculative_count)
+ print "free_count = {:d}\n".format(free_count)
+ print "local_free_count = {:d}\n".format(local_free_count)
+ print "compressor_count = {:d}\n".format(compressor_count)
+
+ print "pageable_internal_count = {:d}\n".format(pageable_internal_count)
+ print "pageable_external_count = {:d}\n".format(pageable_external_count)
+ print "secluded_count = {:d}\n".format(secluded_count)
+ print "secluded_free_count = {:d}\n".format(secluded_free_count)
+ print "secluded_inuse_count = {:d}\n".format(secluded_inuse_count)
+
+
+@lldb_command('show_all_vm_named_entries')
+def ShowAllVMNamedEntries(cmd_args=None):
+ """ Routine to print a summary listing of all the VM named entries
+ """
+ queue_len = kern.globals.vm_named_entry_count
+ queue_head = kern.globals.vm_named_entry_list
+
+ print 'vm_named_entry_list:{: <#018x} vm_named_entry_count:{:d}\n'.format(kern.GetLoadAddressForSymbol('vm_named_entry_list'),queue_len)
+
+# print '{:>6s} {:<6s} {:18s} {:1s} {:>6s} {:>16s} {:>10s} {:>10s} {:>10s} {:>3s} {:18s} {:>6s} {:<20s}\n'.format("#","#","object","P","refcnt","size (pages)","resid","wired","compressed","tag","owner","pid","process")
+ idx = 0
+ for entry in IterateQueue(queue_head, 'struct vm_named_entry *', 'named_entry_list'):
+ idx += 1
+ showmemoryentry(entry, idx, queue_len)
+
+@lldb_command('show_vm_named_entry')
+def ShowVMNamedEntry(cmd_args=None):
+ """ Routine to print a VM named entry
+ """
+ if cmd_args == None or len(cmd_args) < 1:
+ print "Invalid argument.", ShowMapVMNamedEntry.__doc__
+ return
+ named_entry = kern.GetValueFromAddress(cmd_args[0], 'vm_named_entry_t')
+ showmemoryentry(named_entry, 0, 0)
+
+def showmemoryentry(entry, idx=0, queue_len=0):
+ """ Routine to print out a summary a VM memory entry
+ params:
+ entry - core.value : a object of type 'struct vm_named_entry *'
+ returns:
+ None
+ """
+ show_pager_info = True
+ show_all_shadows = True
+
+ backing = ""
+ if entry.is_sub_map == 1:
+ backing += "SUBMAP"
+ if entry.is_copy == 1:
+ backing += "COPY"
+ if entry.is_object == 1:
+ backing += "OBJECT"
+ if entry.is_sub_map == 0 and entry.is_copy == 0 and entry.is_object == 0:
+ backing += "***?***"
+ prot=""
+ if entry.protection & 0x1:
+ prot += "r"
+ else:
+ prot += "-"
+ if entry.protection & 0x2:
+ prot += "w"
+ else:
+ prot += "-"
+ if entry.protection & 0x4:
+ prot += "x"
+ else:
+ prot += "-"
+ extra_str = ""
+ if hasattr(entry, 'named_entry_alias'):
+ extra_str += " alias={:d}".format(entry.named_entry_alias)
+ if hasattr(entry, 'named_entry_port'):
+ extra_str += " port={:#016x}".format(entry.named_entry_port)
+ print "{:d}/{:d} {: <#018x} ref={:d} prot={:d}/{:s} type={:s} backing={: <#018x} offset={:#016x} dataoffset={:#016x} size={:#016x}{:s}\n".format(idx,queue_len,entry,entry.ref_count,entry.protection,prot,backing,entry.backing.copy,entry.offset,entry.data_offset,entry.size,extra_str)
+ if entry.is_sub_map == 1:
+ showmapvme(entry.backing.map, 0, 0, show_pager_info, show_all_shadows)
+ elif entry.is_copy == 1:
+ showmapcopyvme(entry.backing.copy, 0, 0, show_pager_info, show_all_shadows, 0)
+ elif entry.is_object == 1:
+ showmapcopyvme(entry.backing.copy, 0, 0, show_pager_info, show_all_shadows, 0)
+ else:
+ print "***** UNKNOWN TYPE *****"
+ print " \n"
+
+
+def IterateRBTreeEntry2(element, element_type, field_name1, field_name2):
+ """ iterate over a rbtree as defined with RB_HEAD in libkern/tree.h
+ element - value : Value object for rbh_root
+ element_type - str : Type of the link element
+ field_name - str : Name of the field in link element's structure
+ returns:
+ A generator does not return. It is used for iterating
+ value : an object thats of type (element_type) head->sle_next. Always a pointer object
+ """
+ elt = element.__getattr__('rbh_root')
+ if type(element_type) == str:
+ element_type = gettype(element_type)
+ charp_type = gettype('char *');
+
+ # Walk to find min
+ parent = elt
+ while unsigned(elt) != 0:
+ parent = elt
+ elt = cast(elt.__getattr__(field_name1).__getattr__(field_name2).__getattr__('rbe_left'), element_type)
+ elt = parent
+
+ # Now elt is min
+ while unsigned(elt) != 0:
+ yield elt
+ # implementation cribbed from RB_NEXT in libkern/tree.h
+ right = cast(elt.__getattr__(field_name1).__getattr__(fieldname2).__getattr__('rbe_right'), element_type)
+ if unsigned(right) != 0:
+ elt = right
+ left = cast(elt.__getattr__(field_name1).__getattr__(field_name2).__getattr__('rbe_left'), element_type)
+ while unsigned(left) != 0:
+ elt = left
+ left = cast(elt.__getattr__(field_name1).__getattr(__field_name2).__getattr__('rbe_left'), element_type)
+ else:
+
+ # avoid using GetValueFromAddress
+ addr = elt.__getattr__(field_name1).__getattr__(field_name2).__getattr__('rbe_parent')&~1
+ parent = value(elt.GetSBValue().CreateValueFromExpression(None,'(void *)'+str(addr)))
+ parent = cast(parent, element_type)
+
+ if unsigned(parent) != 0:
+ left = cast(parent.__getattr__(field_name1).__getattr__(field_name2).__getattr__('rbe_left'), element_type)
+ if (unsigned(parent) != 0) and (unsigned(elt) == unsigned(left)):
+ elt = parent
+ else:
+ if unsigned(parent) != 0:
+ right = cast(parent.__getattr__(field_name1).__getattr__(field_name2).__getattr__('rbe_right'), element_type)
+ while unsigned(parent) != 0 and (unsigned(elt) == unsigned(right)):
+ elt = parent
+
+ # avoid using GetValueFromAddress
+ addr = elt.__getattr__(field_name1).__getattr__(field_name2).__getattr__('rbe_parent')&~1
+ parent = value(elt.GetSBValue().CreateValueFromExpression(None,'(void *)'+str(addr)))
+ parent = cast(parent, element_type)
+
+ right = cast(parent.__getattr__(field_name1).__getattr__(field_name2).__getattr__('rbe_right'), element_type)
+
+ # avoid using GetValueFromAddress
+ addr = elt.__getattr__(field_name1).__getattr__(field_name2).__getattr__('rbe_parent')&~1
+ elt = value(elt.GetSBValue().CreateValueFromExpression(None,'(void *)'+str(addr)))
+ elt = cast(elt, element_type)
+
+
+@lldb_command("showmaprb")
+def ShowMapRB(cmd_args=None):
+ """Routine to print out a VM map's RB tree
+ usage: showmaprb <vm_map>
+ """
+ if cmd_args == None or len(cmd_args) < 1:
+ print "Invalid argument.", ShowMapRB.__doc__
+ return
+ map_val = kern.GetValueFromAddress(cmd_args[0], 'vm_map_t')
+ print GetVMMapSummary.header
+ print GetVMMapSummary(map_val)
+ vme_rb_root = map_val.hdr.rb_head_store
+ vme_ptr_type = GetType('struct vm_map_entry *')
+ print GetVMEntrySummary.header
+ for vme in IterateRBTreeEntry2(vme_rb_root, 'struct vm_map_entry *', 'store', 'entry'):
+ print GetVMEntrySummary(vme)
+ return None
+
+@lldb_command('show_all_owned_objects', 'T')
+def ShowAllOwnedObjects(cmd_args=None, cmd_options={}):
+ """ Routine to print the list of VM objects owned by each task
+ -T: show only ledger-tagged objects
+ """
+ showonlytagged = False
+ if "-T" in cmd_options:
+ showonlytagged = True
+ for task in kern.tasks:
+ ShowTaskOwnedVmObjects(task, showonlytagged)
+
+@lldb_command('show_task_owned_objects', 'T')
+def ShowTaskOwnedObjects(cmd_args=None, cmd_options={}):
+ """ Routine to print the list of VM objects owned by the specified task
+ -T: show only ledger-tagged objects
+ """
+ showonlytagged = False
+ if "-T" in cmd_options:
+ showonlytagged = True
+ task = kern.GetValueFromAddress(cmd_args[0], 'task *')
+ ShowTaskOwnedVmObjects(task, showonlytagged)
+
+@lldb_command('showdeviceinfo', 'J')
+def ShowDeviceInfo(cmd_args=None, cmd_options={}):
+ """ Routine to show basic device information (model, build, ncpus, etc...)
+ Usage: memstats [-J]
+ -J : Output json
+ """
+ print_json = False
+ if "-J" in cmd_options:
+ print_json = True
+ device_info = {}
+ device_info["build"] = str(kern.globals.osversion)
+ device_info["memoryConfig"] = int(kern.globals.max_mem_actual)
+ device_info["ncpu"] = int(kern.globals.ncpu)
+ device_info["pagesize"] = int(kern.globals.page_size)
+ device_info["mlockLimit"] = long(kern.globals.vm_global_user_wire_limit)
+
+
+ if print_json:
+ print json.dumps(device_info)
+ else:
+ PrettyPrintDictionary(device_info)
+
+def ShowTaskOwnedVmObjects(task, showonlytagged=False):
+ """ Routine to print out a summary listing of all the entries in a vm_map
+ params:
+ task - core.value : a object of type 'task *'
+ returns:
+ None
+ """
+ taskobjq_total = lambda:None
+ taskobjq_total.objects = 0
+ taskobjq_total.vsize = 0
+ taskobjq_total.rsize = 0
+ taskobjq_total.wsize = 0
+ taskobjq_total.csize = 0
+ vmo_list_head = task.task_objq
+ vmo_ptr_type = GetType('vm_object *')
+ idx = 0
+ for vmo in IterateQueue(vmo_list_head, vmo_ptr_type, "task_objq"):
+ idx += 1
+ if not showonlytagged or vmo.vo_ledger_tag != 0:
+ if taskobjq_total.objects == 0:
+ print ' \n'
+ print GetTaskSummary.header + ' ' + GetProcSummary.header
+ print GetTaskSummary(task) + ' ' + GetProcSummary(Cast(task.bsd_info, 'proc *'))
+ print '{:>6s} {:<6s} {:18s} {:1s} {:>6s} {:>16s} {:>10s} {:>10s} {:>10s} {:>2s} {:18s} {:>6s} {:<20s}\n'.format("#","#","object","P","refcnt","size (pages)","resid","wired","compressed","tg","owner","pid","process")
+ ShowOwnedVmObject(vmo, idx, 0, taskobjq_total)
+ if taskobjq_total.objects != 0:
+ print " total:{:<10d} [ virtual:{:<10d} resident:{:<10d} wired:{:<10d} compressed:{:<10d} ]\n".format(taskobjq_total.objects, taskobjq_total.vsize, taskobjq_total.rsize, taskobjq_total.wsize, taskobjq_total.csize)
+ return None
+
+def ShowOwnedVmObject(object, idx, queue_len, taskobjq_total):
+ """ Routine to print out a VM object owned by a task
+ params:
+ object - core.value : a object of type 'struct vm_object *'
+ returns:
+ None
+ """
+ page_size = kern.globals.page_size
+ if object.purgable == 0:
+ purgable = "N"
+ elif object.purgable == 1:
+ purgable = "V"
+ elif object.purgable == 2:
+ purgable = "E"
+ elif object.purgable == 3:
+ purgable = "D"
+ else:
+ purgable = "?"
+ if object.pager == 0:
+ compressed_count = 0
+ else:
+ compressor_pager = Cast(object.pager, 'compressor_pager *')
+ compressed_count = compressor_pager.cpgr_num_slots_occupied
+
+ print "{:>6d}/{:<6d} {: <#018x} {:1s} {:>6d} {:>16d} {:>10d} {:>10d} {:>10d} {:>2d} {: <#018x} {:>6d} {:<20s}\n".format(idx,queue_len,object,purgable,object.ref_count,object.vo_un1.vou_size/page_size,object.resident_page_count,object.wired_page_count,compressed_count, object.vo_ledger_tag, object.vo_un2.vou_owner,GetProcPIDForObjectOwner(object.vo_un2.vou_owner),GetProcNameForObjectOwner(object.vo_un2.vou_owner))
+
+ taskobjq_total.objects += 1
+ taskobjq_total.vsize += object.vo_un1.vou_size/page_size
+ taskobjq_total.rsize += object.resident_page_count
+ taskobjq_total.wsize += object.wired_page_count
+ taskobjq_total.csize += compressed_count
+
+def GetProcPIDForObjectOwner(owner):
+ """ same as GetProcPIDForTask() but deals with -1 for a disowned object
+ """
+ if unsigned(Cast(owner, 'int')) == unsigned(int(0xffffffff)):
+ return -1
+ return GetProcPIDForTask(owner)
+
+def GetProcNameForObjectOwner(owner):
+ """ same as GetProcNameForTask() but deals with -1 for a disowned object
+ """
+ if unsigned(Cast(owner, 'int')) == unsigned(int(0xffffffff)):
+ return "<disowned>"
+ return GetProcNameForTask(owner)
+
+def GetDescForNamedEntry(mem_entry):
+ out_str = "\n"
+ out_str += "\t\tmem_entry {:#08x} ref:{:d} offset:{:#08x} size:{:#08x} prot{:d} backing {:#08x}".format(mem_entry, mem_entry.ref_count, mem_entry.offset, mem_entry.size, mem_entry.protection, mem_entry.backing.copy)
+ if mem_entry.is_sub_map:
+ out_str += " is_sub_map"
+ elif mem_entry.is_copy:
+ out_str += " is_copy"
+ elif mem_entry.is_object:
+ out_str += " is_object"
+ else:
+ out_str += " ???"
+ return out_str