1 import sys
, subprocess
, os
, re
, time
, getopt
, shlex
, xnudefines
3 from functools
import wraps
4 from ctypes
import c_ulonglong
as uint64_t
5 from ctypes
import c_void_p
as voidptr_t
8 from core
import caching
9 from core
.standard
import *
10 from core
.configuration
import *
11 from core
.kernelcore
import *
13 from core
.lazytarget
import *
17 """ Kernel Debugging macros for lldb.
18 Please make sure you read the README COMPLETELY BEFORE reading anything below.
19 It is very critical that you read coding guidelines in Section E in README file.
22 COMMON_HELP_STRING
= """
23 -h Show the help string for the command.
24 -c [always|auto|never|0|1]
25 Control the colorized output of certain commands
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.
33 # End Utility functions
34 # Debugging specific utility functions
36 #decorators. Not to be called directly.
38 def static_var(var_name
, initial_value
):
40 setattr(obj
, var_name
, initial_value
)
44 def header(initial_value
):
46 setattr(obj
, 'header', initial_value
)
50 # holds type declarations done by xnu.
51 #DONOTTOUCHME: Exclusive use of lldb_type_summary only.
52 lldb_summary_definitions
= {}
53 def lldb_type_summary(types_list
):
54 """ A function decorator to register a summary for a type in lldb.
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.
58 def _get_summary(obj
):
59 def _internal_summary_function(lldbval
, internal_dict
):
61 if internal_dict
!= None and len(obj
.header
) > 0 :
62 out_string
+= "\n" + obj
.header
+"\n"
63 out_string
+= obj( core
.value(lldbval
) )
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
__
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
83 #global cache of documentation for lldb commands exported by this module
84 #DONOTTOUCHME: Exclusive use of lldb_command only.
85 lldb_command_documentation
= {}
87 def lldb_command(cmd_name
, option_string
= '', fancy
=False):
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.
90 option_string - str: getopt like option string. Only CAPITAL LETTER options allowed.
91 see README on Customizing command options.
92 fancy - bool : whether the command will receive an 'O' object to do fancy output (tables, indent, color)
94 if option_string
!= option_string
.upper():
95 raise RuntimeError("Cannot setup command with lowercase option args. %s" % option_string
)
98 def _internal_command_function(debugger
, command
, result
, internal_dict
):
99 global config
, lldb_run_command_state
100 stream
= CommandOutput(cmd_name
, result
)
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
)
105 result
.SetImmediateOutputFile(sys
.__stdout
__)
107 command_args
= shlex
.split(command
)
108 lldb
.debugger
.HandleCommand('type category disable kernel' )
109 def_verbose_level
= config
['verbosity']
112 stream
.setOptions(command_args
, option_string
)
113 if stream
.verbose_level
!= 0:
114 config
['verbosity'] += stream
.verbose_level
115 with RedirectStdStreams(stdout
=stream
) :
116 args
= { 'cmd_args': stream.target_cmd_args }
118 args
['cmd_options'] = stream
.target_cmd_options
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())
129 except Exception as exc
:
130 if not config
['debug']:
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. ************
139 if config
['showTypeSummary']:
140 lldb
.debugger
.HandleCommand('type category enable kernel' )
142 if stream
.pluginRequired
:
143 plugin
= LoadXNUPlugin(stream
.pluginName
)
145 print "Could not load plugins."+stream
.pluginName
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()
152 #restore the verbose level after command is complete
153 config
['verbosity'] = def_verbose_level
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
]
162 print "ERROR: Cannot register command({:s}) without documentation".format(cmd_name
)
164 obj
.__doc
__ += "\n" + COMMON_HELP_STRING
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
)
173 def wrapped_fun(cmd_args
=None, cmd_options
={}, O
=None):
175 stream
= CommandOutput(cmd_name
, fhandle
=sys
.stdout
)
176 with RedirectStdStreams(stdout
=stream
):
177 return obj(cmd_args
, cmd_options
, stream
)
179 return obj(cmd_args
, cmd_options
, O
)
184 def lldb_alias(alias_name
, cmd_line
):
185 """ define an alias in the lldb command line.
186 A programatic way of registering an alias. This basically does
187 (lldb)command alias alias_name "cmd_line"
189 lldb_alias('readphys16', 'readphys 16')
191 alias_name
= alias_name
.strip()
192 cmd_line
= cmd_line
.strip()
193 lldb
.debugger
.HandleCommand("command alias " + alias_name
+ " "+ cmd_line
)
195 def SetupLLDBTypeSummaries(reset
=False):
196 global lldb_summary_definitions
, MODULE_NAME
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
__
203 lldb
.debugger
.HandleCommand(lldb_cmd
)
204 if config
['showTypeSummary']:
205 lldb
.debugger
.HandleCommand("type category enable kernel")
207 lldb
.debugger
.HandleCommand("type category disable kernel")
211 def LoadXNUPlugin(name
):
212 """ Try to load a plugin from the plugins directory.
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
:
223 print "Plugin is not correctly implemented. Please read documentation on implementing plugins"
225 print "plugin not found :"+name
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)
233 ret_status
= result_data
[0]
234 ret_string
= result_data
[1]
235 ret_commands
= result_data
[2]
237 if ret_status
== False:
238 print "Plugin failed: " + 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
)
247 # holds tests registered with xnu.
248 #DONOTTOUCHME: Exclusive use of xnudebug_test only
249 lldb_command_tests
= {}
250 def xnudebug_test(test_name
):
251 """ A function decoratore to register a test with the framework. Each test is supposed to be of format
252 def Test<name>(kernel_target, config, lldb_obj, isConnected )
254 NOTE: The testname should start with "Test" else exception will be raised.
257 global lldb_command_tests
258 if obj
.__name
__.find("Test") != 0 :
259 print "Test name ", obj
.__name
__ , " should start with Test"
261 lldb_command_tests
[test_name
] = (test_name
, obj
.__name
__, obj
, obj
.__doc
__)
266 # End Debugging specific utility functions
267 # Kernel Debugging specific classes and accessor methods
269 # global access object for target kernel
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));
274 now to get 3rd int from 'arr' you'd do
276 GetObjectAtIndexFromArray(arr_val,2)
278 array_base : core.value - representing a pointer type (ex. base of type 'ipc_entry *')
279 index : int - 0 based index into the array
281 core.value : core.value of the same type as array_base_val but pointing to index'th element
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
)
287 obj
= kern
.GetValueFromAddress(obj_address
, array_base_val
.GetType().GetName())
288 return Cast(obj
, array_base_val
.GetType())
293 def GetLLDBThreadForKernelThread(thread_obj
):
294 """ Get a reference to lldb.SBThread representation for kernel thread.
296 thread_obj : core.cvalue - thread object of type thread_t
298 lldb.SBThread - lldb thread object for getting backtrace/registers etc.
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
))
309 raise RuntimeError("LLDB process does not support CreateOSPluginThread.")
310 sbthread
= lldb_process
.GetThreadByID(tid
)
312 if not sbthread
.IsValid():
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
))
317 def GetKextSymbolInfo(load_addr
):
318 """ Get a string descriptiong load_addr <kextname> + offset
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.
324 symbol_offset
= load_addr
325 kmod_val
= kern
.globals.kmod
326 if not kern
.arch
.startswith('arm64'):
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
)
333 return "{:#018x} {:s} + {:#x} \n".format(load_addr
, symbol_name
, symbol_offset
)
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()
340 if not cached_kext_info
:
341 return "{:#018x} ~ kext info not available. please run 'showallkexts' once ~ \n".format(load_addr
)
343 for kval
in cached_kext_info
:
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
350 return "{:#018x} {:s} + {:#x} \n".format(load_addr
, symbol_name
, symbol_offset
)
352 def GetThreadBackTrace(thread_obj
, verbosity
= vHUMAN
, prefix
= ""):
353 """ Get a string to display back trace for a thread.
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?
360 str - a multi line string showing each frame in backtrace.
362 is_continuation
= not bool(unsigned(thread_obj
.kernel_stack
))
363 thread_val
= GetLLDBThreadForKernelThread(thread_obj
)
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
)
372 out_string
+= prefix
+ "continuation ="
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()
382 if iteration
== 0 and not is_continuation
:
383 out_string
+= prefix
+"stacktop = {:#018x}\n".format(frame_p
)
386 # No debug info for 'function'.
388 if not is_continuation
:
389 out_string
+= "{fp:#018x} ".format(fp
= frame_p
)
391 symbol
= frame
.GetSymbol()
393 out_string
+= GetKextSymbolInfo(load_addr
)
395 file_addr
= addr
.GetFileAddress()
396 start_addr
= symbol
.GetStartAddress().GetFileAddress()
397 symbol_name
= symbol
.GetName()
398 symbol_offset
= file_addr
- start_addr
399 out_string
+= "{addr:#018x} {mod}`{symbol} + {offset:#x} \n".format(addr
=load_addr
,
400 mod
=mod_name
, symbol
=symbol_name
, offset
=symbol_offset
)
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
))
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
,
415 file=file_name
, line
=line_num
,
416 args
="(" + (str(frame
.arguments
).replace("\n", ", ") if len(frame
.arguments
) > 0 else "void") + ")")
419 last_frame_p
= frame_p
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","")
426 def GetSourceInformationForAddress(addr
):
427 """ convert and address to function +offset information.
428 params: addr - int address in the binary to be symbolicated
429 returns: string of format "0xaddress: function + offset"
431 symbols
= kern
.SymbolicateFromAddress(addr
)
432 format_string
= "{0:#018x} <{1:s} + {2:#0x}>"
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
)
443 def GetFrameLocalVariable(variable_name
, frame_no
=0):
444 """ Find a local variable by name
446 variable_name: str - name of variable to search for
448 core.value - if the variable is found.
449 None - if not found or not Valid
453 lldb_SBThread
= LazyTarget
.GetProcess().GetSelectedThread()
454 frame
= lldb_SBThread
.GetSelectedFrame()
456 frame
= lldb_SBThread
.GetFrameAtIndex(frame_no
)
458 sbval
= frame
.FindVariable(variable_name
)
459 if sbval
and sbval
.IsValid():
460 retval
= core
.cvalue
.value(sbval
)
463 # Begin Macros for kernel debugging
465 @lldb_command('kgmhelp')
466 def KernelDebugCommandsHelp(cmd_args
=None):
467 """ Show a list of registered commands for kenel debugging.
469 global lldb_command_documentation
470 print "List of commands provided by " + MODULE_NAME
+ " for kernel debugging."
471 cmds
= lldb_command_documentation
.keys()
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())
477 print " {0: <20s} - {1}".format(cmd
, "No help string found.")
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.'
484 @lldb_command('showraw')
485 def ShowRawCommand(cmd_args
=None):
486 """ A command to disable the kernel summaries and show data as seen by the system.
487 This is useful when trying to read every field of a struct as compared to brief summary
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' )
495 @lldb_command('xnudebug')
496 def XnuDebugCommand(cmd_args
=None):
497 """ command interface for operating on the xnu macros. Allowed commands are as follows
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)
502 remove any cached data held in static or dynamic data cache.
503 usage: xnudebug flushcache
505 Start running registered test with <name> from various modules.
506 usage: xnudebug test <name> (eg. test_memstats)
508 Go through all registered tests and run them
510 Toggle state of debug configuration flag.
513 command_args
= cmd_args
514 if len(command_args
) == 0:
515 raise ArgumentError("No command specified.")
516 supported_subcommands
= ['debug', 'reload', 'test', 'testall', 'flushcache']
517 subcommand
= GetLongestMatchOption(command_args
[0], supported_subcommands
, True)
519 if len(subcommand
) == 0:
520 raise ArgumentError("Subcommand (%s) is not a valid command. " % str(command_args
[0]))
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. "
531 if subcommand
== 'flushcache':
532 print "Current size of cache: {}".format(caching
.GetSizeOfCache())
533 caching
.ClearAllCache()
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
__
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)
547 print "[PASSED] {:s}".format(test_name
)
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])
555 if test
[2](kern
, config
, lldb
, True) :
556 print "[PASSED] {:s}".format(test
[0])
558 print "[FAILED] {:s}".format(test
[0])
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():
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
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
588 if not panic_stackshot_addr
:
589 print "No panic stackshot available (invalid addr)"
592 if not panic_stackshot_len
:
593 print "No panic stackshot available (zero length)"
596 ts
= int(time
.time())
597 ss_binfile
= "/tmp/panic_%d.bin" % ts
598 ss_ipsfile
= "/tmp/stacks_%d.ips" % ts
600 if not SaveDataToFile(panic_stackshot_addr
, panic_stackshot_len
, ss_binfile
, None):
601 print "Failed to save stackshot binary data to file"
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
))
609 print "Saved ips stackshot file as %s" % ss_ipsfile
612 print "Failed to run command: exit code: %d, SO: %s SE: %s" % (c
, so
, se
)
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
)
630 if panic_log_magic
!= 0 and panic_log_magic
!= expected_panic_magic
:
631 warn_str
+= "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic
,
632 expected_panic_magic
)
635 print "\n %s" % warn_str
636 if panic_log_begin_offset
== 0:
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
)
643 print "No panic stackshot available"
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
)])
649 panic_log_curindex
+= 1
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
)])
656 other_log_curindex
+= 1
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
672 panic_stackshot_addr
= unsigned(panic_header
) + unsigned(panic_header
.mph_stackshot_offset
)
673 panic_stackshot_len
= unsigned(panic_header
.mph_stackshot_len
)
674 panic_header_flags
= unsigned(panic_header
.mph_panic_flags
)
679 if panic_log_magic
!= 0 and panic_log_magic
!= expected_panic_magic
:
680 warn_str
+= "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic
,
681 expected_panic_magic
)
684 print "\n %s" % warn_str
685 if panic_log_begin_offset
== 0:
688 if "-S" in cmd_options
:
689 if panic_header_flags
& xnudefines
.MACOS_PANIC_STACKSHOT_SUCCEEDED_FLAG
:
690 ProcessPanicStackshot(panic_stackshot_addr
, panic_stackshot_len
)
692 print "No panic stackshot available"
694 panic_log_curindex
= 0
695 while panic_log_curindex
< panic_log_len
:
696 p_char
= str(panic_buf
[(panic_log_begin_offset
+ panic_log_curindex
)])
698 panic_log_curindex
+= 1
700 if other_log_begin_offset
!= 0:
701 other_log_curindex
= 0
702 while other_log_curindex
< other_log_len
:
703 p_char
= str(panic_buf
[(other_log_begin_offset
+ other_log_curindex
)])
705 other_log_curindex
+= 1
710 def ParseAURRPanicLog(panic_header
, cmd_options
={}):
716 0x4: "STRAIGHT TO S5",
719 expected_panic_magic
= xnudefines
.AURR_PANIC_MAGIC
721 panic_buf
= Cast(panic_header
, 'char *')
724 # This line will blow up if there's not type info for this struct (older kernel)
725 # We fall back to manual parsing below
726 aurr_panic_header
= Cast(panic_header
, 'struct efi_aurr_panic_header *')
727 panic_log_magic
= unsigned(aurr_panic_header
.efi_aurr_magic
)
728 panic_log_version
= unsigned(aurr_panic_header
.efi_aurr_version
)
729 panic_log_reset_cause
= unsigned(aurr_panic_header
.efi_aurr_reset_cause
)
730 panic_log_reset_log_offset
= unsigned(aurr_panic_header
.efi_aurr_reset_log_offset
)
731 panic_log_reset_log_len
= unsigned(aurr_panic_header
.efi_aurr_reset_log_len
)
732 except Exception as e
:
733 print "*** Warning: kernel symbol file has no type information for 'struct efi_aurr_panic_header'..."
734 print "*** Warning: trying to manually parse..."
735 aurr_panic_header
= Cast(panic_header
, "uint32_t *")
736 panic_log_magic
= unsigned(aurr_panic_header
[0])
737 # panic_log_crc = unsigned(aurr_panic_header[1])
738 panic_log_version
= unsigned(aurr_panic_header
[2])
739 panic_log_reset_cause
= unsigned(aurr_panic_header
[3])
740 panic_log_reset_log_offset
= unsigned(aurr_panic_header
[4])
741 panic_log_reset_log_len
= unsigned(aurr_panic_header
[5])
743 if panic_log_magic
!= 0 and panic_log_magic
!= expected_panic_magic
:
744 print "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic
,
745 expected_panic_magic
)
748 print "AURR Panic Version: %d" % (panic_log_version
)
750 # When it comes time to extend this in the future, please follow the
751 # construct used below in ShowPanicLog()
752 if panic_log_version
in (xnudefines
.AURR_PANIC_VERSION
, xnudefines
.AURR_CRASHLOG_PANIC_VERSION
):
753 # AURR Report Version 1 (AURR/MacEFI) or 2 (Crashlog)
754 # see macefifirmware/Vendor/Apple/EfiPkg/AppleDebugSupport/Library/Debugger.h
755 print "Reset Cause: 0x%x (%s)" % (panic_log_reset_cause
, reset_cause
.get(panic_log_reset_cause
, "UNKNOWN"))
757 # Adjust panic log string length (cap to maximum supported values)
758 if panic_log_version
== xnudefines
.AURR_PANIC_VERSION
:
759 max_string_len
= panic_log_reset_log_len
and min(panic_log_reset_log_len
, xnudefines
.AURR_PANIC_STRING_LEN
) or 0
760 elif panic_log_version
== xnudefines
.AURR_CRASHLOG_PANIC_VERSION
:
761 max_string_len
= xnudefines
.CRASHLOG_PANIC_STRING_LEN
766 while panic_str_offset
< max_string_len
:
767 p_char
= str(panic_buf
[panic_log_reset_log_offset
+ panic_str_offset
])
769 panic_str_offset
+= 1
773 # Save Crashlog Binary Data (if available)
774 if "-S" in cmd_options
and panic_log_version
== xnudefines
.AURR_CRASHLOG_PANIC_VERSION
:
775 crashlog_binary_offset
= panic_log_reset_log_offset
+ xnudefines
.CRASHLOG_PANIC_STRING_LEN
776 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
778 if 0 == crashlog_binary_size
:
779 print "No crashlog data found..."
783 ts
= int(time
.time())
784 ss_binfile
= "/tmp/crashlog_%d.bin" % ts
786 if not SaveDataToFile(panic_buf
+ crashlog_binary_offset
, crashlog_binary_size
, ss_binfile
, None):
787 print "Failed to save crashlog binary data to file"
790 return ParseUnknownPanicLog(panic_header
, cmd_options
)
794 def ParseUnknownPanicLog(panic_header
, cmd_options
={}):
795 magic_ptr
= Cast(panic_header
, 'uint32_t *')
796 panic_log_magic
= dereference(magic_ptr
)
797 print "Unrecognized panic header format. Magic: 0x%x..." % unsigned(panic_log_magic
)
798 print "Panic region starts at 0x%08x" % int(panic_header
)
799 print "Hint: To dump this panic header in order to try manually parsing it, use this command:"
800 print " (lldb) memory read -fx -s4 -c64 0x%08x" % int(panic_header
)
801 print " ^ that will dump the first 256 bytes of the panic region"
802 ## TBD: Hexdump some bits here to allow folks to poke at the region manually?
806 @lldb_command('paniclog', 'SM')
807 def ShowPanicLog(cmd_args
=None, cmd_options
={}):
808 """ Display the paniclog information
809 usage: (lldb) paniclog
811 -v : increase verbosity
812 -S : parse stackshot data (if panic stackshot available)
813 -M : parse macOS panic area (print panic string (if available), and/or capture crashlog info)
816 if "-M" in cmd_options
:
817 if not hasattr(kern
.globals, "mac_panic_header"):
818 print "macOS panic data requested but unavailable on this device"
820 panic_header
= kern
.globals.mac_panic_header
821 # DEBUG HACK FOR TESTING
822 #panic_header = kern.GetValueFromAddress(0xfffffff054098000, "uint32_t *")
824 panic_header
= kern
.globals.panic_info
826 if hasattr(panic_header
, "eph_magic"):
827 panic_log_magic
= unsigned(panic_header
.eph_magic
)
828 elif hasattr(panic_header
, "mph_magic"):
829 panic_log_magic
= unsigned(panic_header
.mph_magic
)
831 print "*** Warning: unsure of panic header format, trying anyway"
832 magic_ptr
= Cast(panic_header
, 'uint32_t *')
833 panic_log_magic
= int(dereference(magic_ptr
))
835 if panic_log_magic
== 0:
840 int(xnudefines
.AURR_PANIC_MAGIC
) : ParseAURRPanicLog
,
841 int(xnudefines
.MACOS_PANIC_MAGIC
) : ParseMacOSPanicLog
,
842 int(xnudefines
.EMBEDDED_PANIC_MAGIC
) : ParseEmbeddedPanicLog
,
845 # Find the right parser (fall back to unknown parser above)
846 parser
= panic_parsers
.get(panic_log_magic
, ParseUnknownPanicLog
)
849 return parser(panic_header
, cmd_options
)
851 @lldb_command('showbootargs')
852 def ShowBootArgs(cmd_args
=None):
853 """ Display boot arguments passed to the target kernel
855 bootargs
= Cast(kern
.GetGlobalVariable('PE_state').bootArgs
, 'boot_args *')
856 bootargs_cmd
= bootargs
.CommandLine
857 print str(bootargs_cmd
)
859 @static_var("last_process_uniq_id", 1)
860 def GetDebuggerStopIDValue():
861 """ Create a unique session identifier.
863 int - a unique number identified by processid and stopid.
866 process_obj
= LazyTarget
.GetProcess()
867 if hasattr(process_obj
, "GetStopID"):
868 stop_id
= process_obj
.GetStopID()
870 if hasattr(process_obj
, 'GetUniqueID'):
871 proc_uniq_id
= process_obj
.GetUniqueID()
872 #FIXME <rdar://problem/13034329> forces us to do this twice
873 proc_uniq_id
= process_obj
.GetUniqueID()
875 GetDebuggerStopIDValue
.last_process_uniq_id
+=1
876 proc_uniq_id
= GetDebuggerStopIDValue
.last_process_uniq_id
+ 1
878 stop_id_str
= "{:d}:{:d}".format(proc_uniq_id
, stop_id
)
879 return hash(stop_id_str
)
881 # The initialization code to add your commands
882 _xnu_framework_init
= False
883 def __lldb_init_module(debugger
, internal_dict
):
884 global kern
, lldb_command_documentation
, config
, _xnu_framework_init
885 if _xnu_framework_init
:
887 _xnu_framework_init
= True
888 caching
._GetDebuggerSessionID
= GetDebuggerStopIDValue
889 debugger
.HandleCommand('type summary add --regex --summary-string "${var%s}" -C yes -p -v "char \[[0-9]*\]"')
890 debugger
.HandleCommand('type format add --format hex -C yes uintptr_t')
891 kern
= KernelTarget(debugger
)
892 if not hasattr(lldb
.SBValue
, 'GetValueAsAddress'):
893 warn_str
= "WARNING: lldb version is too old. Some commands may break. Please update to latest lldb."
894 if os
.isatty(sys
.__stdout
__.fileno()):
895 warn_str
= VT
.DarkRed
+ warn_str
+ VT
.Default
897 print "xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries."
899 __lldb_init_module(lldb
.debugger
, None)
901 @lldb_command("showlldbtypesummaries")
902 def ShowLLDBTypeSummaries(cmd_args
=[]):
903 """ Enable/Disable kernel type summaries. Default is disabled.
904 Usage: showlldbtypesummaries [enable|disable]
910 if len(cmd_args
) > 0 and cmd_args
[0].lower().find('disable') >=0:
912 config
['showTypeSummary'] = False
913 trailer_msg
= "Please run 'showlldbtypesummaries enable' to enable the summary feature."
915 config
['showTypeSummary'] = True
916 SetupLLDBTypeSummaries(True)
917 trailer_msg
= "Please run 'showlldbtypesummaries disable' to disable the summary feature."
918 lldb_run_command("type category "+ action
+" kernel")
919 print "Successfully "+action
+"d the kernel type summaries. %s" % trailer_msg
921 @lldb_command('walkqueue_head', 'S')
922 def WalkQueueHead(cmd_args
=[], cmd_options
={}):
923 """ walk a queue_head_t and list all members in it. Note this is for queue_head_t. refer to osfmk/kern/queue.h
924 Option: -S - suppress summary output.
925 Usage: (lldb) walkqueue_head <queue_entry *> <struct type> <fieldname>
926 ex: (lldb) walkqueue_head 0x7fffff80 "thread *" "task_threads"
929 global lldb_summary_definitions
931 raise ArgumentError("invalid arguments")
932 if len(cmd_args
) != 3:
933 raise ArgumentError("insufficient arguments")
934 queue_head
= kern
.GetValueFromAddress(cmd_args
[0], 'struct queue_entry *')
935 el_type
= cmd_args
[1]
936 field_name
= cmd_args
[2]
938 if el_type
in lldb_summary_definitions
:
940 if '-S' in cmd_options
:
943 for i
in IterateQueue(queue_head
, el_type
, field_name
):
945 print lldb_summary_definitions
[el_type
](i
)
947 print "{0: <#020x}".format(i
)
951 @lldb_command('walklist_entry', 'S')
952 def WalkList(cmd_args
=[], cmd_options
={}):
953 """ iterate over a list as defined with LIST_ENTRY in bsd/sys/queue.h
955 object addr - value : address of object
956 element_type - str : Type of the next element
957 field_name - str : Name of the field in next element's structure
959 Option: -S - suppress summary output.
960 Usage: (lldb) walklist_entry <obj with list_entry *> <struct type> <fieldname>
961 ex: (lldb) walklist_entry 0x7fffff80 "struct proc *" "p_sibling"
964 global lldb_summary_definitions
966 raise ArgumentError("invalid arguments")
967 if len(cmd_args
) != 3:
968 raise ArgumentError("insufficient arguments")
969 el_type
= cmd_args
[1]
970 queue_head
= kern
.GetValueFromAddress(cmd_args
[0], el_type
)
971 field_name
= cmd_args
[2]
974 if el_type
in lldb_summary_definitions
:
976 if '-S' in cmd_options
:
979 while unsigned(elt
) != 0:
981 elt
= elt
.__getattr
__(field_name
).le_next
983 print lldb_summary_definitions
[el_type
](i
)
985 print "{0: <#020x}".format(i
)
987 def trace_parse_Copt(Copt
):
988 """Parses the -C option argument and returns a list of CPUs
991 cpuList
= cpusOpt
.split(",")
993 for cpu_num_string
in cpuList
:
995 if '-' in cpu_num_string
:
996 parts
= cpu_num_string
.split('-')
997 if len(parts
) != 2 or not (parts
[0].isdigit() and parts
[1].isdigit()):
998 raise ArgumentError("Invalid cpu specification: %s" % cpu_num_string
)
999 firstRange
= int(parts
[0])
1000 lastRange
= int(parts
[1])
1001 if firstRange
>= kern
.globals.real_ncpus
or lastRange
>= kern
.globals.real_ncpus
:
1003 if lastRange
< firstRange
:
1004 raise ArgumentError("Invalid CPU range specified: `%s'" % cpu_num_string
)
1005 for cpu_num
in range(firstRange
, lastRange
+ 1):
1006 if cpu_num
not in chosen_cpus
:
1007 chosen_cpus
.append(cpu_num
)
1009 chosen_cpu
= int(cpu_num_string
)
1010 if chosen_cpu
< 0 or chosen_cpu
>= kern
.globals.real_ncpus
:
1012 if chosen_cpu
not in chosen_cpus
:
1013 chosen_cpus
.append(chosen_cpu
)
1015 raise ArgumentError("Invalid CPU number specified. Valid range is 0..%d" % (kern
.globals.real_ncpus
- 1))
1023 def Trace_cmd(cmd_args
=[], cmd_options
={}, headerString
=lambda:"", entryString
=lambda x
:"", ring
=[], entries_per_cpu
=0, max_backtraces
=0):
1024 """Generic trace dumper helper function
1027 if '-S' in cmd_options
:
1028 field_arg
= cmd_options
['-S']
1030 getattr(ring
[0][0], field_arg
)
1031 sort_key_field_name
= field_arg
1032 except AttributeError:
1033 raise ArgumentError("Invalid sort key field name `%s'" % field_arg
)
1035 sort_key_field_name
= 'start_time_abs'
1037 if '-C' in cmd_options
:
1038 chosen_cpus
= trace_parse_Copt(cmd_options
['-C'])
1040 chosen_cpus
= [x
for x
in range(kern
.globals.real_ncpus
)]
1043 limit_output_count
= int(cmd_options
['-N'])
1045 raise ArgumentError("Invalid output count `%s'" % cmd_options
['-N']);
1047 limit_output_count
= None
1049 reverse_sort
= '-R' in cmd_options
1050 backtraces
= '-B' in cmd_options
1052 # entries will be a list of 3-tuples, each holding the CPU on which the iotrace entry was collected,
1053 # the original ring index, and the iotrace entry.
1055 for x
in chosen_cpus
:
1056 ring_slice
= [(x
, y
, ring
[x
][y
]) for y
in range(entries_per_cpu
)]
1057 entries
.extend(ring_slice
)
1059 total_entries
= len(entries
)
1061 entries
.sort(key
=lambda x
: getattr(x
[IDX_RINGENTRY
], sort_key_field_name
), reverse
=reverse_sort
)
1063 if limit_output_count
is not None and limit_output_count
> total_entries
:
1064 print ("NOTE: Output count `%d' is too large; showing all %d entries" % (limit_output_count
, total_entries
));
1065 limit_output_count
= total_entries
1067 if len(chosen_cpus
) < kern
.globals.real_ncpus
:
1068 print "NOTE: Limiting to entries from cpu%s %s" % ("s" if len(chosen_cpus
) > 1 else "", str(chosen_cpus
))
1070 if limit_output_count
is not None and limit_output_count
< total_entries
:
1071 entries_to_display
= limit_output_count
1072 print "NOTE: Limiting to the %s" % ("first entry" if entries_to_display
== 1 else ("first %d entries" % entries_to_display
))
1074 entries_to_display
= total_entries
1076 print headerString()
1078 for x
in xrange(entries_to_display
):
1079 print entryString(entries
[x
])
1082 for btidx
in range(max_backtraces
):
1083 nextbt
= entries
[x
][IDX_RINGENTRY
].backtrace
[btidx
]
1086 print "\t" + GetSourceInformationForAddress(nextbt
)
1089 @lldb_command('iotrace', 'C:N:S:RB')
1090 def IOTrace_cmd(cmd_args
=[], cmd_options
={}):
1091 """ Prints the iotrace ring buffers for all CPUs by default.
1093 -B : Print backtraces for each ring entry
1094 -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a
1095 single CPU number or a range separated by a dash (e.g. "0-3"))
1096 -N <count> : Limit output to the first <count> entries (across all chosen CPUs)
1097 -R : Display results in reverse-sorted order (oldest first; default is newest-first)
1098 -S <sort_key_field_name> : Sort output by specified iotrace_entry_t field name (instead of by timestamp)
1100 MAX_IOTRACE_BACKTRACES
= 16
1102 if kern
.arch
!= "x86_64":
1103 print "Sorry, iotrace is an x86-only command."
1106 hdrString
= lambda : "%-19s %-8s %-10s %-20s SZ %-18s %-17s DATA" % (
1114 entryString
= lambda x
: "%-20u(%6u) %6s[%02d] %-20s %-2d 0x%016x 0x%016x 0x%x" % (
1115 x
[IDX_RINGENTRY
].start_time_abs
,
1116 x
[IDX_RINGENTRY
].duration
,
1117 "CPU%d" % x
[IDX_CPU
],
1119 str(x
[IDX_RINGENTRY
].iotype
).split("=")[1].strip(),
1120 x
[IDX_RINGENTRY
].size
,
1121 x
[IDX_RINGENTRY
].vaddr
,
1122 x
[IDX_RINGENTRY
].paddr
,
1123 x
[IDX_RINGENTRY
].val
)
1125 Trace_cmd(cmd_args
, cmd_options
, hdrString
, entryString
, kern
.globals.iotrace_ring
, kern
.globals.iotrace_entries_per_cpu
, MAX_IOTRACE_BACKTRACES
)
1128 @lldb_command('ttrace', 'C:N:S:RB')
1129 def TrapTrace_cmd(cmd_args
=[], cmd_options
={}):
1130 """ Prints the iotrace ring buffers for all CPUs by default.
1132 -B : Print backtraces for each ring entry
1133 -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a
1134 single CPU number or a range separated by a dash (e.g. "0-3"))
1135 -N <count> : Limit output to the first <count> entries (across all chosen CPUs)
1136 -R : Display results in reverse-sorted order (oldest first; default is newest-first)
1137 -S <sort_key_field_name> : Sort output by specified traptrace_entry_t field name (instead of by timestamp)
1139 MAX_TRAPTRACE_BACKTRACES
= 8
1141 if kern
.arch
!= "x86_64":
1142 print "Sorry, ttrace is an x86-only command."
1145 hdrString
= lambda : "%-30s CPU#[RIDX] VECT INTERRUPTED_THREAD PREMLV INTRLV INTERRUPTED_PC" % (
1146 "START TIME (DURATION [ns])")
1147 entryString
= lambda x
: "%-20u(%6s) %8s[%02d] 0x%02x 0x%016x %6d %6d %s" % (
1148 x
[IDX_RINGENTRY
].start_time_abs
,
1149 str(x
[IDX_RINGENTRY
].duration
) if hex(x
[IDX_RINGENTRY
].duration
) != "0xffffffffffffffff" else 'inprog',
1150 "CPU%d" % x
[IDX_CPU
],
1152 int(x
[IDX_RINGENTRY
].vector
),
1153 x
[IDX_RINGENTRY
].curthread
,
1154 x
[IDX_RINGENTRY
].curpl
,
1155 x
[IDX_RINGENTRY
].curil
,
1156 GetSourceInformationForAddress(x
[IDX_RINGENTRY
].interrupted_pc
))
1158 Trace_cmd(cmd_args
, cmd_options
, hdrString
, entryString
, kern
.globals.traptrace_ring
,
1159 kern
.globals.traptrace_entries_per_cpu
, MAX_TRAPTRACE_BACKTRACES
)
1164 from memory
import *
1165 from process
import *
1171 from skywalk
import *
1173 from userspace
import *
1177 from scheduler
import *
1179 from structanalyze
import *
1180 from ipcimportancedetail
import *
1182 from turnstile
import *
1186 from usertaskgdbserver
import *
1187 from ktrace
import *
1188 from pgtrace
import *
1189 from xnutriage
import *
1190 from kevent
import *
1191 from workqueue
import *
1193 from ntstat
import *
1194 from zonetriage
import *
1195 from sysreg
import *