]>
Commit | Line | Data |
---|---|---|
5ba3f43e | 1 | import sys, subprocess, os, re, time, getopt, shlex, xnudefines |
39236c6e A |
2 | import lldb |
3 | from functools import wraps | |
4 | from ctypes import c_ulonglong as uint64_t | |
5 | from ctypes import c_void_p as voidptr_t | |
6 | import code | |
7 | import core | |
8 | from core import caching | |
9 | from core.standard import * | |
10 | from core.configuration import * | |
11 | from core.kernelcore import * | |
12 | from utils import * | |
13 | from core.lazytarget import * | |
14 | ||
5ba3f43e | 15 | MODULE_NAME=__name__ |
39236c6e A |
16 | |
17 | """ Kernel Debugging macros for lldb. | |
18 | Please make sure you read the README COMPLETELY BEFORE reading anything below. | |
5ba3f43e | 19 | It is very critical that you read coding guidelines in Section E in README file. |
39236c6e A |
20 | """ |
21 | ||
5ba3f43e A |
22 | COMMON_HELP_STRING = """ |
23 | -h Show the help string for the command. | |
cb323159 A |
24 | -c [always|auto|never|0|1] |
25 | Control the colorized output of certain commands | |
5ba3f43e A |
26 | -o <path/to/filename> The output of this command execution will be saved to file. Parser information or errors will |
27 | not be sent to file though. eg /tmp/output.txt | |
28 | -s <filter_string> The "filter_string" param is parsed to python regex expression and each line of output | |
29 | will be printed/saved only if it matches the expression. | |
30 | -v [-v...] Each additional -v will increase the verbosity of the command. | |
31 | -p <plugin_name> Send the output of the command to plugin. Please see README for usage of plugins. | |
32 | """ | |
39236c6e | 33 | # End Utility functions |
5ba3f43e | 34 | # Debugging specific utility functions |
39236c6e A |
35 | |
36 | #decorators. Not to be called directly. | |
37 | ||
38 | def static_var(var_name, initial_value): | |
39 | def _set_var(obj): | |
40 | setattr(obj, var_name, initial_value) | |
41 | return obj | |
42 | return _set_var | |
43 | ||
44 | def header(initial_value): | |
45 | def _set_header(obj): | |
46 | setattr(obj, 'header', initial_value) | |
47 | return obj | |
48 | return _set_header | |
49 | ||
cb323159 | 50 | # holds type declarations done by xnu. |
39236c6e | 51 | #DONOTTOUCHME: Exclusive use of lldb_type_summary only. |
cb323159 | 52 | lldb_summary_definitions = {} |
39236c6e | 53 | def lldb_type_summary(types_list): |
cb323159 | 54 | """ A function decorator to register a summary for a type in lldb. |
39236c6e A |
55 | params: types_list - [] an array of types that you wish to register a summary callback function. (ex. ['task *', 'task_t']) |
56 | returns: Nothing. This is a decorator. | |
57 | """ | |
58 | def _get_summary(obj): | |
59 | def _internal_summary_function(lldbval, internal_dict): | |
60 | out_string= "" | |
61 | if internal_dict != None and len(obj.header) > 0 : | |
62 | out_string += "\n" + obj.header +"\n" | |
63 | out_string += obj( core.value(lldbval) ) | |
64 | return out_string | |
cb323159 | 65 | |
39236c6e A |
66 | myglobals = globals() |
67 | summary_function_name = "LLDBSummary" + obj.__name__ | |
68 | myglobals[summary_function_name] = _internal_summary_function | |
69 | summary_function = myglobals[summary_function_name] | |
70 | summary_function.__doc__ = obj.__doc__ | |
cb323159 | 71 | |
39236c6e A |
72 | global lldb_summary_definitions |
73 | for single_type in types_list: | |
74 | if config['showTypeSummary']: | |
75 | if single_type in lldb_summary_definitions.keys(): | |
76 | lldb.debugger.HandleCommand("type summary delete --category kernel \""+ single_type + "\"") | |
77 | lldb.debugger.HandleCommand("type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + "." + summary_function_name) | |
78 | lldb_summary_definitions[single_type] = obj | |
cb323159 | 79 | |
39236c6e A |
80 | return obj |
81 | return _get_summary | |
82 | ||
cb323159 | 83 | #global cache of documentation for lldb commands exported by this module |
39236c6e A |
84 | #DONOTTOUCHME: Exclusive use of lldb_command only. |
85 | lldb_command_documentation = {} | |
86 | ||
cb323159 | 87 | def lldb_command(cmd_name, option_string = '', fancy=False): |
39236c6e A |
88 | """ A function decorator to define a command with namd 'cmd_name' in the lldb scope to call python function. |
89 | params: cmd_name - str : name of command to be set in lldb prompt. | |
cb323159 | 90 | option_string - str: getopt like option string. Only CAPITAL LETTER options allowed. |
39236c6e | 91 | see README on Customizing command options. |
cb323159 | 92 | fancy - bool : whether the command will receive an 'O' object to do fancy output (tables, indent, color) |
39236c6e A |
93 | """ |
94 | if option_string != option_string.upper(): | |
95 | raise RuntimeError("Cannot setup command with lowercase option args. %s" % option_string) | |
96 | ||
97 | def _cmd(obj): | |
98 | def _internal_command_function(debugger, command, result, internal_dict): | |
99 | global config, lldb_run_command_state | |
813fb2f6 | 100 | stream = CommandOutput(cmd_name, result) |
39236c6e A |
101 | # need to avoid printing on stdout if called from lldb_run_command. |
102 | if 'active' in lldb_run_command_state and lldb_run_command_state['active']: | |
103 | debuglog('Running %s from lldb_run_command' % command) | |
104 | else: | |
105 | result.SetImmediateOutputFile(sys.__stdout__) | |
106 | ||
107 | command_args = shlex.split(command) | |
108 | lldb.debugger.HandleCommand('type category disable kernel' ) | |
109 | def_verbose_level = config['verbosity'] | |
cb323159 | 110 | |
39236c6e A |
111 | try: |
112 | stream.setOptions(command_args, option_string) | |
113 | if stream.verbose_level != 0: | |
cb323159 | 114 | config['verbosity'] += stream.verbose_level |
39236c6e | 115 | with RedirectStdStreams(stdout=stream) : |
cb323159 | 116 | args = { 'cmd_args': stream.target_cmd_args } |
39236c6e | 117 | if option_string: |
cb323159 A |
118 | args['cmd_options'] = stream.target_cmd_options |
119 | if fancy: | |
120 | args['O'] = stream | |
121 | obj(**args) | |
39236c6e A |
122 | except KeyboardInterrupt: |
123 | print "Execution interrupted by user" | |
124 | except ArgumentError as arg_error: | |
125 | if str(arg_error) != "HELP": | |
126 | print "Argument Error: " + str(arg_error) | |
127 | print "{0:s}:\n {1:s}".format(cmd_name, obj.__doc__.strip()) | |
128 | return False | |
129 | except Exception as exc: | |
130 | if not config['debug']: | |
131 | print """ | |
132 | ************ LLDB found an exception ************ | |
133 | There has been an uncaught exception. A possible cause could be that remote connection has been disconnected. | |
134 | However, it is recommended that you report the exception to lldb/kernel debugging team about it. | |
135 | ************ Please run 'xnudebug debug enable' to start collecting logs. ************ | |
136 | """ | |
137 | raise | |
138 | ||
139 | if config['showTypeSummary']: | |
140 | lldb.debugger.HandleCommand('type category enable kernel' ) | |
cb323159 | 141 | |
39236c6e A |
142 | if stream.pluginRequired : |
143 | plugin = LoadXNUPlugin(stream.pluginName) | |
144 | if plugin == None : | |
145 | print "Could not load plugins."+stream.pluginName | |
146 | return | |
147 | plugin.plugin_init(kern, config, lldb, kern.IsDebuggerConnected()) | |
148 | return_data = plugin.plugin_execute(cmd_name, result.GetOutput()) | |
149 | ProcessXNUPluginResult(return_data) | |
150 | plugin.plugin_cleanup() | |
cb323159 | 151 | |
39236c6e A |
152 | #restore the verbose level after command is complete |
153 | config['verbosity'] = def_verbose_level | |
cb323159 | 154 | |
39236c6e A |
155 | return |
156 | ||
157 | myglobals = globals() | |
158 | command_function_name = obj.__name__+"Command" | |
159 | myglobals[command_function_name] = _internal_command_function | |
160 | command_function = myglobals[command_function_name] | |
161 | if not obj.__doc__ : | |
162 | print "ERROR: Cannot register command({:s}) without documentation".format(cmd_name) | |
163 | return obj | |
5ba3f43e | 164 | obj.__doc__ += "\n" + COMMON_HELP_STRING |
39236c6e A |
165 | command_function.__doc__ = obj.__doc__ |
166 | global lldb_command_documentation | |
167 | if cmd_name in lldb_command_documentation: | |
168 | lldb.debugger.HandleCommand("command script delete "+cmd_name) | |
169 | lldb_command_documentation[cmd_name] = (obj.__name__, obj.__doc__.lstrip(), option_string) | |
170 | lldb.debugger.HandleCommand("command script add -f " + MODULE_NAME + "." + command_function_name + " " + cmd_name) | |
cb323159 A |
171 | |
172 | if fancy: | |
173 | def wrapped_fun(cmd_args=None, cmd_options={}, O=None): | |
174 | if O is None: | |
175 | stream = CommandOutput(cmd_name, fhandle=sys.stdout) | |
176 | with RedirectStdStreams(stdout=stream): | |
177 | return obj(cmd_args, cmd_options, stream) | |
178 | else: | |
179 | return obj(cmd_args, cmd_options, O) | |
180 | return wrapped_fun | |
39236c6e A |
181 | return obj |
182 | return _cmd | |
183 | ||
184 | def lldb_alias(alias_name, cmd_line): | |
cb323159 | 185 | """ define an alias in the lldb command line. |
39236c6e A |
186 | A programatic way of registering an alias. This basically does |
187 | (lldb)command alias alias_name "cmd_line" | |
cb323159 | 188 | ex. |
39236c6e A |
189 | lldb_alias('readphys16', 'readphys 16') |
190 | """ | |
191 | alias_name = alias_name.strip() | |
192 | cmd_line = cmd_line.strip() | |
193 | lldb.debugger.HandleCommand("command alias " + alias_name + " "+ cmd_line) | |
194 | ||
195 | def SetupLLDBTypeSummaries(reset=False): | |
196 | global lldb_summary_definitions, MODULE_NAME | |
197 | if reset == True: | |
198 | lldb.debugger.HandleCommand("type category delete kernel ") | |
199 | for single_type in lldb_summary_definitions.keys(): | |
200 | summary_function = lldb_summary_definitions[single_type] | |
201 | lldb_cmd = "type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + ".LLDBSummary" + summary_function.__name__ | |
202 | debuglog(lldb_cmd) | |
203 | lldb.debugger.HandleCommand(lldb_cmd) | |
204 | if config['showTypeSummary']: | |
205 | lldb.debugger.HandleCommand("type category enable kernel") | |
206 | else: | |
207 | lldb.debugger.HandleCommand("type category disable kernel") | |
208 | ||
209 | return | |
210 | ||
211 | def LoadXNUPlugin(name): | |
cb323159 | 212 | """ Try to load a plugin from the plugins directory. |
39236c6e A |
213 | """ |
214 | retval = None | |
215 | name=name.strip() | |
216 | try: | |
217 | module_obj = __import__('plugins.'+name, globals(), locals(), [], -1) | |
218 | module_obj = module_obj.__dict__[name] | |
219 | defs = dir(module_obj) | |
220 | if 'plugin_init' in defs and 'plugin_execute' in defs and 'plugin_cleanup' in defs: | |
221 | retval = module_obj | |
222 | else: | |
223 | print "Plugin is not correctly implemented. Please read documentation on implementing plugins" | |
224 | except: | |
225 | print "plugin not found :"+name | |
cb323159 | 226 | |
39236c6e A |
227 | return retval |
228 | ||
229 | def ProcessXNUPluginResult(result_data): | |
230 | """ Look at the returned data from plugin and see if anymore actions are required or not | |
231 | params: result_data - list of format (status, out_string, more_commands) | |
232 | """ | |
233 | ret_status = result_data[0] | |
234 | ret_string = result_data[1] | |
235 | ret_commands = result_data[2] | |
cb323159 | 236 | |
39236c6e A |
237 | if ret_status == False: |
238 | print "Plugin failed: " + ret_string | |
239 | return | |
240 | print ret_string | |
241 | if len(ret_commands) >= 0: | |
242 | for cmd in ret_commands: | |
243 | print "Running command on behalf of plugin:" + cmd | |
244 | lldb.debugger.HandleCommand(cmd) | |
245 | return | |
246 | ||
247 | # holds tests registered with xnu. | |
248 | #DONOTTOUCHME: Exclusive use of xnudebug_test only | |
249 | lldb_command_tests = {} | |
250 | def xnudebug_test(test_name): | |
cb323159 | 251 | """ A function decoratore to register a test with the framework. Each test is supposed to be of format |
39236c6e | 252 | def Test<name>(kernel_target, config, lldb_obj, isConnected ) |
cb323159 | 253 | |
39236c6e A |
254 | NOTE: The testname should start with "Test" else exception will be raised. |
255 | """ | |
256 | def _test(obj): | |
257 | global lldb_command_tests | |
258 | if obj.__name__.find("Test") != 0 : | |
cb323159 | 259 | print "Test name ", obj.__name__ , " should start with Test" |
39236c6e A |
260 | raise ValueError |
261 | lldb_command_tests[test_name] = (test_name, obj.__name__, obj, obj.__doc__) | |
262 | return obj | |
263 | return _test | |
264 | ||
265 | ||
266 | # End Debugging specific utility functions | |
cb323159 | 267 | # Kernel Debugging specific classes and accessor methods |
39236c6e A |
268 | |
269 | # global access object for target kernel | |
270 | ||
271 | def GetObjectAtIndexFromArray(array_base, index): | |
272 | """ Subscript indexing for arrays that are represented in C as pointers. | |
273 | for ex. int *arr = malloc(20*sizeof(int)); | |
cb323159 | 274 | now to get 3rd int from 'arr' you'd do |
39236c6e A |
275 | arr[2] in C |
276 | GetObjectAtIndexFromArray(arr_val,2) | |
277 | params: | |
278 | array_base : core.value - representing a pointer type (ex. base of type 'ipc_entry *') | |
279 | index : int - 0 based index into the array | |
280 | returns: | |
281 | core.value : core.value of the same type as array_base_val but pointing to index'th element | |
282 | """ | |
283 | array_base_val = array_base.GetSBValue() | |
284 | base_address = array_base_val.GetValueAsUnsigned() | |
285 | size = array_base_val.GetType().GetPointeeType().GetByteSize() | |
286 | obj_address = base_address + (index * size) | |
c3c9b80d | 287 | obj = kern.GetValueFromAddress(obj_address, array_base_val.GetType()) |
39236c6e A |
288 | return Cast(obj, array_base_val.GetType()) |
289 | ||
290 | ||
291 | kern = None | |
292 | ||
293 | def GetLLDBThreadForKernelThread(thread_obj): | |
294 | """ Get a reference to lldb.SBThread representation for kernel thread. | |
295 | params: | |
cb323159 A |
296 | thread_obj : core.cvalue - thread object of type thread_t |
297 | returns | |
39236c6e A |
298 | lldb.SBThread - lldb thread object for getting backtrace/registers etc. |
299 | """ | |
300 | tid = unsigned(thread_obj.thread_id) | |
301 | lldb_process = LazyTarget.GetProcess() | |
302 | sbthread = lldb_process.GetThreadByID(tid) | |
303 | if not sbthread.IsValid(): | |
304 | # in case lldb doesnt know about this thread, create one | |
305 | if hasattr(lldb_process, "CreateOSPluginThread"): | |
306 | debuglog("creating os plugin thread on the fly for {0:d} 0x{1:x}".format(tid, thread_obj)) | |
307 | lldb_process.CreateOSPluginThread(tid, unsigned(thread_obj)) | |
308 | else: | |
309 | raise RuntimeError("LLDB process does not support CreateOSPluginThread.") | |
310 | sbthread = lldb_process.GetThreadByID(tid) | |
311 | ||
312 | if not sbthread.IsValid(): | |
fe8ab488 A |
313 | raise RuntimeError("Unable to find lldb thread for tid={0:d} thread = {1:#018x} (#16049947: have you put 'settings set target.load-script-from-symbol-file true' in your .lldbinit?)".format(tid, thread_obj)) |
314 | ||
39236c6e A |
315 | return sbthread |
316 | ||
39037602 A |
317 | def GetKextSymbolInfo(load_addr): |
318 | """ Get a string descriptiong load_addr <kextname> + offset | |
319 | params: | |
320 | load_addr - int address value of pc in backtrace. | |
321 | returns: str - kext name + offset string. If no cached data available, warning message is returned. | |
322 | """ | |
323 | symbol_name = "None" | |
324 | symbol_offset = load_addr | |
325 | kmod_val = kern.globals.kmod | |
5ba3f43e | 326 | if not kern.arch.startswith('arm64'): |
39037602 A |
327 | for kval in IterateLinkedList(kmod_val, 'next'): |
328 | if load_addr >= unsigned(kval.address) and \ | |
329 | load_addr <= (unsigned(kval.address) + unsigned(kval.size)): | |
330 | symbol_name = kval.name | |
331 | symbol_offset = load_addr - unsigned(kval.address) | |
332 | break | |
333 | return "{:#018x} {:s} + {:#x} \n".format(load_addr, symbol_name, symbol_offset) | |
334 | ||
335 | # only for arm64 we do lookup for split kexts. | |
336 | cached_kext_info = caching.GetDynamicCacheData("kern.kexts.loadinformation", []) | |
337 | if not cached_kext_info and str(GetConnectionProtocol()) == "core": | |
338 | cached_kext_info = GetKextLoadInformation() | |
339 | ||
340 | if not cached_kext_info: | |
341 | return "{:#018x} ~ kext info not available. please run 'showallkexts' once ~ \n".format(load_addr) | |
342 | ||
343 | for kval in cached_kext_info: | |
344 | text_seg = kval[5] | |
345 | if load_addr >= text_seg.vmaddr and \ | |
346 | load_addr <= (text_seg.vmaddr + text_seg.vmsize): | |
347 | symbol_name = kval[2] | |
348 | symbol_offset = load_addr - text_seg.vmaddr | |
349 | break | |
350 | return "{:#018x} {:s} + {:#x} \n".format(load_addr, symbol_name, symbol_offset) | |
351 | ||
39236c6e A |
352 | def GetThreadBackTrace(thread_obj, verbosity = vHUMAN, prefix = ""): |
353 | """ Get a string to display back trace for a thread. | |
354 | params: | |
355 | thread_obj - core.cvalue : a thread object of type thread_t. | |
356 | verbosity - int : either of vHUMAN, vSCRIPT or vDETAIL to describe the verbosity of output | |
357 | prefix - str : a string prefix added before the line for each frame. | |
358 | isContinuation - bool : is thread a continuation? | |
359 | returns: | |
360 | str - a multi line string showing each frame in backtrace. | |
361 | """ | |
362 | is_continuation = not bool(unsigned(thread_obj.kernel_stack)) | |
363 | thread_val = GetLLDBThreadForKernelThread(thread_obj) | |
364 | out_string = "" | |
365 | kernel_stack = unsigned(thread_obj.kernel_stack) | |
366 | reserved_stack = unsigned(thread_obj.reserved_stack) | |
367 | if not is_continuation: | |
368 | if kernel_stack and reserved_stack: | |
369 | out_string += prefix + "reserved_stack = {:#018x}\n".format(reserved_stack) | |
370 | out_string += prefix + "kernel_stack = {:#018x}\n".format(kernel_stack) | |
371 | else: | |
372 | out_string += prefix + "continuation =" | |
373 | iteration = 0 | |
374 | last_frame_p = 0 | |
375 | for frame in thread_val.frames: | |
376 | addr = frame.GetPCAddress() | |
377 | load_addr = addr.GetLoadAddress(LazyTarget.GetTarget()) | |
378 | function = frame.GetFunction() | |
379 | frame_p = frame.GetFP() | |
380 | mod_name = frame.GetModule().GetFileSpec().GetFilename() | |
381 | ||
382 | if iteration == 0 and not is_continuation: | |
383 | out_string += prefix +"stacktop = {:#018x}\n".format(frame_p) | |
fe8ab488 | 384 | |
39236c6e A |
385 | if not function: |
386 | # No debug info for 'function'. | |
cb323159 | 387 | out_string += prefix |
39236c6e | 388 | if not is_continuation: |
cb323159 A |
389 | out_string += "{fp:#018x} ".format(fp = frame_p) |
390 | ||
fe8ab488 A |
391 | symbol = frame.GetSymbol() |
392 | if not symbol: | |
39037602 | 393 | out_string += GetKextSymbolInfo(load_addr) |
fe8ab488 A |
394 | else: |
395 | file_addr = addr.GetFileAddress() | |
396 | start_addr = symbol.GetStartAddress().GetFileAddress() | |
397 | symbol_name = symbol.GetName() | |
398 | symbol_offset = file_addr - start_addr | |
cb323159 | 399 | out_string += "{addr:#018x} {mod}`{symbol} + {offset:#x} \n".format(addr=load_addr, |
fe8ab488 | 400 | mod=mod_name, symbol=symbol_name, offset=symbol_offset) |
39236c6e A |
401 | else: |
402 | # Debug info is available for 'function'. | |
403 | func_name = frame.GetFunctionName() | |
404 | file_name = frame.GetLineEntry().GetFileSpec().GetFilename() | |
405 | line_num = frame.GetLineEntry().GetLine() | |
406 | func_name = '%s [inlined]' % func_name if frame.IsInlined() else func_name | |
407 | if is_continuation and frame.IsInlined(): | |
408 | debuglog("Skipping frame for thread {:#018x} since its inlined".format(thread_obj)) | |
cb323159 A |
409 | continue |
410 | out_string += prefix | |
39236c6e A |
411 | if not is_continuation: |
412 | out_string += "{fp:#018x} ".format(fp=frame_p) | |
413 | out_string += "{addr:#018x} {func}{args} \n".format(addr=load_addr, | |
414 | func=func_name, | |
415 | file=file_name, line=line_num, | |
416 | args="(" + (str(frame.arguments).replace("\n", ", ") if len(frame.arguments) > 0 else "void") + ")") | |
cb323159 | 417 | iteration += 1 |
39236c6e A |
418 | if frame_p: |
419 | last_frame_p = frame_p | |
420 | ||
421 | if not is_continuation and last_frame_p: | |
422 | out_string += prefix + "stackbottom = {:#018x}".format(last_frame_p) | |
423 | out_string = out_string.replace("variable not available","") | |
424 | return out_string | |
425 | ||
426 | def GetSourceInformationForAddress(addr): | |
cb323159 | 427 | """ convert and address to function +offset information. |
39236c6e | 428 | params: addr - int address in the binary to be symbolicated |
cb323159 | 429 | returns: string of format "0xaddress: function + offset" |
39236c6e A |
430 | """ |
431 | symbols = kern.SymbolicateFromAddress(addr) | |
432 | format_string = "{0:#018x} <{1:s} + {2:#0x}>" | |
433 | offset = 0 | |
434 | function_name = "" | |
435 | if len(symbols) > 0: | |
436 | s = symbols[0] | |
437 | function_name = str(s.name) | |
438 | offset = addr - s.GetStartAddress().GetLoadAddress(LazyTarget.GetTarget()) | |
439 | if function_name == "": | |
440 | function_name = "???" | |
441 | return format_string.format(addr, function_name, offset) | |
442 | ||
443 | def GetFrameLocalVariable(variable_name, frame_no=0): | |
444 | """ Find a local variable by name | |
445 | params: | |
446 | variable_name: str - name of variable to search for | |
cb323159 | 447 | returns: |
39236c6e A |
448 | core.value - if the variable is found. |
449 | None - if not found or not Valid | |
450 | """ | |
451 | retval = None | |
452 | sbval = None | |
453 | lldb_SBThread = LazyTarget.GetProcess().GetSelectedThread() | |
454 | frame = lldb_SBThread.GetSelectedFrame() | |
455 | if frame_no : | |
456 | frame = lldb_SBThread.GetFrameAtIndex(frame_no) | |
457 | if frame : | |
458 | sbval = frame.FindVariable(variable_name) | |
459 | if sbval and sbval.IsValid(): | |
460 | retval = core.cvalue.value(sbval) | |
461 | return retval | |
462 | ||
463 | # Begin Macros for kernel debugging | |
464 | ||
465 | @lldb_command('kgmhelp') | |
466 | def KernelDebugCommandsHelp(cmd_args=None): | |
467 | """ Show a list of registered commands for kenel debugging. | |
468 | """ | |
469 | global lldb_command_documentation | |
470 | print "List of commands provided by " + MODULE_NAME + " for kernel debugging." | |
471 | cmds = lldb_command_documentation.keys() | |
472 | cmds.sort() | |
473 | for cmd in cmds: | |
474 | if type(lldb_command_documentation[cmd][-1]) == type(""): | |
475 | print " {0: <20s} - {1}".format(cmd , lldb_command_documentation[cmd][1].split("\n")[0].strip()) | |
476 | else: | |
477 | print " {0: <20s} - {1}".format(cmd , "No help string found.") | |
5ba3f43e A |
478 | print 'Each of the functions listed here accept the following common options. ' |
479 | print COMMON_HELP_STRING | |
480 | print 'Additionally, each command implementation may have more options. "(lldb) help <command> " will show these options.' | |
39236c6e A |
481 | return None |
482 | ||
483 | ||
cb323159 | 484 | @lldb_command('showraw') |
39236c6e | 485 | def ShowRawCommand(cmd_args=None): |
cb323159 | 486 | """ A command to disable the kernel summaries and show data as seen by the system. |
39236c6e A |
487 | This is useful when trying to read every field of a struct as compared to brief summary |
488 | """ | |
489 | command = " ".join(cmd_args) | |
490 | lldb.debugger.HandleCommand('type category disable kernel' ) | |
491 | lldb.debugger.HandleCommand( command ) | |
492 | lldb.debugger.HandleCommand('type category enable kernel' ) | |
cb323159 | 493 | |
39236c6e A |
494 | |
495 | @lldb_command('xnudebug') | |
496 | def XnuDebugCommand(cmd_args=None): | |
497 | """ command interface for operating on the xnu macros. Allowed commands are as follows | |
498 | reload: | |
499 | Reload a submodule from the xnu/tools/lldb directory. Do not include the ".py" suffix in modulename. | |
500 | usage: xnudebug reload <modulename> (eg. memory, process, stats etc) | |
39037602 A |
501 | flushcache: |
502 | remove any cached data held in static or dynamic data cache. | |
503 | usage: xnudebug flushcache | |
39236c6e A |
504 | test: |
505 | Start running registered test with <name> from various modules. | |
506 | usage: xnudebug test <name> (eg. test_memstats) | |
507 | testall: | |
508 | Go through all registered tests and run them | |
509 | debug: | |
510 | Toggle state of debug configuration flag. | |
511 | """ | |
512 | global config | |
513 | command_args = cmd_args | |
514 | if len(command_args) == 0: | |
515 | raise ArgumentError("No command specified.") | |
39037602 | 516 | supported_subcommands = ['debug', 'reload', 'test', 'testall', 'flushcache'] |
39236c6e A |
517 | subcommand = GetLongestMatchOption(command_args[0], supported_subcommands, True) |
518 | ||
519 | if len(subcommand) == 0: | |
520 | raise ArgumentError("Subcommand (%s) is not a valid command. " % str(command_args[0])) | |
39037602 | 521 | |
39236c6e A |
522 | subcommand = subcommand[0].lower() |
523 | if subcommand == 'debug': | |
524 | if command_args[-1].lower().find('dis') >=0 and config['debug']: | |
525 | config['debug'] = False | |
526 | print "Disabled debug logging." | |
527 | elif command_args[-1].lower().find('dis') < 0 and not config['debug']: | |
528 | config['debug'] = True | |
529 | EnableLLDBAPILogging() # provided by utils.py | |
530 | print "Enabled debug logging. \nPlease run 'xnudebug debug disable' to disable it again. " | |
39037602 A |
531 | if subcommand == 'flushcache': |
532 | print "Current size of cache: {}".format(caching.GetSizeOfCache()) | |
533 | caching.ClearAllCache() | |
534 | ||
39236c6e A |
535 | if subcommand == 'reload': |
536 | module_name = command_args[-1] | |
537 | if module_name in sys.modules: | |
538 | reload(sys.modules[module_name]) | |
539 | print module_name + " is reloaded from " + sys.modules[module_name].__file__ | |
540 | else: | |
541 | print "Unable to locate module named ", module_name | |
542 | if subcommand == 'testall': | |
543 | for test_name in lldb_command_tests.keys(): | |
544 | print "[BEGIN]", test_name | |
545 | res = lldb_command_tests[test_name][2](kern, config, lldb, True) | |
546 | if res: | |
547 | print "[PASSED] {:s}".format(test_name) | |
548 | else: | |
549 | print "[FAILED] {:s}".format(test_name) | |
550 | if subcommand == 'test': | |
551 | test_name = command_args[-1] | |
552 | if test_name in lldb_command_tests: | |
553 | test = lldb_command_tests[test_name] | |
554 | print "Running test {:s}".format(test[0]) | |
cb323159 | 555 | if test[2](kern, config, lldb, True) : |
39236c6e A |
556 | print "[PASSED] {:s}".format(test[0]) |
557 | else: | |
558 | print "[FAILED] {:s}".format(test[0]) | |
cb323159 | 559 | return "" |
39236c6e A |
560 | else: |
561 | print "No such test registered with name: {:s}".format(test_name) | |
562 | print "XNUDEBUG Available tests are:" | |
563 | for i in lldb_command_tests.keys(): | |
564 | print i | |
565 | return None | |
cb323159 | 566 | |
39236c6e A |
567 | return False |
568 | ||
569 | @lldb_command('showversion') | |
570 | def ShowVersion(cmd_args=None): | |
571 | """ Read the kernel version string from a fixed address in low | |
572 | memory. Useful if you don't know which kernel is on the other end, | |
573 | and need to find the appropriate symbols. Beware that if you've | |
574 | loaded a symbol file, but aren't connected to a remote target, | |
575 | the version string from the symbol file will be displayed instead. | |
576 | This macro expects to be connected to the remote kernel to function | |
577 | correctly. | |
578 | ||
579 | """ | |
580 | print kern.version | |
581 | ||
cb323159 A |
582 | def ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len): |
583 | """ Process the panic stackshot from the panic header, saving it to a file if it is valid | |
584 | params: panic_stackshot_addr : start address of the panic stackshot binary data | |
585 | panic_stackshot_len : length of the stackshot binary data | |
586 | returns: nothing | |
39236c6e | 587 | """ |
cb323159 A |
588 | if not panic_stackshot_addr: |
589 | print "No panic stackshot available (invalid addr)" | |
590 | return | |
3e170ce0 | 591 | |
cb323159 A |
592 | if not panic_stackshot_len: |
593 | print "No panic stackshot available (zero length)" | |
594 | return; | |
3e170ce0 | 595 | |
cb323159 A |
596 | ts = int(time.time()) |
597 | ss_binfile = "/tmp/panic_%d.bin" % ts | |
598 | ss_ipsfile = "/tmp/stacks_%d.ips" % ts | |
5ba3f43e | 599 | |
cb323159 A |
600 | if not SaveDataToFile(panic_stackshot_addr, panic_stackshot_len, ss_binfile, None): |
601 | print "Failed to save stackshot binary data to file" | |
602 | return | |
603 | ||
604 | self_path = str(__file__) | |
605 | base_dir_name = self_path[:self_path.rfind("/")] | |
606 | print "python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile) | |
607 | (c,so,se) = RunShellCommand("python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile)) | |
608 | if c == 0: | |
609 | print "Saved ips stackshot file as %s" % ss_ipsfile | |
610 | return | |
5ba3f43e | 611 | else: |
cb323159 | 612 | print "Failed to run command: exit code: %d, SO: %s SE: %s" % (c, so, se) |
cc8bc92a | 613 | return |
5ba3f43e | 614 | |
cb323159 A |
615 | def ParseEmbeddedPanicLog(panic_header, cmd_options={}): |
616 | panic_buf = Cast(panic_header, 'char *') | |
617 | panic_log_magic = unsigned(panic_header.eph_magic) | |
618 | panic_log_begin_offset = unsigned(panic_header.eph_panic_log_offset) | |
619 | panic_log_len = unsigned(panic_header.eph_panic_log_len) | |
620 | other_log_begin_offset = unsigned(panic_header.eph_other_log_offset) | |
621 | other_log_len = unsigned(panic_header.eph_other_log_len) | |
622 | expected_panic_magic = xnudefines.EMBEDDED_PANIC_MAGIC | |
623 | panic_stackshot_addr = unsigned(panic_header) + unsigned(panic_header.eph_stackshot_offset) | |
624 | panic_stackshot_len = unsigned(panic_header.eph_stackshot_len) | |
625 | panic_header_flags = unsigned(panic_header.eph_panic_flags) | |
626 | ||
627 | warn_str = "" | |
628 | out_str = "" | |
629 | ||
cc8bc92a | 630 | if panic_log_magic != 0 and panic_log_magic != expected_panic_magic: |
cb323159 | 631 | warn_str += "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic, |
cc8bc92a | 632 | expected_panic_magic) |
5ba3f43e | 633 | |
cb323159 A |
634 | if warn_str: |
635 | print "\n %s" % warn_str | |
636 | if panic_log_begin_offset == 0: | |
637 | return | |
638 | ||
639 | if "-S" in cmd_options: | |
640 | if panic_header_flags & xnudefines.EMBEDDED_PANIC_STACKSHOT_SUCCEEDED_FLAG: | |
641 | ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len) | |
642 | else: | |
643 | print "No panic stackshot available" | |
cc8bc92a A |
644 | |
645 | panic_log_curindex = 0 | |
646 | while panic_log_curindex < panic_log_len: | |
647 | p_char = str(panic_buf[(panic_log_begin_offset + panic_log_curindex)]) | |
648 | out_str += p_char | |
649 | panic_log_curindex += 1 | |
5ba3f43e | 650 | |
cc8bc92a A |
651 | if other_log_begin_offset != 0: |
652 | other_log_curindex = 0 | |
653 | while other_log_curindex < other_log_len: | |
654 | p_char = str(panic_buf[(other_log_begin_offset + other_log_curindex)]) | |
5ba3f43e | 655 | out_str += p_char |
cc8bc92a | 656 | other_log_curindex += 1 |
5ba3f43e A |
657 | |
658 | print out_str | |
cb323159 A |
659 | return |
660 | ||
661 | def ParseMacOSPanicLog(panic_header, cmd_options={}): | |
662 | panic_buf = Cast(panic_header, 'char *') | |
663 | panic_log_magic = unsigned(panic_header.mph_magic) | |
664 | panic_log_begin_offset = unsigned(panic_header.mph_panic_log_offset) | |
665 | panic_log_len = unsigned(panic_header.mph_panic_log_len) | |
666 | other_log_begin_offset = unsigned(panic_header.mph_other_log_offset) | |
667 | other_log_len = unsigned(panic_header.mph_other_log_len) | |
668 | cur_debug_buf_ptr_offset = (unsigned(kern.globals.debug_buf_ptr) - unsigned(panic_header)) | |
669 | if other_log_begin_offset != 0 and (other_log_len == 0 or other_log_len < (cur_debug_buf_ptr_offset - other_log_begin_offset)): | |
670 | other_log_len = cur_debug_buf_ptr_offset - other_log_begin_offset | |
671 | expected_panic_magic = xnudefines.MACOS_PANIC_MAGIC | |
f427ee49 A |
672 | |
673 | # use the global if it's available (on an x86 corefile), otherwise refer to the header | |
674 | if hasattr(kern.globals, "panic_stackshot_buf"): | |
675 | panic_stackshot_addr = unsigned(kern.globals.panic_stackshot_buf) | |
676 | panic_stackshot_len = unsigned(kern.globals.panic_stackshot_len) | |
677 | else: | |
678 | panic_stackshot_addr = unsigned(panic_header) + unsigned(panic_header.mph_stackshot_offset) | |
679 | panic_stackshot_len = unsigned(panic_header.mph_stackshot_len) | |
680 | ||
cb323159 A |
681 | panic_header_flags = unsigned(panic_header.mph_panic_flags) |
682 | ||
683 | warn_str = "" | |
684 | out_str = "" | |
685 | ||
686 | if panic_log_magic != 0 and panic_log_magic != expected_panic_magic: | |
687 | warn_str += "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic, | |
688 | expected_panic_magic) | |
3e170ce0 | 689 | |
fe8ab488 | 690 | if warn_str: |
5ba3f43e | 691 | print "\n %s" % warn_str |
cb323159 A |
692 | if panic_log_begin_offset == 0: |
693 | return | |
694 | ||
695 | if "-S" in cmd_options: | |
696 | if panic_header_flags & xnudefines.MACOS_PANIC_STACKSHOT_SUCCEEDED_FLAG: | |
697 | ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len) | |
698 | else: | |
699 | print "No panic stackshot available" | |
700 | ||
701 | panic_log_curindex = 0 | |
702 | while panic_log_curindex < panic_log_len: | |
703 | p_char = str(panic_buf[(panic_log_begin_offset + panic_log_curindex)]) | |
704 | out_str += p_char | |
705 | panic_log_curindex += 1 | |
706 | ||
707 | if other_log_begin_offset != 0: | |
708 | other_log_curindex = 0 | |
709 | while other_log_curindex < other_log_len: | |
710 | p_char = str(panic_buf[(other_log_begin_offset + other_log_curindex)]) | |
711 | out_str += p_char | |
712 | other_log_curindex += 1 | |
713 | ||
714 | print out_str | |
715 | return | |
716 | ||
717 | def ParseAURRPanicLog(panic_header, cmd_options={}): | |
718 | reset_cause = { | |
719 | 0x0: "OTHER", | |
720 | 0x1: "CATERR", | |
721 | 0x2: "SWD_TIMEOUT", | |
722 | 0x3: "GLOBAL RESET", | |
723 | 0x4: "STRAIGHT TO S5", | |
724 | } | |
725 | ||
726 | expected_panic_magic = xnudefines.AURR_PANIC_MAGIC | |
727 | ||
728 | panic_buf = Cast(panic_header, 'char *') | |
729 | ||
730 | try: | |
731 | # This line will blow up if there's not type info for this struct (older kernel) | |
732 | # We fall back to manual parsing below | |
733 | aurr_panic_header = Cast(panic_header, 'struct efi_aurr_panic_header *') | |
734 | panic_log_magic = unsigned(aurr_panic_header.efi_aurr_magic) | |
735 | panic_log_version = unsigned(aurr_panic_header.efi_aurr_version) | |
736 | panic_log_reset_cause = unsigned(aurr_panic_header.efi_aurr_reset_cause) | |
737 | panic_log_reset_log_offset = unsigned(aurr_panic_header.efi_aurr_reset_log_offset) | |
738 | panic_log_reset_log_len = unsigned(aurr_panic_header.efi_aurr_reset_log_len) | |
739 | except Exception as e: | |
740 | print "*** Warning: kernel symbol file has no type information for 'struct efi_aurr_panic_header'..." | |
741 | print "*** Warning: trying to manually parse..." | |
742 | aurr_panic_header = Cast(panic_header, "uint32_t *") | |
743 | panic_log_magic = unsigned(aurr_panic_header[0]) | |
744 | # panic_log_crc = unsigned(aurr_panic_header[1]) | |
745 | panic_log_version = unsigned(aurr_panic_header[2]) | |
746 | panic_log_reset_cause = unsigned(aurr_panic_header[3]) | |
747 | panic_log_reset_log_offset = unsigned(aurr_panic_header[4]) | |
748 | panic_log_reset_log_len = unsigned(aurr_panic_header[5]) | |
749 | ||
750 | if panic_log_magic != 0 and panic_log_magic != expected_panic_magic: | |
751 | print "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic, | |
752 | expected_panic_magic) | |
753 | return | |
754 | ||
755 | print "AURR Panic Version: %d" % (panic_log_version) | |
756 | ||
757 | # When it comes time to extend this in the future, please follow the | |
758 | # construct used below in ShowPanicLog() | |
759 | if panic_log_version in (xnudefines.AURR_PANIC_VERSION, xnudefines.AURR_CRASHLOG_PANIC_VERSION): | |
760 | # AURR Report Version 1 (AURR/MacEFI) or 2 (Crashlog) | |
761 | # see macefifirmware/Vendor/Apple/EfiPkg/AppleDebugSupport/Library/Debugger.h | |
762 | print "Reset Cause: 0x%x (%s)" % (panic_log_reset_cause, reset_cause.get(panic_log_reset_cause, "UNKNOWN")) | |
763 | ||
764 | # Adjust panic log string length (cap to maximum supported values) | |
765 | if panic_log_version == xnudefines.AURR_PANIC_VERSION: | |
f427ee49 | 766 | max_string_len = panic_log_reset_log_len |
cb323159 A |
767 | elif panic_log_version == xnudefines.AURR_CRASHLOG_PANIC_VERSION: |
768 | max_string_len = xnudefines.CRASHLOG_PANIC_STRING_LEN | |
769 | ||
770 | panic_str_offset = 0 | |
771 | out_str = "" | |
772 | ||
773 | while panic_str_offset < max_string_len: | |
774 | p_char = str(panic_buf[panic_log_reset_log_offset + panic_str_offset]) | |
775 | out_str += p_char | |
776 | panic_str_offset += 1 | |
777 | ||
778 | print out_str | |
3e170ce0 | 779 | |
cb323159 A |
780 | # Save Crashlog Binary Data (if available) |
781 | if "-S" in cmd_options and panic_log_version == xnudefines.AURR_CRASHLOG_PANIC_VERSION: | |
782 | crashlog_binary_offset = panic_log_reset_log_offset + xnudefines.CRASHLOG_PANIC_STRING_LEN | |
783 | crashlog_binary_size = (panic_log_reset_log_len > xnudefines.CRASHLOG_PANIC_STRING_LEN) and (panic_log_reset_log_len - xnudefines.CRASHLOG_PANIC_STRING_LEN) or 0 | |
784 | ||
785 | if 0 == crashlog_binary_size: | |
786 | print "No crashlog data found..." | |
787 | return | |
788 | ||
789 | # Save to file | |
790 | ts = int(time.time()) | |
791 | ss_binfile = "/tmp/crashlog_%d.bin" % ts | |
792 | ||
793 | if not SaveDataToFile(panic_buf + crashlog_binary_offset, crashlog_binary_size, ss_binfile, None): | |
794 | print "Failed to save crashlog binary data to file" | |
795 | return | |
796 | else: | |
797 | return ParseUnknownPanicLog(panic_header, cmd_options) | |
798 | ||
799 | return | |
800 | ||
801 | def ParseUnknownPanicLog(panic_header, cmd_options={}): | |
802 | magic_ptr = Cast(panic_header, 'uint32_t *') | |
803 | panic_log_magic = dereference(magic_ptr) | |
804 | print "Unrecognized panic header format. Magic: 0x%x..." % unsigned(panic_log_magic) | |
805 | print "Panic region starts at 0x%08x" % int(panic_header) | |
806 | print "Hint: To dump this panic header in order to try manually parsing it, use this command:" | |
807 | print " (lldb) memory read -fx -s4 -c64 0x%08x" % int(panic_header) | |
808 | print " ^ that will dump the first 256 bytes of the panic region" | |
809 | ## TBD: Hexdump some bits here to allow folks to poke at the region manually? | |
39236c6e A |
810 | return |
811 | ||
cb323159 A |
812 | |
813 | @lldb_command('paniclog', 'SM') | |
814 | def ShowPanicLog(cmd_args=None, cmd_options={}): | |
815 | """ Display the paniclog information | |
816 | usage: (lldb) paniclog | |
817 | options: | |
818 | -v : increase verbosity | |
819 | -S : parse stackshot data (if panic stackshot available) | |
820 | -M : parse macOS panic area (print panic string (if available), and/or capture crashlog info) | |
821 | """ | |
822 | ||
823 | if "-M" in cmd_options: | |
824 | if not hasattr(kern.globals, "mac_panic_header"): | |
825 | print "macOS panic data requested but unavailable on this device" | |
826 | return | |
827 | panic_header = kern.globals.mac_panic_header | |
828 | # DEBUG HACK FOR TESTING | |
829 | #panic_header = kern.GetValueFromAddress(0xfffffff054098000, "uint32_t *") | |
830 | else: | |
831 | panic_header = kern.globals.panic_info | |
832 | ||
833 | if hasattr(panic_header, "eph_magic"): | |
834 | panic_log_magic = unsigned(panic_header.eph_magic) | |
835 | elif hasattr(panic_header, "mph_magic"): | |
836 | panic_log_magic = unsigned(panic_header.mph_magic) | |
837 | else: | |
838 | print "*** Warning: unsure of panic header format, trying anyway" | |
839 | magic_ptr = Cast(panic_header, 'uint32_t *') | |
840 | panic_log_magic = int(dereference(magic_ptr)) | |
841 | ||
842 | if panic_log_magic == 0: | |
843 | # No panic here.. | |
844 | return | |
845 | ||
846 | panic_parsers = { | |
847 | int(xnudefines.AURR_PANIC_MAGIC) : ParseAURRPanicLog, | |
848 | int(xnudefines.MACOS_PANIC_MAGIC) : ParseMacOSPanicLog, | |
849 | int(xnudefines.EMBEDDED_PANIC_MAGIC) : ParseEmbeddedPanicLog, | |
850 | } | |
851 | ||
852 | # Find the right parser (fall back to unknown parser above) | |
853 | parser = panic_parsers.get(panic_log_magic, ParseUnknownPanicLog) | |
854 | ||
855 | # execute it | |
856 | return parser(panic_header, cmd_options) | |
857 | ||
39236c6e A |
858 | @lldb_command('showbootargs') |
859 | def ShowBootArgs(cmd_args=None): | |
860 | """ Display boot arguments passed to the target kernel | |
861 | """ | |
862 | bootargs = Cast(kern.GetGlobalVariable('PE_state').bootArgs, 'boot_args *') | |
863 | bootargs_cmd = bootargs.CommandLine | |
864 | print str(bootargs_cmd) | |
865 | ||
866 | @static_var("last_process_uniq_id", 1) | |
867 | def GetDebuggerStopIDValue(): | |
cb323159 | 868 | """ Create a unique session identifier. |
39236c6e A |
869 | returns: |
870 | int - a unique number identified by processid and stopid. | |
871 | """ | |
872 | stop_id = 0 | |
873 | process_obj = LazyTarget.GetProcess() | |
874 | if hasattr(process_obj, "GetStopID"): | |
875 | stop_id = process_obj.GetStopID() | |
876 | proc_uniq_id = 0 | |
877 | if hasattr(process_obj, 'GetUniqueID'): | |
878 | proc_uniq_id = process_obj.GetUniqueID() | |
879 | #FIXME <rdar://problem/13034329> forces us to do this twice | |
880 | proc_uniq_id = process_obj.GetUniqueID() | |
881 | else: | |
882 | GetDebuggerStopIDValue.last_process_uniq_id +=1 | |
883 | proc_uniq_id = GetDebuggerStopIDValue.last_process_uniq_id + 1 | |
884 | ||
cb323159 | 885 | stop_id_str = "{:d}:{:d}".format(proc_uniq_id, stop_id) |
39236c6e A |
886 | return hash(stop_id_str) |
887 | ||
888 | # The initialization code to add your commands | |
889 | _xnu_framework_init = False | |
890 | def __lldb_init_module(debugger, internal_dict): | |
891 | global kern, lldb_command_documentation, config, _xnu_framework_init | |
892 | if _xnu_framework_init: | |
893 | return | |
894 | _xnu_framework_init = True | |
895 | caching._GetDebuggerSessionID = GetDebuggerStopIDValue | |
896 | debugger.HandleCommand('type summary add --regex --summary-string "${var%s}" -C yes -p -v "char \[[0-9]*\]"') | |
897 | debugger.HandleCommand('type format add --format hex -C yes uintptr_t') | |
898 | kern = KernelTarget(debugger) | |
cb323159 A |
899 | if not hasattr(lldb.SBValue, 'GetValueAsAddress'): |
900 | warn_str = "WARNING: lldb version is too old. Some commands may break. Please update to latest lldb." | |
901 | if os.isatty(sys.__stdout__.fileno()): | |
902 | warn_str = VT.DarkRed + warn_str + VT.Default | |
903 | print warn_str | |
39236c6e A |
904 | print "xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries." |
905 | ||
906 | __lldb_init_module(lldb.debugger, None) | |
907 | ||
908 | @lldb_command("showlldbtypesummaries") | |
909 | def ShowLLDBTypeSummaries(cmd_args=[]): | |
910 | """ Enable/Disable kernel type summaries. Default is disabled. | |
911 | Usage: showlldbtypesummaries [enable|disable] | |
912 | default is enable | |
913 | """ | |
914 | global config | |
915 | action = "enable" | |
916 | trailer_msg = '' | |
917 | if len(cmd_args) > 0 and cmd_args[0].lower().find('disable') >=0: | |
918 | action = "disable" | |
919 | config['showTypeSummary'] = False | |
920 | trailer_msg = "Please run 'showlldbtypesummaries enable' to enable the summary feature." | |
921 | else: | |
922 | config['showTypeSummary'] = True | |
923 | SetupLLDBTypeSummaries(True) | |
924 | trailer_msg = "Please run 'showlldbtypesummaries disable' to disable the summary feature." | |
925 | lldb_run_command("type category "+ action +" kernel") | |
926 | print "Successfully "+action+"d the kernel type summaries. %s" % trailer_msg | |
927 | ||
fe8ab488 A |
928 | @lldb_command('walkqueue_head', 'S') |
929 | def WalkQueueHead(cmd_args=[], cmd_options={}): | |
cb323159 | 930 | """ walk a queue_head_t and list all members in it. Note this is for queue_head_t. refer to osfmk/kern/queue.h |
fe8ab488 A |
931 | Option: -S - suppress summary output. |
932 | Usage: (lldb) walkqueue_head <queue_entry *> <struct type> <fieldname> | |
933 | ex: (lldb) walkqueue_head 0x7fffff80 "thread *" "task_threads" | |
cb323159 | 934 | |
fe8ab488 A |
935 | """ |
936 | global lldb_summary_definitions | |
937 | if not cmd_args: | |
938 | raise ArgumentError("invalid arguments") | |
939 | if len(cmd_args) != 3: | |
940 | raise ArgumentError("insufficient arguments") | |
941 | queue_head = kern.GetValueFromAddress(cmd_args[0], 'struct queue_entry *') | |
942 | el_type = cmd_args[1] | |
943 | field_name = cmd_args[2] | |
944 | showsummary = False | |
945 | if el_type in lldb_summary_definitions: | |
946 | showsummary = True | |
947 | if '-S' in cmd_options: | |
948 | showsummary = False | |
949 | ||
950 | for i in IterateQueue(queue_head, el_type, field_name): | |
951 | if showsummary: | |
952 | print lldb_summary_definitions[el_type](i) | |
953 | else: | |
954 | print "{0: <#020x}".format(i) | |
cb323159 | 955 | |
fe8ab488 A |
956 | |
957 | ||
4ba76501 | 958 | @lldb_command('walklist_entry', 'SE') |
fe8ab488 A |
959 | def WalkList(cmd_args=[], cmd_options={}): |
960 | """ iterate over a list as defined with LIST_ENTRY in bsd/sys/queue.h | |
961 | params: | |
962 | object addr - value : address of object | |
963 | element_type - str : Type of the next element | |
964 | field_name - str : Name of the field in next element's structure | |
965 | ||
4ba76501 A |
966 | Options: -S - suppress summary output. |
967 | -E - Iterate using SLIST_ENTRYs | |
968 | ||
fe8ab488 A |
969 | Usage: (lldb) walklist_entry <obj with list_entry *> <struct type> <fieldname> |
970 | ex: (lldb) walklist_entry 0x7fffff80 "struct proc *" "p_sibling" | |
cb323159 | 971 | |
fe8ab488 A |
972 | """ |
973 | global lldb_summary_definitions | |
974 | if not cmd_args: | |
975 | raise ArgumentError("invalid arguments") | |
976 | if len(cmd_args) != 3: | |
977 | raise ArgumentError("insufficient arguments") | |
978 | el_type = cmd_args[1] | |
979 | queue_head = kern.GetValueFromAddress(cmd_args[0], el_type) | |
980 | field_name = cmd_args[2] | |
fe8ab488 A |
981 | showsummary = False |
982 | if el_type in lldb_summary_definitions: | |
983 | showsummary = True | |
984 | if '-S' in cmd_options: | |
985 | showsummary = False | |
4ba76501 A |
986 | if '-E' in cmd_options: |
987 | prefix = 's' | |
988 | else: | |
989 | prefix = '' | |
fe8ab488 A |
990 | elt = queue_head |
991 | while unsigned(elt) != 0: | |
992 | i = elt | |
4ba76501 | 993 | elt = elt.__getattr__(field_name).__getattr__(prefix + 'le_next') |
fe8ab488 A |
994 | if showsummary: |
995 | print lldb_summary_definitions[el_type](i) | |
996 | else: | |
997 | print "{0: <#020x}".format(i) | |
998 | ||
94ff46dc | 999 | def trace_parse_Copt(Copt): |
0a7de745 A |
1000 | """Parses the -C option argument and returns a list of CPUs |
1001 | """ | |
1002 | cpusOpt = Copt | |
1003 | cpuList = cpusOpt.split(",") | |
1004 | chosen_cpus = [] | |
1005 | for cpu_num_string in cpuList: | |
1006 | try: | |
1007 | if '-' in cpu_num_string: | |
1008 | parts = cpu_num_string.split('-') | |
1009 | if len(parts) != 2 or not (parts[0].isdigit() and parts[1].isdigit()): | |
1010 | raise ArgumentError("Invalid cpu specification: %s" % cpu_num_string) | |
1011 | firstRange = int(parts[0]) | |
1012 | lastRange = int(parts[1]) | |
1013 | if firstRange >= kern.globals.real_ncpus or lastRange >= kern.globals.real_ncpus: | |
1014 | raise ValueError() | |
1015 | if lastRange < firstRange: | |
1016 | raise ArgumentError("Invalid CPU range specified: `%s'" % cpu_num_string) | |
1017 | for cpu_num in range(firstRange, lastRange + 1): | |
1018 | if cpu_num not in chosen_cpus: | |
1019 | chosen_cpus.append(cpu_num) | |
1020 | else: | |
1021 | chosen_cpu = int(cpu_num_string) | |
1022 | if chosen_cpu < 0 or chosen_cpu >= kern.globals.real_ncpus: | |
1023 | raise ValueError() | |
1024 | if chosen_cpu not in chosen_cpus: | |
1025 | chosen_cpus.append(chosen_cpu) | |
1026 | except ValueError: | |
1027 | raise ArgumentError("Invalid CPU number specified. Valid range is 0..%d" % (kern.globals.real_ncpus - 1)) | |
1028 | ||
1029 | return chosen_cpus | |
1030 | ||
1031 | ||
94ff46dc A |
1032 | IDX_CPU = 0 |
1033 | IDX_RINGPOS = 1 | |
1034 | IDX_RINGENTRY = 2 | |
1035 | def Trace_cmd(cmd_args=[], cmd_options={}, headerString=lambda:"", entryString=lambda x:"", ring=[], entries_per_cpu=0, max_backtraces=0): | |
1036 | """Generic trace dumper helper function | |
0a7de745 | 1037 | """ |
0a7de745 A |
1038 | |
1039 | if '-S' in cmd_options: | |
1040 | field_arg = cmd_options['-S'] | |
1041 | try: | |
94ff46dc | 1042 | getattr(ring[0][0], field_arg) |
0a7de745 A |
1043 | sort_key_field_name = field_arg |
1044 | except AttributeError: | |
1045 | raise ArgumentError("Invalid sort key field name `%s'" % field_arg) | |
1046 | else: | |
1047 | sort_key_field_name = 'start_time_abs' | |
1048 | ||
1049 | if '-C' in cmd_options: | |
94ff46dc | 1050 | chosen_cpus = trace_parse_Copt(cmd_options['-C']) |
0a7de745 A |
1051 | else: |
1052 | chosen_cpus = [x for x in range(kern.globals.real_ncpus)] | |
1053 | ||
1054 | try: | |
1055 | limit_output_count = int(cmd_options['-N']) | |
1056 | except ValueError: | |
1057 | raise ArgumentError("Invalid output count `%s'" % cmd_options['-N']); | |
1058 | except KeyError: | |
1059 | limit_output_count = None | |
1060 | ||
1061 | reverse_sort = '-R' in cmd_options | |
1062 | backtraces = '-B' in cmd_options | |
1063 | ||
1064 | # entries will be a list of 3-tuples, each holding the CPU on which the iotrace entry was collected, | |
1065 | # the original ring index, and the iotrace entry. | |
1066 | entries = [] | |
1067 | for x in chosen_cpus: | |
94ff46dc | 1068 | ring_slice = [(x, y, ring[x][y]) for y in range(entries_per_cpu)] |
0a7de745 A |
1069 | entries.extend(ring_slice) |
1070 | ||
1071 | total_entries = len(entries) | |
1072 | ||
1073 | entries.sort(key=lambda x: getattr(x[IDX_RINGENTRY], sort_key_field_name), reverse=reverse_sort) | |
1074 | ||
1075 | if limit_output_count is not None and limit_output_count > total_entries: | |
1076 | print ("NOTE: Output count `%d' is too large; showing all %d entries" % (limit_output_count, total_entries)); | |
1077 | limit_output_count = total_entries | |
1078 | ||
1079 | if len(chosen_cpus) < kern.globals.real_ncpus: | |
1080 | print "NOTE: Limiting to entries from cpu%s %s" % ("s" if len(chosen_cpus) > 1 else "", str(chosen_cpus)) | |
1081 | ||
1082 | if limit_output_count is not None and limit_output_count < total_entries: | |
1083 | entries_to_display = limit_output_count | |
1084 | print "NOTE: Limiting to the %s" % ("first entry" if entries_to_display == 1 else ("first %d entries" % entries_to_display)) | |
1085 | else: | |
1086 | entries_to_display = total_entries | |
1087 | ||
94ff46dc | 1088 | print headerString() |
0a7de745 A |
1089 | |
1090 | for x in xrange(entries_to_display): | |
94ff46dc A |
1091 | print entryString(entries[x]) |
1092 | ||
0a7de745 | 1093 | if backtraces: |
94ff46dc | 1094 | for btidx in range(max_backtraces): |
0a7de745 A |
1095 | nextbt = entries[x][IDX_RINGENTRY].backtrace[btidx] |
1096 | if nextbt == 0: | |
1097 | break | |
1098 | print "\t" + GetSourceInformationForAddress(nextbt) | |
94ff46dc A |
1099 | |
1100 | ||
1101 | @lldb_command('iotrace', 'C:N:S:RB') | |
1102 | def IOTrace_cmd(cmd_args=[], cmd_options={}): | |
1103 | """ Prints the iotrace ring buffers for all CPUs by default. | |
1104 | Arguments: | |
1105 | -B : Print backtraces for each ring entry | |
1106 | -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a | |
1107 | single CPU number or a range separated by a dash (e.g. "0-3")) | |
1108 | -N <count> : Limit output to the first <count> entries (across all chosen CPUs) | |
1109 | -R : Display results in reverse-sorted order (oldest first; default is newest-first) | |
1110 | -S <sort_key_field_name> : Sort output by specified iotrace_entry_t field name (instead of by timestamp) | |
1111 | """ | |
1112 | MAX_IOTRACE_BACKTRACES = 16 | |
1113 | ||
1114 | if kern.arch != "x86_64": | |
1115 | print "Sorry, iotrace is an x86-only command." | |
1116 | return | |
1117 | ||
1118 | hdrString = lambda : "%-19s %-8s %-10s %-20s SZ %-18s %-17s DATA" % ( | |
1119 | "START TIME", | |
1120 | "DURATION", | |
1121 | "CPU#[RIDX]", | |
1122 | " TYPE", | |
1123 | " VIRT ADDR", | |
1124 | " PHYS ADDR") | |
1125 | ||
1126 | entryString = lambda x : "%-20u(%6u) %6s[%02d] %-20s %-2d 0x%016x 0x%016x 0x%x" % ( | |
1127 | x[IDX_RINGENTRY].start_time_abs, | |
1128 | x[IDX_RINGENTRY].duration, | |
1129 | "CPU%d" % x[IDX_CPU], | |
1130 | x[IDX_RINGPOS], | |
1131 | str(x[IDX_RINGENTRY].iotype).split("=")[1].strip(), | |
1132 | x[IDX_RINGENTRY].size, | |
1133 | x[IDX_RINGENTRY].vaddr, | |
1134 | x[IDX_RINGENTRY].paddr, | |
1135 | x[IDX_RINGENTRY].val) | |
1136 | ||
1137 | Trace_cmd(cmd_args, cmd_options, hdrString, entryString, kern.globals.iotrace_ring, kern.globals.iotrace_entries_per_cpu, MAX_IOTRACE_BACKTRACES) | |
1138 | ||
1139 | ||
1140 | @lldb_command('ttrace', 'C:N:S:RB') | |
1141 | def TrapTrace_cmd(cmd_args=[], cmd_options={}): | |
1142 | """ Prints the iotrace ring buffers for all CPUs by default. | |
1143 | Arguments: | |
1144 | -B : Print backtraces for each ring entry | |
1145 | -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a | |
1146 | single CPU number or a range separated by a dash (e.g. "0-3")) | |
1147 | -N <count> : Limit output to the first <count> entries (across all chosen CPUs) | |
1148 | -R : Display results in reverse-sorted order (oldest first; default is newest-first) | |
1149 | -S <sort_key_field_name> : Sort output by specified traptrace_entry_t field name (instead of by timestamp) | |
1150 | """ | |
1151 | MAX_TRAPTRACE_BACKTRACES = 8 | |
1152 | ||
1153 | if kern.arch != "x86_64": | |
1154 | print "Sorry, ttrace is an x86-only command." | |
1155 | return | |
1156 | ||
1157 | hdrString = lambda : "%-30s CPU#[RIDX] VECT INTERRUPTED_THREAD PREMLV INTRLV INTERRUPTED_PC" % ( | |
1158 | "START TIME (DURATION [ns])") | |
1159 | entryString = lambda x : "%-20u(%6s) %8s[%02d] 0x%02x 0x%016x %6d %6d %s" % ( | |
1160 | x[IDX_RINGENTRY].start_time_abs, | |
1161 | str(x[IDX_RINGENTRY].duration) if hex(x[IDX_RINGENTRY].duration) != "0xffffffffffffffff" else 'inprog', | |
1162 | "CPU%d" % x[IDX_CPU], | |
1163 | x[IDX_RINGPOS], | |
1164 | int(x[IDX_RINGENTRY].vector), | |
1165 | x[IDX_RINGENTRY].curthread, | |
1166 | x[IDX_RINGENTRY].curpl, | |
1167 | x[IDX_RINGENTRY].curil, | |
1168 | GetSourceInformationForAddress(x[IDX_RINGENTRY].interrupted_pc)) | |
1169 | ||
1170 | Trace_cmd(cmd_args, cmd_options, hdrString, entryString, kern.globals.traptrace_ring, | |
1171 | kern.globals.traptrace_entries_per_cpu, MAX_TRAPTRACE_BACKTRACES) | |
c3c9b80d A |
1172 | |
1173 | # Yields an iterator over all the sysctls from the provided root. | |
1174 | # Can optionally filter by the given prefix | |
1175 | def IterateSysctls(root_oid=kern.globals.sysctl__children, prefix="", depth = 0, parent = ""): | |
1176 | headp = root_oid | |
1177 | for pp in IterateListEntry(headp, 'struct sysctl_oid *', 'oid_link', 's'): | |
1178 | node_str = "" | |
1179 | if prefix != "": | |
1180 | node_str = str(pp.oid_name) | |
1181 | if parent != "": | |
1182 | node_str = parent + "." + node_str | |
1183 | if node_str.startswith(prefix): | |
1184 | yield pp, depth, parent | |
1185 | else: | |
1186 | yield pp, depth, parent | |
1187 | type = pp.oid_kind & 0xf | |
1188 | if type == 1 and pp.oid_arg1 != 0: | |
1189 | if node_str == "": | |
1190 | next_parent = str(pp.oid_name) | |
1191 | if parent != "": | |
1192 | next_parent = parent + "." + next_parent | |
1193 | else: | |
1194 | next_parent = node_str | |
1195 | # Only recurse if the next parent starts with our allowed prefix. | |
1196 | # Note that it's OK if the parent string is too short (because the prefix might be for a deeper node). | |
1197 | prefix_len = min(len(prefix), len(next_parent)) | |
1198 | if next_parent[:prefix_len] == prefix[:prefix_len]: | |
1199 | for x in IterateSysctls(Cast(pp.oid_arg1, "struct sysctl_oid_list *"), prefix, depth + 1, next_parent): | |
1200 | yield x | |
0a7de745 | 1201 | |
4ba76501 A |
1202 | @lldb_command('showsysctls', 'P:') |
1203 | def ShowSysctls(cmd_args=[], cmd_options={}): | |
1204 | """ Walks the list of sysctl data structures, printing out each during traversal. | |
1205 | Arguments: | |
1206 | -P <string> : Limit output to sysctls starting with the specified prefix. | |
1207 | """ | |
1208 | if '-P' in cmd_options: | |
1209 | _ShowSysctl_prefix = cmd_options['-P'] | |
1210 | allowed_prefixes = _ShowSysctl_prefix.split('.') | |
1211 | if allowed_prefixes: | |
1212 | for x in xrange(1, len(allowed_prefixes)): | |
1213 | allowed_prefixes[x] = allowed_prefixes[x - 1] + "." + allowed_prefixes[x] | |
1214 | else: | |
1215 | _ShowSysctl_prefix = '' | |
1216 | allowed_prefixes = [] | |
4ba76501 | 1217 | |
c3c9b80d A |
1218 | for sysctl, depth, parentstr in IterateSysctls(kern.globals.sysctl__children, _ShowSysctl_prefix): |
1219 | if parentstr == "": | |
1220 | parentstr = "<none>" | |
1221 | headp = sysctl | |
1222 | st = (" " * depth * 2) + str(sysctl.GetSBValue().Dereference()).replace("\n", "\n" + (" " * depth * 2)) | |
1223 | print 'parent = "%s"' % parentstr, st[st.find("{"):] | |
1224 | ||
1225 | @lldb_command('showexperiments', 'F') | |
1226 | def ShowExperiments(cmd_args=[], cmd_options={}): | |
1227 | """ Shows any active kernel experiments being run on the device via trial. | |
1228 | Arguments: | |
1229 | -F: Scan for changed experiment values even if no trial identifiers have been set. | |
1230 | """ | |
1231 | ||
1232 | treatment_id = str(kern.globals.trial_treatment_id) | |
1233 | experiment_id = str(kern.globals.trial_experiment_id) | |
1234 | deployment_id = kern.globals.trial_deployment_id._GetValueAsSigned() | |
1235 | if treatment_id == "" and experiment_id == "" and deployment_id == -1: | |
1236 | print("Device is not enrolled in any kernel experiments.") | |
1237 | if not '-F' in cmd_options: | |
1238 | return | |
1239 | else: | |
1240 | print("""Device is enrolled in a kernel experiment: | |
1241 | treatment_id: %s | |
1242 | experiment_id: %s | |
1243 | deployment_id: %d""" % (treatment_id, experiment_id, deployment_id)) | |
1244 | ||
1245 | print("Scanning sysctl tree for modified factors...") | |
1246 | ||
1247 | kExperimentFactorFlag = 0x00100000 | |
1248 | ||
1249 | formats = { | |
1250 | "IU": gettype("unsigned int *"), | |
1251 | "I": gettype("int *"), | |
1252 | "LU": gettype("unsigned long *"), | |
1253 | "L": gettype("long *"), | |
1254 | "QU": gettype("uint64_t *"), | |
1255 | "Q": gettype("int64_t *") | |
1256 | } | |
fe8ab488 | 1257 | |
c3c9b80d A |
1258 | for sysctl, depth, parentstr in IterateSysctls(kern.globals.sysctl__children): |
1259 | if sysctl.oid_kind & kExperimentFactorFlag: | |
1260 | spec = cast(sysctl.oid_arg1, "struct experiment_spec *") | |
1261 | # Skip if arg2 isn't set to 1 (indicates an experiment factor created without an experiment_spec). | |
1262 | if sysctl.oid_arg2 == 1: | |
1263 | if spec.modified == 1: | |
1264 | fmt = str(sysctl.oid_fmt) | |
1265 | ptr = spec.ptr | |
1266 | t = formats.get(fmt, None) | |
1267 | if t: | |
1268 | value = cast(ptr, t) | |
1269 | else: | |
1270 | # Unknown type | |
1271 | continue | |
1272 | name = str(parentstr) + "." + str(sysctl.oid_name) | |
1273 | print("%s = %d (Default value is %d)" % (name, dereference(value), spec.original_value)) | |
fe8ab488 | 1274 | |
39236c6e A |
1275 | from memory import * |
1276 | from process import * | |
cb323159 | 1277 | from ipc import * |
39236c6e A |
1278 | from pmap import * |
1279 | from ioreg import * | |
1280 | from mbufs import * | |
1281 | from net import * | |
d9a64523 | 1282 | from skywalk import * |
39236c6e A |
1283 | from kdp import * |
1284 | from userspace import * | |
1285 | from pci import * | |
1286 | from misc import * | |
1287 | from apic import * | |
1288 | from scheduler import * | |
fe8ab488 A |
1289 | from structanalyze import * |
1290 | from ipcimportancedetail import * | |
1291 | from bank import * | |
d9a64523 | 1292 | from turnstile import * |
5ba3f43e | 1293 | from kasan import * |
3e170ce0 A |
1294 | from kauth import * |
1295 | from waitq import * | |
1296 | from usertaskgdbserver import * | |
39037602 A |
1297 | from ktrace import * |
1298 | from pgtrace import * | |
1299 | from xnutriage import * | |
5ba3f43e | 1300 | from kevent import * |
d9a64523 | 1301 | from workqueue import * |
cb323159 | 1302 | from ulock import * |
5ba3f43e | 1303 | from ntstat import * |
a39ff7e2 | 1304 | from zonetriage import * |
cb323159 | 1305 | from sysreg import * |
c3c9b80d | 1306 | from counter import * |