]>
Commit | Line | Data |
---|---|---|
39236c6e A |
1 | import sys, subprocess, os, re, time, getopt, shlex |
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 | ||
15 | MODULE_NAME=__name__ | |
16 | ||
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. | |
20 | """ | |
21 | ||
22 | # End Utility functions | |
23 | # Debugging specific utility functions | |
24 | ||
25 | #decorators. Not to be called directly. | |
26 | ||
27 | def static_var(var_name, initial_value): | |
28 | def _set_var(obj): | |
29 | setattr(obj, var_name, initial_value) | |
30 | return obj | |
31 | return _set_var | |
32 | ||
33 | def header(initial_value): | |
34 | def _set_header(obj): | |
35 | setattr(obj, 'header', initial_value) | |
36 | return obj | |
37 | return _set_header | |
38 | ||
39 | # holds type declarations done by xnu. | |
40 | #DONOTTOUCHME: Exclusive use of lldb_type_summary only. | |
41 | lldb_summary_definitions = {} | |
42 | def lldb_type_summary(types_list): | |
43 | """ A function decorator to register a summary for a type in lldb. | |
44 | params: types_list - [] an array of types that you wish to register a summary callback function. (ex. ['task *', 'task_t']) | |
45 | returns: Nothing. This is a decorator. | |
46 | """ | |
47 | def _get_summary(obj): | |
48 | def _internal_summary_function(lldbval, internal_dict): | |
49 | out_string= "" | |
50 | if internal_dict != None and len(obj.header) > 0 : | |
51 | out_string += "\n" + obj.header +"\n" | |
52 | out_string += obj( core.value(lldbval) ) | |
53 | return out_string | |
54 | ||
55 | myglobals = globals() | |
56 | summary_function_name = "LLDBSummary" + obj.__name__ | |
57 | myglobals[summary_function_name] = _internal_summary_function | |
58 | summary_function = myglobals[summary_function_name] | |
59 | summary_function.__doc__ = obj.__doc__ | |
60 | ||
61 | global lldb_summary_definitions | |
62 | for single_type in types_list: | |
63 | if config['showTypeSummary']: | |
64 | if single_type in lldb_summary_definitions.keys(): | |
65 | lldb.debugger.HandleCommand("type summary delete --category kernel \""+ single_type + "\"") | |
66 | lldb.debugger.HandleCommand("type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + "." + summary_function_name) | |
67 | lldb_summary_definitions[single_type] = obj | |
68 | ||
69 | return obj | |
70 | return _get_summary | |
71 | ||
72 | #global cache of documentation for lldb commands exported by this module | |
73 | #DONOTTOUCHME: Exclusive use of lldb_command only. | |
74 | lldb_command_documentation = {} | |
75 | ||
76 | def lldb_command(cmd_name, option_string = ''): | |
77 | """ A function decorator to define a command with namd 'cmd_name' in the lldb scope to call python function. | |
78 | params: cmd_name - str : name of command to be set in lldb prompt. | |
79 | option_string - str: getopt like option string. Only CAPITAL LETTER options allowed. | |
80 | see README on Customizing command options. | |
81 | """ | |
82 | if option_string != option_string.upper(): | |
83 | raise RuntimeError("Cannot setup command with lowercase option args. %s" % option_string) | |
84 | ||
85 | def _cmd(obj): | |
86 | def _internal_command_function(debugger, command, result, internal_dict): | |
87 | global config, lldb_run_command_state | |
88 | stream = CommandOutput(result) | |
89 | # need to avoid printing on stdout if called from lldb_run_command. | |
90 | if 'active' in lldb_run_command_state and lldb_run_command_state['active']: | |
91 | debuglog('Running %s from lldb_run_command' % command) | |
92 | else: | |
93 | result.SetImmediateOutputFile(sys.__stdout__) | |
94 | ||
95 | command_args = shlex.split(command) | |
96 | lldb.debugger.HandleCommand('type category disable kernel' ) | |
97 | def_verbose_level = config['verbosity'] | |
98 | ||
99 | try: | |
100 | stream.setOptions(command_args, option_string) | |
101 | if stream.verbose_level != 0: | |
fe8ab488 | 102 | config['verbosity'] += stream.verbose_level |
39236c6e A |
103 | with RedirectStdStreams(stdout=stream) : |
104 | if option_string: | |
105 | obj(cmd_args=stream.target_cmd_args, cmd_options=stream.target_cmd_options) | |
106 | else: | |
107 | obj(cmd_args=stream.target_cmd_args) | |
108 | except KeyboardInterrupt: | |
109 | print "Execution interrupted by user" | |
110 | except ArgumentError as arg_error: | |
111 | if str(arg_error) != "HELP": | |
112 | print "Argument Error: " + str(arg_error) | |
113 | print "{0:s}:\n {1:s}".format(cmd_name, obj.__doc__.strip()) | |
114 | return False | |
115 | except Exception as exc: | |
116 | if not config['debug']: | |
117 | print """ | |
118 | ************ LLDB found an exception ************ | |
119 | There has been an uncaught exception. A possible cause could be that remote connection has been disconnected. | |
120 | However, it is recommended that you report the exception to lldb/kernel debugging team about it. | |
121 | ************ Please run 'xnudebug debug enable' to start collecting logs. ************ | |
122 | """ | |
123 | raise | |
124 | ||
125 | if config['showTypeSummary']: | |
126 | lldb.debugger.HandleCommand('type category enable kernel' ) | |
127 | ||
128 | if stream.pluginRequired : | |
129 | plugin = LoadXNUPlugin(stream.pluginName) | |
130 | if plugin == None : | |
131 | print "Could not load plugins."+stream.pluginName | |
132 | return | |
133 | plugin.plugin_init(kern, config, lldb, kern.IsDebuggerConnected()) | |
134 | return_data = plugin.plugin_execute(cmd_name, result.GetOutput()) | |
135 | ProcessXNUPluginResult(return_data) | |
136 | plugin.plugin_cleanup() | |
137 | ||
138 | #restore the verbose level after command is complete | |
139 | config['verbosity'] = def_verbose_level | |
140 | ||
141 | return | |
142 | ||
143 | myglobals = globals() | |
144 | command_function_name = obj.__name__+"Command" | |
145 | myglobals[command_function_name] = _internal_command_function | |
146 | command_function = myglobals[command_function_name] | |
147 | if not obj.__doc__ : | |
148 | print "ERROR: Cannot register command({:s}) without documentation".format(cmd_name) | |
149 | return obj | |
150 | command_function.__doc__ = obj.__doc__ | |
151 | global lldb_command_documentation | |
152 | if cmd_name in lldb_command_documentation: | |
153 | lldb.debugger.HandleCommand("command script delete "+cmd_name) | |
154 | lldb_command_documentation[cmd_name] = (obj.__name__, obj.__doc__.lstrip(), option_string) | |
155 | lldb.debugger.HandleCommand("command script add -f " + MODULE_NAME + "." + command_function_name + " " + cmd_name) | |
156 | return obj | |
157 | return _cmd | |
158 | ||
159 | def lldb_alias(alias_name, cmd_line): | |
160 | """ define an alias in the lldb command line. | |
161 | A programatic way of registering an alias. This basically does | |
162 | (lldb)command alias alias_name "cmd_line" | |
163 | ex. | |
164 | lldb_alias('readphys16', 'readphys 16') | |
165 | """ | |
166 | alias_name = alias_name.strip() | |
167 | cmd_line = cmd_line.strip() | |
168 | lldb.debugger.HandleCommand("command alias " + alias_name + " "+ cmd_line) | |
169 | ||
170 | def SetupLLDBTypeSummaries(reset=False): | |
171 | global lldb_summary_definitions, MODULE_NAME | |
172 | if reset == True: | |
173 | lldb.debugger.HandleCommand("type category delete kernel ") | |
174 | for single_type in lldb_summary_definitions.keys(): | |
175 | summary_function = lldb_summary_definitions[single_type] | |
176 | lldb_cmd = "type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + ".LLDBSummary" + summary_function.__name__ | |
177 | debuglog(lldb_cmd) | |
178 | lldb.debugger.HandleCommand(lldb_cmd) | |
179 | if config['showTypeSummary']: | |
180 | lldb.debugger.HandleCommand("type category enable kernel") | |
181 | else: | |
182 | lldb.debugger.HandleCommand("type category disable kernel") | |
183 | ||
184 | return | |
185 | ||
186 | def LoadXNUPlugin(name): | |
187 | """ Try to load a plugin from the plugins directory. | |
188 | """ | |
189 | retval = None | |
190 | name=name.strip() | |
191 | try: | |
192 | module_obj = __import__('plugins.'+name, globals(), locals(), [], -1) | |
193 | module_obj = module_obj.__dict__[name] | |
194 | defs = dir(module_obj) | |
195 | if 'plugin_init' in defs and 'plugin_execute' in defs and 'plugin_cleanup' in defs: | |
196 | retval = module_obj | |
197 | else: | |
198 | print "Plugin is not correctly implemented. Please read documentation on implementing plugins" | |
199 | except: | |
200 | print "plugin not found :"+name | |
201 | ||
202 | return retval | |
203 | ||
204 | def ProcessXNUPluginResult(result_data): | |
205 | """ Look at the returned data from plugin and see if anymore actions are required or not | |
206 | params: result_data - list of format (status, out_string, more_commands) | |
207 | """ | |
208 | ret_status = result_data[0] | |
209 | ret_string = result_data[1] | |
210 | ret_commands = result_data[2] | |
211 | ||
212 | if ret_status == False: | |
213 | print "Plugin failed: " + ret_string | |
214 | return | |
215 | print ret_string | |
216 | if len(ret_commands) >= 0: | |
217 | for cmd in ret_commands: | |
218 | print "Running command on behalf of plugin:" + cmd | |
219 | lldb.debugger.HandleCommand(cmd) | |
220 | return | |
221 | ||
222 | # holds tests registered with xnu. | |
223 | #DONOTTOUCHME: Exclusive use of xnudebug_test only | |
224 | lldb_command_tests = {} | |
225 | def xnudebug_test(test_name): | |
226 | """ A function decoratore to register a test with the framework. Each test is supposed to be of format | |
227 | def Test<name>(kernel_target, config, lldb_obj, isConnected ) | |
228 | ||
229 | NOTE: The testname should start with "Test" else exception will be raised. | |
230 | """ | |
231 | def _test(obj): | |
232 | global lldb_command_tests | |
233 | if obj.__name__.find("Test") != 0 : | |
234 | print "Test name ", obj.__name__ , " should start with Test" | |
235 | raise ValueError | |
236 | lldb_command_tests[test_name] = (test_name, obj.__name__, obj, obj.__doc__) | |
237 | return obj | |
238 | return _test | |
239 | ||
240 | ||
241 | # End Debugging specific utility functions | |
242 | # Kernel Debugging specific classes and accessor methods | |
243 | ||
244 | # global access object for target kernel | |
245 | ||
246 | def GetObjectAtIndexFromArray(array_base, index): | |
247 | """ Subscript indexing for arrays that are represented in C as pointers. | |
248 | for ex. int *arr = malloc(20*sizeof(int)); | |
249 | now to get 3rd int from 'arr' you'd do | |
250 | arr[2] in C | |
251 | GetObjectAtIndexFromArray(arr_val,2) | |
252 | params: | |
253 | array_base : core.value - representing a pointer type (ex. base of type 'ipc_entry *') | |
254 | index : int - 0 based index into the array | |
255 | returns: | |
256 | core.value : core.value of the same type as array_base_val but pointing to index'th element | |
257 | """ | |
258 | array_base_val = array_base.GetSBValue() | |
259 | base_address = array_base_val.GetValueAsUnsigned() | |
260 | size = array_base_val.GetType().GetPointeeType().GetByteSize() | |
261 | obj_address = base_address + (index * size) | |
262 | obj = kern.GetValueFromAddress(obj_address, array_base_val.GetType().GetName()) | |
263 | return Cast(obj, array_base_val.GetType()) | |
264 | ||
265 | ||
266 | kern = None | |
267 | ||
268 | def GetLLDBThreadForKernelThread(thread_obj): | |
269 | """ Get a reference to lldb.SBThread representation for kernel thread. | |
270 | params: | |
271 | thread_obj : core.cvalue - thread object of type thread_t | |
272 | returns | |
273 | lldb.SBThread - lldb thread object for getting backtrace/registers etc. | |
274 | """ | |
275 | tid = unsigned(thread_obj.thread_id) | |
276 | lldb_process = LazyTarget.GetProcess() | |
277 | sbthread = lldb_process.GetThreadByID(tid) | |
278 | if not sbthread.IsValid(): | |
279 | # in case lldb doesnt know about this thread, create one | |
280 | if hasattr(lldb_process, "CreateOSPluginThread"): | |
281 | debuglog("creating os plugin thread on the fly for {0:d} 0x{1:x}".format(tid, thread_obj)) | |
282 | lldb_process.CreateOSPluginThread(tid, unsigned(thread_obj)) | |
283 | else: | |
284 | raise RuntimeError("LLDB process does not support CreateOSPluginThread.") | |
285 | sbthread = lldb_process.GetThreadByID(tid) | |
286 | ||
287 | if not sbthread.IsValid(): | |
fe8ab488 A |
288 | 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)) |
289 | ||
39236c6e A |
290 | return sbthread |
291 | ||
292 | def GetThreadBackTrace(thread_obj, verbosity = vHUMAN, prefix = ""): | |
293 | """ Get a string to display back trace for a thread. | |
294 | params: | |
295 | thread_obj - core.cvalue : a thread object of type thread_t. | |
296 | verbosity - int : either of vHUMAN, vSCRIPT or vDETAIL to describe the verbosity of output | |
297 | prefix - str : a string prefix added before the line for each frame. | |
298 | isContinuation - bool : is thread a continuation? | |
299 | returns: | |
300 | str - a multi line string showing each frame in backtrace. | |
301 | """ | |
302 | is_continuation = not bool(unsigned(thread_obj.kernel_stack)) | |
303 | thread_val = GetLLDBThreadForKernelThread(thread_obj) | |
304 | out_string = "" | |
305 | kernel_stack = unsigned(thread_obj.kernel_stack) | |
306 | reserved_stack = unsigned(thread_obj.reserved_stack) | |
307 | if not is_continuation: | |
308 | if kernel_stack and reserved_stack: | |
309 | out_string += prefix + "reserved_stack = {:#018x}\n".format(reserved_stack) | |
310 | out_string += prefix + "kernel_stack = {:#018x}\n".format(kernel_stack) | |
311 | else: | |
312 | out_string += prefix + "continuation =" | |
313 | iteration = 0 | |
314 | last_frame_p = 0 | |
315 | for frame in thread_val.frames: | |
316 | addr = frame.GetPCAddress() | |
317 | load_addr = addr.GetLoadAddress(LazyTarget.GetTarget()) | |
318 | function = frame.GetFunction() | |
319 | frame_p = frame.GetFP() | |
320 | mod_name = frame.GetModule().GetFileSpec().GetFilename() | |
321 | ||
322 | if iteration == 0 and not is_continuation: | |
323 | out_string += prefix +"stacktop = {:#018x}\n".format(frame_p) | |
fe8ab488 | 324 | |
39236c6e A |
325 | if not function: |
326 | # No debug info for 'function'. | |
39236c6e A |
327 | out_string += prefix |
328 | if not is_continuation: | |
329 | out_string += "{fp:#018x} ".format(fp = frame_p) | |
fe8ab488 A |
330 | |
331 | symbol = frame.GetSymbol() | |
332 | if not symbol: | |
333 | symbol_name = "None" | |
334 | symbol_offset = load_addr | |
335 | kmod_val = kern.globals.kmod | |
336 | for kval in IterateLinkedList(kmod_val, 'next'): | |
337 | if load_addr >= unsigned(kval.address) and \ | |
338 | load_addr <= (unsigned(kval.address) + unsigned(kval.size)): | |
339 | symbol_name = kval.name | |
340 | symbol_offset = load_addr - unsigned(kval.address) | |
341 | break | |
342 | out_string += "{:#018x} {:s} + {:#x} \n".format(load_addr, symbol_name, symbol_offset) | |
343 | else: | |
344 | file_addr = addr.GetFileAddress() | |
345 | start_addr = symbol.GetStartAddress().GetFileAddress() | |
346 | symbol_name = symbol.GetName() | |
347 | symbol_offset = file_addr - start_addr | |
348 | out_string += "{addr:#018x} {mod}`{symbol} + {offset:#x} \n".format(addr=load_addr, | |
349 | mod=mod_name, symbol=symbol_name, offset=symbol_offset) | |
39236c6e A |
350 | else: |
351 | # Debug info is available for 'function'. | |
352 | func_name = frame.GetFunctionName() | |
353 | file_name = frame.GetLineEntry().GetFileSpec().GetFilename() | |
354 | line_num = frame.GetLineEntry().GetLine() | |
355 | func_name = '%s [inlined]' % func_name if frame.IsInlined() else func_name | |
356 | if is_continuation and frame.IsInlined(): | |
357 | debuglog("Skipping frame for thread {:#018x} since its inlined".format(thread_obj)) | |
358 | continue | |
359 | out_string += prefix | |
360 | if not is_continuation: | |
361 | out_string += "{fp:#018x} ".format(fp=frame_p) | |
362 | out_string += "{addr:#018x} {func}{args} \n".format(addr=load_addr, | |
363 | func=func_name, | |
364 | file=file_name, line=line_num, | |
365 | args="(" + (str(frame.arguments).replace("\n", ", ") if len(frame.arguments) > 0 else "void") + ")") | |
366 | iteration += 1 | |
367 | if frame_p: | |
368 | last_frame_p = frame_p | |
369 | ||
370 | if not is_continuation and last_frame_p: | |
371 | out_string += prefix + "stackbottom = {:#018x}".format(last_frame_p) | |
372 | out_string = out_string.replace("variable not available","") | |
373 | return out_string | |
374 | ||
375 | def GetSourceInformationForAddress(addr): | |
376 | """ convert and address to function +offset information. | |
377 | params: addr - int address in the binary to be symbolicated | |
378 | returns: string of format "0xaddress: function + offset" | |
379 | """ | |
380 | symbols = kern.SymbolicateFromAddress(addr) | |
381 | format_string = "{0:#018x} <{1:s} + {2:#0x}>" | |
382 | offset = 0 | |
383 | function_name = "" | |
384 | if len(symbols) > 0: | |
385 | s = symbols[0] | |
386 | function_name = str(s.name) | |
387 | offset = addr - s.GetStartAddress().GetLoadAddress(LazyTarget.GetTarget()) | |
388 | if function_name == "": | |
389 | function_name = "???" | |
390 | return format_string.format(addr, function_name, offset) | |
391 | ||
392 | def GetFrameLocalVariable(variable_name, frame_no=0): | |
393 | """ Find a local variable by name | |
394 | params: | |
395 | variable_name: str - name of variable to search for | |
396 | returns: | |
397 | core.value - if the variable is found. | |
398 | None - if not found or not Valid | |
399 | """ | |
400 | retval = None | |
401 | sbval = None | |
402 | lldb_SBThread = LazyTarget.GetProcess().GetSelectedThread() | |
403 | frame = lldb_SBThread.GetSelectedFrame() | |
404 | if frame_no : | |
405 | frame = lldb_SBThread.GetFrameAtIndex(frame_no) | |
406 | if frame : | |
407 | sbval = frame.FindVariable(variable_name) | |
408 | if sbval and sbval.IsValid(): | |
409 | retval = core.cvalue.value(sbval) | |
410 | return retval | |
411 | ||
412 | # Begin Macros for kernel debugging | |
413 | ||
414 | @lldb_command('kgmhelp') | |
415 | def KernelDebugCommandsHelp(cmd_args=None): | |
416 | """ Show a list of registered commands for kenel debugging. | |
417 | """ | |
418 | global lldb_command_documentation | |
419 | print "List of commands provided by " + MODULE_NAME + " for kernel debugging." | |
420 | cmds = lldb_command_documentation.keys() | |
421 | cmds.sort() | |
422 | for cmd in cmds: | |
423 | if type(lldb_command_documentation[cmd][-1]) == type(""): | |
424 | print " {0: <20s} - {1}".format(cmd , lldb_command_documentation[cmd][1].split("\n")[0].strip()) | |
425 | else: | |
426 | print " {0: <20s} - {1}".format(cmd , "No help string found.") | |
427 | print """ | |
428 | Each of the functions listed here accept the following common options. | |
429 | -h Show the help string for the command. | |
430 | -o <path/to/filename> The output of this command execution will be saved to file. Parser information or errors will | |
431 | not be sent to file though. eg /tmp/output.txt | |
432 | -s <filter_string> The "filter_string" param is parsed to python regex expression and each line of output | |
433 | will be printed/saved only if it matches the expression. | |
434 | -v [-v...] Each additional -v will increase the verbosity of the command. | |
435 | -p <plugin_name> Send the output of the command to plugin. Please see README for usage of plugins. | |
436 | ||
437 | Additionally, each command implementation may have more options. "(lldb) help <command> " will show these options. | |
438 | """ | |
439 | return None | |
440 | ||
441 | ||
442 | @lldb_command('showraw') | |
443 | def ShowRawCommand(cmd_args=None): | |
444 | """ A command to disable the kernel summaries and show data as seen by the system. | |
445 | This is useful when trying to read every field of a struct as compared to brief summary | |
446 | """ | |
447 | command = " ".join(cmd_args) | |
448 | lldb.debugger.HandleCommand('type category disable kernel' ) | |
449 | lldb.debugger.HandleCommand( command ) | |
450 | lldb.debugger.HandleCommand('type category enable kernel' ) | |
451 | ||
452 | ||
453 | @lldb_command('xnudebug') | |
454 | def XnuDebugCommand(cmd_args=None): | |
455 | """ command interface for operating on the xnu macros. Allowed commands are as follows | |
456 | reload: | |
457 | Reload a submodule from the xnu/tools/lldb directory. Do not include the ".py" suffix in modulename. | |
458 | usage: xnudebug reload <modulename> (eg. memory, process, stats etc) | |
459 | test: | |
460 | Start running registered test with <name> from various modules. | |
461 | usage: xnudebug test <name> (eg. test_memstats) | |
462 | testall: | |
463 | Go through all registered tests and run them | |
464 | debug: | |
465 | Toggle state of debug configuration flag. | |
466 | """ | |
467 | global config | |
468 | command_args = cmd_args | |
469 | if len(command_args) == 0: | |
470 | raise ArgumentError("No command specified.") | |
471 | supported_subcommands = ['debug', 'reload', 'test', 'testall'] | |
472 | subcommand = GetLongestMatchOption(command_args[0], supported_subcommands, True) | |
473 | ||
474 | if len(subcommand) == 0: | |
475 | raise ArgumentError("Subcommand (%s) is not a valid command. " % str(command_args[0])) | |
476 | ||
477 | subcommand = subcommand[0].lower() | |
478 | if subcommand == 'debug': | |
479 | if command_args[-1].lower().find('dis') >=0 and config['debug']: | |
480 | config['debug'] = False | |
481 | print "Disabled debug logging." | |
482 | elif command_args[-1].lower().find('dis') < 0 and not config['debug']: | |
483 | config['debug'] = True | |
484 | EnableLLDBAPILogging() # provided by utils.py | |
485 | print "Enabled debug logging. \nPlease run 'xnudebug debug disable' to disable it again. " | |
486 | ||
487 | if subcommand == 'reload': | |
488 | module_name = command_args[-1] | |
489 | if module_name in sys.modules: | |
490 | reload(sys.modules[module_name]) | |
491 | print module_name + " is reloaded from " + sys.modules[module_name].__file__ | |
492 | else: | |
493 | print "Unable to locate module named ", module_name | |
494 | if subcommand == 'testall': | |
495 | for test_name in lldb_command_tests.keys(): | |
496 | print "[BEGIN]", test_name | |
497 | res = lldb_command_tests[test_name][2](kern, config, lldb, True) | |
498 | if res: | |
499 | print "[PASSED] {:s}".format(test_name) | |
500 | else: | |
501 | print "[FAILED] {:s}".format(test_name) | |
502 | if subcommand == 'test': | |
503 | test_name = command_args[-1] | |
504 | if test_name in lldb_command_tests: | |
505 | test = lldb_command_tests[test_name] | |
506 | print "Running test {:s}".format(test[0]) | |
507 | if test[2](kern, config, lldb, True) : | |
508 | print "[PASSED] {:s}".format(test[0]) | |
509 | else: | |
510 | print "[FAILED] {:s}".format(test[0]) | |
511 | return "" | |
512 | else: | |
513 | print "No such test registered with name: {:s}".format(test_name) | |
514 | print "XNUDEBUG Available tests are:" | |
515 | for i in lldb_command_tests.keys(): | |
516 | print i | |
517 | return None | |
518 | ||
519 | return False | |
520 | ||
521 | @lldb_command('showversion') | |
522 | def ShowVersion(cmd_args=None): | |
523 | """ Read the kernel version string from a fixed address in low | |
524 | memory. Useful if you don't know which kernel is on the other end, | |
525 | and need to find the appropriate symbols. Beware that if you've | |
526 | loaded a symbol file, but aren't connected to a remote target, | |
527 | the version string from the symbol file will be displayed instead. | |
528 | This macro expects to be connected to the remote kernel to function | |
529 | correctly. | |
530 | ||
531 | """ | |
532 | print kern.version | |
533 | ||
534 | ||
3e170ce0 A |
535 | @lldb_command('paniclog', 'S') |
536 | def ShowPanicLog(cmd_args=None, cmd_options={}): | |
39236c6e | 537 | """ Display the paniclog information |
fe8ab488 A |
538 | usage: (lldb) paniclog |
539 | options: | |
540 | -v : increase verbosity | |
3e170ce0 | 541 | -S : parse stackshot data (if panic stackshot available) |
39236c6e | 542 | """ |
3e170ce0 A |
543 | binary_data_bytes_to_skip = 0 |
544 | if hasattr(kern.globals, "kc_panic_data"): | |
545 | binary_data_bytes_to_skip = unsigned(kern.globals.kc_panic_data.kcd_addr_end) - unsigned(kern.globals.kc_panic_data.kcd_addr_begin) | |
546 | if binary_data_bytes_to_skip > 0: | |
547 | binary_data_bytes_to_skip += sizeof("struct kcdata_item") | |
548 | else: | |
549 | binary_data_bytes_to_skip = 0 | |
550 | ||
551 | if "-S" in cmd_options: | |
552 | if hasattr(kern.globals, "kc_panic_data"): | |
553 | kc_data = unsigned(addressof(kern.globals.kc_panic_data)) | |
554 | ts = int(time.time()) | |
555 | ss_binfile = "/tmp/panic_%d.bin" % ts | |
556 | ss_ipsfile = "/tmp/stacks_%d.ips" % ts | |
557 | print "savekcdata 0x%x -O %s" % (kc_data, ss_binfile) | |
558 | SaveKCDataToFile(["0x%x" % kc_data], {"-O":ss_binfile}) | |
559 | self_path = str(__file__) | |
560 | base_dir_name = self_path[:self_path.rfind("/")] | |
561 | print "python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile) | |
562 | (c,so,se) = RunShellCommand("python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile)) | |
563 | if c == 0: | |
564 | print "Saved ips stackshot file as %s" % ss_ipsfile | |
565 | else: | |
566 | print "Failed to run command: exit code: %d, SO: %s SE: %s" % (c, so, se) | |
567 | else: | |
568 | print "kc_panic_data is unavailable for this kernel config." | |
569 | ||
fe8ab488 A |
570 | panic_buf = kern.globals.debug_buf_addr |
571 | panic_buf_start = unsigned(panic_buf) | |
39236c6e A |
572 | panic_buf_end = unsigned(kern.globals.debug_buf_ptr) |
573 | num_bytes = panic_buf_end - panic_buf_start | |
574 | if num_bytes == 0 : | |
575 | return | |
fe8ab488 | 576 | out_str = "" |
3e170ce0 A |
577 | warn_str = "" |
578 | num_print_bytes = 0 | |
579 | in_binary_data_region = False | |
580 | pos = 0 | |
581 | while pos < num_bytes: | |
582 | p_char = str(panic_buf[pos]) | |
fe8ab488 A |
583 | out_str += p_char |
584 | if p_char == '\n': | |
3e170ce0 A |
585 | if not in_binary_data_region: |
586 | num_print_bytes += 1 | |
587 | print out_str | |
588 | if (out_str.find("Data: BEGIN>>") >= 0): | |
589 | in_binary_data_region = True | |
590 | pos += binary_data_bytes_to_skip - 1 | |
591 | if (out_str.find("<<END") >= 0): | |
592 | in_binary_data_region = False | |
fe8ab488 | 593 | out_str = "" |
3e170ce0 A |
594 | if num_print_bytes > 4096 and config['verbosity'] == vHUMAN: |
595 | warn_str = "LLDBMacro Warning: The paniclog is too large. Trimming to 4096 bytes." | |
596 | warn_str += " If you wish to see entire log please use '-v' argument." | |
597 | break | |
598 | pos += 1 | |
599 | ||
fe8ab488 A |
600 | if warn_str: |
601 | print warn_str | |
3e170ce0 | 602 | |
39236c6e A |
603 | return |
604 | ||
605 | @lldb_command('showbootargs') | |
606 | def ShowBootArgs(cmd_args=None): | |
607 | """ Display boot arguments passed to the target kernel | |
608 | """ | |
609 | bootargs = Cast(kern.GetGlobalVariable('PE_state').bootArgs, 'boot_args *') | |
610 | bootargs_cmd = bootargs.CommandLine | |
611 | print str(bootargs_cmd) | |
612 | ||
613 | @static_var("last_process_uniq_id", 1) | |
614 | def GetDebuggerStopIDValue(): | |
615 | """ Create a unique session identifier. | |
616 | returns: | |
617 | int - a unique number identified by processid and stopid. | |
618 | """ | |
619 | stop_id = 0 | |
620 | process_obj = LazyTarget.GetProcess() | |
621 | if hasattr(process_obj, "GetStopID"): | |
622 | stop_id = process_obj.GetStopID() | |
623 | proc_uniq_id = 0 | |
624 | if hasattr(process_obj, 'GetUniqueID'): | |
625 | proc_uniq_id = process_obj.GetUniqueID() | |
626 | #FIXME <rdar://problem/13034329> forces us to do this twice | |
627 | proc_uniq_id = process_obj.GetUniqueID() | |
628 | else: | |
629 | GetDebuggerStopIDValue.last_process_uniq_id +=1 | |
630 | proc_uniq_id = GetDebuggerStopIDValue.last_process_uniq_id + 1 | |
631 | ||
632 | stop_id_str = "{:d}:{:d}".format(proc_uniq_id, stop_id) | |
633 | return hash(stop_id_str) | |
634 | ||
635 | # The initialization code to add your commands | |
636 | _xnu_framework_init = False | |
637 | def __lldb_init_module(debugger, internal_dict): | |
638 | global kern, lldb_command_documentation, config, _xnu_framework_init | |
639 | if _xnu_framework_init: | |
640 | return | |
641 | _xnu_framework_init = True | |
642 | caching._GetDebuggerSessionID = GetDebuggerStopIDValue | |
643 | debugger.HandleCommand('type summary add --regex --summary-string "${var%s}" -C yes -p -v "char \[[0-9]*\]"') | |
644 | debugger.HandleCommand('type format add --format hex -C yes uintptr_t') | |
645 | kern = KernelTarget(debugger) | |
646 | print "xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries." | |
647 | ||
648 | __lldb_init_module(lldb.debugger, None) | |
649 | ||
650 | @lldb_command("showlldbtypesummaries") | |
651 | def ShowLLDBTypeSummaries(cmd_args=[]): | |
652 | """ Enable/Disable kernel type summaries. Default is disabled. | |
653 | Usage: showlldbtypesummaries [enable|disable] | |
654 | default is enable | |
655 | """ | |
656 | global config | |
657 | action = "enable" | |
658 | trailer_msg = '' | |
659 | if len(cmd_args) > 0 and cmd_args[0].lower().find('disable') >=0: | |
660 | action = "disable" | |
661 | config['showTypeSummary'] = False | |
662 | trailer_msg = "Please run 'showlldbtypesummaries enable' to enable the summary feature." | |
663 | else: | |
664 | config['showTypeSummary'] = True | |
665 | SetupLLDBTypeSummaries(True) | |
666 | trailer_msg = "Please run 'showlldbtypesummaries disable' to disable the summary feature." | |
667 | lldb_run_command("type category "+ action +" kernel") | |
668 | print "Successfully "+action+"d the kernel type summaries. %s" % trailer_msg | |
669 | ||
fe8ab488 A |
670 | @lldb_command('walkqueue_head', 'S') |
671 | def WalkQueueHead(cmd_args=[], cmd_options={}): | |
672 | """ walk a queue_head_t and list all members in it. Note this is for queue_head_t. refer to osfmk/kern/queue.h | |
673 | Option: -S - suppress summary output. | |
674 | Usage: (lldb) walkqueue_head <queue_entry *> <struct type> <fieldname> | |
675 | ex: (lldb) walkqueue_head 0x7fffff80 "thread *" "task_threads" | |
676 | ||
677 | """ | |
678 | global lldb_summary_definitions | |
679 | if not cmd_args: | |
680 | raise ArgumentError("invalid arguments") | |
681 | if len(cmd_args) != 3: | |
682 | raise ArgumentError("insufficient arguments") | |
683 | queue_head = kern.GetValueFromAddress(cmd_args[0], 'struct queue_entry *') | |
684 | el_type = cmd_args[1] | |
685 | field_name = cmd_args[2] | |
686 | showsummary = False | |
687 | if el_type in lldb_summary_definitions: | |
688 | showsummary = True | |
689 | if '-S' in cmd_options: | |
690 | showsummary = False | |
691 | ||
692 | for i in IterateQueue(queue_head, el_type, field_name): | |
693 | if showsummary: | |
694 | print lldb_summary_definitions[el_type](i) | |
695 | else: | |
696 | print "{0: <#020x}".format(i) | |
697 | ||
698 | ||
699 | ||
700 | @lldb_command('walklist_entry', 'S') | |
701 | def WalkList(cmd_args=[], cmd_options={}): | |
702 | """ iterate over a list as defined with LIST_ENTRY in bsd/sys/queue.h | |
703 | params: | |
704 | object addr - value : address of object | |
705 | element_type - str : Type of the next element | |
706 | field_name - str : Name of the field in next element's structure | |
707 | ||
708 | Option: -S - suppress summary output. | |
709 | Usage: (lldb) walklist_entry <obj with list_entry *> <struct type> <fieldname> | |
710 | ex: (lldb) walklist_entry 0x7fffff80 "struct proc *" "p_sibling" | |
711 | ||
712 | """ | |
713 | global lldb_summary_definitions | |
714 | if not cmd_args: | |
715 | raise ArgumentError("invalid arguments") | |
716 | if len(cmd_args) != 3: | |
717 | raise ArgumentError("insufficient arguments") | |
718 | el_type = cmd_args[1] | |
719 | queue_head = kern.GetValueFromAddress(cmd_args[0], el_type) | |
720 | field_name = cmd_args[2] | |
721 | ||
722 | showsummary = False | |
723 | if el_type in lldb_summary_definitions: | |
724 | showsummary = True | |
725 | if '-S' in cmd_options: | |
726 | showsummary = False | |
727 | elt = queue_head | |
728 | while unsigned(elt) != 0: | |
729 | i = elt | |
730 | elt = elt.__getattr__(field_name).le_next | |
731 | if showsummary: | |
732 | print lldb_summary_definitions[el_type](i) | |
733 | else: | |
734 | print "{0: <#020x}".format(i) | |
735 | ||
736 | ||
737 | ||
39236c6e A |
738 | from memory import * |
739 | from process import * | |
740 | from ipc import * | |
741 | from pmap import * | |
742 | from ioreg import * | |
743 | from mbufs import * | |
744 | from net import * | |
745 | from kdp import * | |
746 | from userspace import * | |
747 | from pci import * | |
748 | from misc import * | |
749 | from apic import * | |
750 | from scheduler import * | |
fe8ab488 A |
751 | from atm import * |
752 | from structanalyze import * | |
753 | from ipcimportancedetail import * | |
754 | from bank import * | |
3e170ce0 A |
755 | from kauth import * |
756 | from waitq import * | |
757 | from usertaskgdbserver import * |