]>
Commit | Line | Data |
---|---|---|
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 | """ | |
18 | from xnu import * | |
19 | import sys, shlex | |
20 | from utils import * | |
21 | import xnudefines | |
22 | import re | |
23 | import os.path | |
24 | ||
25 | ## Globals | |
26 | panic_string = None | |
27 | ## If the following panic strings are modified in xnu/osfmk/kern/zalloc.c, they must be updated here to reflect the change. | |
28 | zone_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]*)?.*" | |
29 | zone_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\).*" | |
30 | ||
31 | # Macro: zonetriage, zonetriage_freedelement, zonetriage_memoryleak | |
32 | @lldb_command('zonetriage') | |
33 | def ZoneTriage(cmd_args=None): | |
34 | """ Calls function specific to type of zone panic based on the panic string | |
35 | """ | |
36 | global panic_string | |
37 | if panic_string is None: | |
38 | try: | |
39 | panic_string = lldb_run_command("paniclog").split('\n', 1)[0] | |
40 | except: | |
41 | return | |
42 | if re.match(zone_element_modified, panic_string) is not None: | |
43 | ZoneTriageFreedElement() | |
44 | elif re.match(zone_map_exhausted, panic_string) is not None: | |
45 | ZoneTriageMemoryLeak() | |
46 | else: | |
47 | print "zonetriage does not currently support this panic string." | |
48 | ||
49 | @lldb_command('zonetriage_freedelement') | |
50 | def ZoneTriageFreedElement(cmd_args=None): | |
51 | """ Runs zstack_findelem on the element and zone being logged based on the panic string regex | |
52 | """ | |
53 | global panic_string | |
54 | if panic_string is None: | |
55 | try: | |
56 | panic_string = lldb_run_command("paniclog").split('\n', 1)[0] | |
57 | except: | |
58 | return | |
59 | CheckZoneBootArgs() | |
60 | ## Run showzonesbeinglogged. | |
61 | print "(lldb) zstack_showzonesbeinglogged\n%s\n" % lldb_run_command("zstack_showzonesbeinglogged") | |
62 | ## Capture zone and element from panic string. | |
63 | values = re.search(zone_element_modified, panic_string) | |
64 | if values is None or 'zone' not in values.group() or 'element' not in values.group(): | |
65 | return | |
66 | element = values.group('element') | |
67 | zone = values.group('zone') | |
68 | btlog = FindZoneBTLog(zone) | |
69 | if btlog is not None: | |
70 | print "(lldb) zstack_findelem " + btlog + " " + element | |
71 | findelem_output = lldb_run_command("zstack_findelem " + btlog + " " + element) | |
72 | findelem_output = re.sub('Scanning is ongoing. [0-9]* items scanned since last check.\n', '', findelem_output) | |
73 | print findelem_output | |
74 | ||
75 | @lldb_command('zonetriage_memoryleak') | |
76 | def ZoneTriageMemoryLeak(cmd_args=None): | |
77 | """ Runs zstack_findtop and zstack_findleak on all zones being logged | |
78 | """ | |
79 | global kern | |
80 | CheckZoneBootArgs() | |
81 | ## Run showzonesbeinglogged. | |
82 | print "(lldb) zstack_showzonesbeinglogged\n%s\n" % lldb_run_command("zstack_showzonesbeinglogged") | |
83 | for zval in kern.zones: | |
84 | if zval.zlog_btlog: | |
85 | print '%s:' % zval.zone_name | |
86 | print "(lldb) zstack_findtop -N 5 0x%lx" % zval.zlog_btlog | |
87 | print lldb_run_command("zstack_findtop -N 5 0x%lx" % zval.zlog_btlog) | |
88 | print "(lldb) zstack_findleak 0x%lx" % zval.zlog_btlog | |
89 | print lldb_run_command("zstack_findleak 0x%lx" % zval.zlog_btlog) | |
90 | ||
91 | def CheckZoneBootArgs(cmd_args=None): | |
92 | """ Check boot args to see if zone is being logged, if not, suggest new boot args | |
93 | """ | |
94 | global panic_string | |
95 | if panic_string is None: | |
96 | try: | |
97 | panic_string = lldb_run_command("paniclog").split('\n', 1)[0] | |
98 | except: | |
99 | return | |
100 | panic_string_regex = "" | |
101 | if re.match(zone_element_modified, panic_string) is not None: | |
102 | panic_string_regex = zone_element_modified | |
103 | if re.match(zone_map_exhausted, panic_string) is not None: | |
104 | panic_string_regex = zone_map_exhausted | |
105 | values = re.search(panic_string_regex, panic_string) | |
106 | if values is None or 'zone' not in values.group(): | |
107 | return | |
108 | zone = values.group('zone') | |
109 | bootargs = lldb_run_command("showbootargs") | |
110 | correct_boot_args = re.search('zlog([1-9]|10)?=' + re.sub(' ', '.', zone), bootargs) | |
111 | if correct_boot_args is None: | |
112 | print "Current boot-args:\n" + bootargs | |
113 | print "You may need to include: -zc -zp zlog([1-9]|10)?=" + re.sub(' ', '.', zone) | |
114 | ||
115 | def FindZoneBTLog(zone): | |
116 | """ Returns the btlog address in the format 0x%lx for the zone name passed as a parameter | |
117 | """ | |
118 | global kern | |
119 | for zval in kern.zones: | |
120 | if zval.zlog_btlog: | |
121 | if zone == "%s" % zval.zone_name: | |
122 | return "0x%lx" % zval.zlog_btlog | |
123 | return None | |
124 | # EndMacro: zonetriage, zonetriage_freedelement, zonetriage_memoryleak |