xref: /xnu-10002.1.13/tools/lldbmacros/zonetriage.py (revision 1031c584a5e37aff177559b9f69dbd3c8c3fd30a)
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        btlog = getattr(zval, 'z_btlog', None)
87        if btlog:
88            print('%s:' % zval.z_name)
89            print("(lldb) zstack_findtop -N 5 0x%lx" % btlog)
90            print(lldb_run_command("zstack_findtop -N 5 0x%lx" % btlog))
91            print("(lldb) zstack_findleak 0x%lx" % btlog)
92            print(lldb_run_command("zstack_findleak 0x%lx" % btlog))
93
94def CheckZoneBootArgs(cmd_args=None):
95    """ Check boot args to see if zone is being logged, if not, suggest new boot args
96    """
97    global panic_string
98    if panic_string is None:
99        try:
100            panic_string = lldb_run_command("paniclog").split('\n', 1)[0]
101        except:
102            return
103    panic_string_regex = ""
104    if re.match(zone_element_modified, panic_string) is not None:
105        panic_string_regex = zone_element_modified
106    if re.match(zone_map_exhausted, panic_string) is not None:
107        panic_string_regex = zone_map_exhausted
108    values = re.search(panic_string_regex, panic_string)
109    if values is None or 'zone' not in values.group():
110        return
111    zone = values.group('zone')
112    bootargs = lldb_run_command("showbootargs")
113    correct_boot_args = re.search('zlog([1-9]|10)?=' + re.sub(' ', '.', zone), bootargs)
114    if correct_boot_args is None:
115        print("Current boot-args:\n" + bootargs)
116        print("You may need to include: -zc -zp zlog([1-9]|10)?=" + re.sub(' ', '.', zone))
117
118def FindZoneBTLog(zone):
119    """ Returns the btlog address in the format 0x%lx for the zone name passed as a parameter
120    """
121    global kern
122    for zval, _ in kern.zones:
123        btlog = getattr(zval, 'z_btlog', None)
124        if btlog:
125            if zone == "%s" % zval.z_name:
126                return "0x%lx" % btlog
127    return None
128# EndMacro: zonetriage, zonetriage_freedelement, zonetriage_memoryleak
129