1""" 2 Triage Macros for zone related panics 3 4 Supported panic strings from xnu/osfmk/kern/zalloc.c: 5 "a freed zone element has been modified in zone %s: expected %p but found %p, bits changed %p, at offset %d of %d in element %p, cookies %p %p" and 6 "zalloc: zone map exhausted while allocating from zone %s, likely due to memory leak in zone %s (%lu total bytes, %d elements allocated)" 7 These macros are dependant on the above panic strings. If the strings are modified in any way, this script must be updated to reflect the change. 8 9 To support more zone panic strings: 10 1. Add the panic string regex to the globals and include in the named capture group 'zone' (the zone to be 11 logged) as well as any other info necessary to parse out of the panic string. 12 2. Add a check for the panic string regex in ZoneTriage(), which then calls into the function you create. 13 3. Add a check for the panic string regex in CheckZoneBootArgs() which sets the variable panic_string_regex to your 14 panic string regex if found. 15 4. Create a function that can be called either through the zonetriage macro ZoneTriage() or using its own macro. 16 This function should handle all lldb commands you want to run for this type of zone panic. 17""" 18from __future__ import absolute_import, print_function 19 20from xnu import * 21import sys, shlex 22from utils import * 23import xnudefines 24import re 25import os.path 26 27## Globals 28panic_string = None 29## If the following panic strings are modified in xnu/osfmk/kern/zalloc.c, they must be updated here to reflect the change. 30zone_element_modified = ".*a freed zone element has been modified in zone (?P<zone>.+): expected (0x)?([0-9A-Fa-f]*)? but found (0x)?([0-9A-Fa-f]*)?, bits changed (0x)?([0-9A-Fa-f]*)?, at offset ([0-9]*)? of ([0-9]*)? in element (?P<element>0x[0-9A-Fa-f]*), cookies (0x)?([0-9A-Fa-f]*)? (0x)?([0-9A-Fa-f]*)?.*" 31zone_map_exhausted = ".*zalloc: zone map exhausted while allocating from zone .+, likely due to memory leak in zone (?P<zone>.+) \(([0-9]*)? total bytes, ([0-9]*)? elements allocated\).*" 32 33# Macro: zonetriage, zonetriage_freedelement, zonetriage_memoryleak 34@lldb_command('zonetriage') 35def ZoneTriage(cmd_args=None): 36 """ Calls function specific to type of zone panic based on the panic string 37 """ 38 global panic_string 39 if panic_string is None: 40 try: 41 panic_string = lldb_run_command("paniclog").split('\n', 1)[0] 42 except: 43 return 44 if re.match(zone_element_modified, panic_string) is not None: 45 ZoneTriageFreedElement() 46 elif re.match(zone_map_exhausted, panic_string) is not None: 47 ZoneTriageMemoryLeak() 48 else: 49 print("zonetriage does not currently support this panic string.") 50 51@lldb_command('zonetriage_freedelement') 52def ZoneTriageFreedElement(cmd_args=None): 53 """ Runs zstack_findelem on the element and zone being logged based on the panic string regex 54 """ 55 global panic_string 56 if panic_string is None: 57 try: 58 panic_string = lldb_run_command("paniclog").split('\n', 1)[0] 59 except: 60 return 61 CheckZoneBootArgs() 62 ## Run showzonesbeinglogged. 63 print("(lldb) zstack_showzonesbeinglogged\n%s\n" % lldb_run_command("zstack_showzonesbeinglogged")) 64 ## Capture zone and element from panic string. 65 values = re.search(zone_element_modified, panic_string) 66 if values is None or 'zone' not in values.group() or 'element' not in values.group(): 67 return 68 element = values.group('element') 69 zone = values.group('zone') 70 btlog = FindZoneBTLog(zone) 71 if btlog is not None: 72 print("(lldb) zstack_findelem " + btlog + " " + element) 73 findelem_output = lldb_run_command("zstack_findelem " + btlog + " " + element) 74 findelem_output = re.sub('Scanning is ongoing. [0-9]* items scanned since last check.\n', '', findelem_output) 75 print(findelem_output) 76 77@lldb_command('zonetriage_memoryleak') 78def ZoneTriageMemoryLeak(cmd_args=None): 79 """ Runs zstack_findtop and zstack_findleak on all zones being logged 80 """ 81 global kern 82 CheckZoneBootArgs() 83 ## Run showzonesbeinglogged. 84 print("(lldb) zstack_showzonesbeinglogged\n%s\n" % lldb_run_command("zstack_showzonesbeinglogged")) 85 for zval, _ in kern.zones: 86 if zval.z_btlog: 87 print('%s:' % zval.z_name) 88 print("(lldb) zstack_findtop -N 5 0x%lx" % zval.z_btlog) 89 print(lldb_run_command("zstack_findtop -N 5 0x%lx" % zval.z_btlog)) 90 print("(lldb) zstack_findleak 0x%lx" % zval.z_btlog) 91 print(lldb_run_command("zstack_findleak 0x%lx" % zval.z_btlog)) 92 93def CheckZoneBootArgs(cmd_args=None): 94 """ Check boot args to see if zone is being logged, if not, suggest new boot args 95 """ 96 global panic_string 97 if panic_string is None: 98 try: 99 panic_string = lldb_run_command("paniclog").split('\n', 1)[0] 100 except: 101 return 102 panic_string_regex = "" 103 if re.match(zone_element_modified, panic_string) is not None: 104 panic_string_regex = zone_element_modified 105 if re.match(zone_map_exhausted, panic_string) is not None: 106 panic_string_regex = zone_map_exhausted 107 values = re.search(panic_string_regex, panic_string) 108 if values is None or 'zone' not in values.group(): 109 return 110 zone = values.group('zone') 111 bootargs = lldb_run_command("showbootargs") 112 correct_boot_args = re.search('zlog([1-9]|10)?=' + re.sub(' ', '.', zone), bootargs) 113 if correct_boot_args is None: 114 print("Current boot-args:\n" + bootargs) 115 print("You may need to include: -zc -zp zlog([1-9]|10)?=" + re.sub(' ', '.', zone)) 116 117def FindZoneBTLog(zone): 118 """ Returns the btlog address in the format 0x%lx for the zone name passed as a parameter 119 """ 120 global kern 121 for zval, _ in kern.zones: 122 if zval.z_btlog: 123 if zone == "%s" % zval.z_name: 124 return "0x%lx" % zval.z_btlog 125 return None 126# EndMacro: zonetriage, zonetriage_freedelement, zonetriage_memoryleak 127