]>
Commit | Line | Data |
---|---|---|
1 | #General Utility functions for debugging or introspection | |
2 | ||
3 | """ Please make sure you read the README file COMPLETELY BEFORE reading anything below. | |
4 | It is very critical that you read coding guidelines in Section E in README file. | |
5 | """ | |
6 | import sys, re, time, getopt, shlex, os, time | |
7 | import lldb | |
8 | import struct | |
9 | from core.cvalue import * | |
10 | from core.configuration import * | |
11 | from core.lazytarget import * | |
12 | ||
13 | #DONOTTOUCHME: exclusive use for lldb_run_command only. | |
14 | lldb_run_command_state = {'active':False} | |
15 | ||
16 | def lldb_run_command(cmdstring): | |
17 | """ Run a lldb command and get the string output. | |
18 | params: cmdstring - str : lldb command string which could be executed at (lldb) prompt. (eg. "register read") | |
19 | returns: str - output of command. it may be "" in case if command did not return any output. | |
20 | """ | |
21 | global lldb_run_command_state | |
22 | retval ="" | |
23 | res = lldb.SBCommandReturnObject() | |
24 | # set special attribute to notify xnu framework to not print on stdout | |
25 | lldb_run_command_state['active'] = True | |
26 | lldb.debugger.GetCommandInterpreter().HandleCommand(cmdstring, res) | |
27 | lldb_run_command_state['active'] = False | |
28 | if res.Succeeded(): | |
29 | retval = res.GetOutput() | |
30 | else: | |
31 | retval = "ERROR:" + res.GetError() | |
32 | return retval | |
33 | ||
34 | def EnableLLDBAPILogging(): | |
35 | """ Enable file based logging for lldb and also provide essential information about what information | |
36 | to include when filing a bug with lldb or xnu. | |
37 | """ | |
38 | logfile_name = "/tmp/lldb.%d.log" % int(time.time()) | |
39 | enable_log_base_cmd = "log enable --file %s " % logfile_name | |
40 | cmd_str = enable_log_base_cmd + ' lldb api' | |
41 | print cmd_str | |
42 | print lldb_run_command(cmd_str) | |
43 | cmd_str = enable_log_base_cmd + ' gdb-remote packets' | |
44 | print cmd_str | |
45 | print lldb_run_command(cmd_str) | |
46 | cmd_str = enable_log_base_cmd + ' kdp-remote packets' | |
47 | print cmd_str | |
48 | print lldb_run_command(cmd_str) | |
49 | print lldb_run_command("version") | |
50 | print "Please collect the logs from %s for filing a radar. If you had encountered an exception in a lldbmacro command please re-run it." % logfile_name | |
51 | print "Please make sure to provide the output of 'version', 'image list' and output of command that failed." | |
52 | return | |
53 | ||
54 | def GetConnectionProtocol(): | |
55 | """ Returns a string representing what kind of connection is used for debugging the target. | |
56 | params: None | |
57 | returns: | |
58 | str - connection type. One of ("core","kdp","gdb", "unknown") | |
59 | """ | |
60 | retval = "unknown" | |
61 | process_plugin_name = LazyTarget.GetProcess().GetPluginName().lower() | |
62 | if "kdp" in process_plugin_name: | |
63 | retval = "kdp" | |
64 | elif "gdb" in process_plugin_name: | |
65 | retval = "gdb" | |
66 | elif "mach-o" in process_plugin_name and "core" in process_plugin_name: | |
67 | retval = "core" | |
68 | return retval | |
69 | ||
70 | def SBValueToPointer(sbval): | |
71 | """ Helper function for getting pointer value from an object of pointer type. | |
72 | ex. void *astring = 0x12345 | |
73 | use SBValueToPointer(astring_val) to get 0x12345 | |
74 | params: sbval - value object of type '<type> *' | |
75 | returns: int - pointer value as an int. | |
76 | """ | |
77 | if type(sbval) == core.value: | |
78 | sbval = sbval.GetSBValue() | |
79 | if sbval.IsPointerType(): | |
80 | return sbval.GetValueAsUnsigned() | |
81 | else: | |
82 | return int(sbval.GetAddress()) | |
83 | ||
84 | def ArgumentStringToInt(arg_string): | |
85 | """ convert '1234' or '0x123' to int | |
86 | params: | |
87 | arg_string: str - typically string passed from commandline. ex '1234' or '0xA12CD' | |
88 | returns: | |
89 | int - integer representation of the string | |
90 | """ | |
91 | arg_string = arg_string.strip() | |
92 | if arg_string.find('0x') >=0: | |
93 | return int(arg_string, 16) | |
94 | else: | |
95 | return int(arg_string) | |
96 | ||
97 | def GetLongestMatchOption(searchstr, options=[], ignore_case=True): | |
98 | """ Get longest matched string from set of options. | |
99 | params: | |
100 | searchstr : string of chars to be matched | |
101 | options : array of strings that are to be matched | |
102 | returns: | |
103 | [] - array of matched options. The order of options is same as the arguments. | |
104 | empty array is returned if searchstr does not match any option. | |
105 | example: | |
106 | subcommand = LongestMatch('Rel', ['decode', 'enable', 'reload'], ignore_case=True) | |
107 | print subcommand # prints ['reload'] | |
108 | """ | |
109 | if ignore_case: | |
110 | searchstr = searchstr.lower() | |
111 | found_options = [] | |
112 | for o in options: | |
113 | so = o | |
114 | if ignore_case: | |
115 | so = o.lower() | |
116 | if so == searchstr: | |
117 | return [o] | |
118 | if so.find(searchstr) >=0 : | |
119 | found_options.append(o) | |
120 | return found_options | |
121 | ||
122 | def GetType(target_type): | |
123 | """ type cast an object to new type. | |
124 | params: | |
125 | target_type - str, ex. 'char', 'uint32_t' etc | |
126 | returns: | |
127 | lldb.SBType - a new Type that can be used as param to lldb.SBValue.Cast() | |
128 | raises: | |
129 | NameError - Incase the type is not identified | |
130 | """ | |
131 | return gettype(target_type) | |
132 | ||
133 | ||
134 | def Cast(obj, target_type): | |
135 | """ Type cast an object to another C type. | |
136 | params: | |
137 | obj - core.value object representing some C construct in lldb | |
138 | target_type - str : ex 'char *' | |
139 | - lldb.SBType : | |
140 | """ | |
141 | return cast(obj, target_type) | |
142 | ||
143 | def ContainerOf(obj, target_type, field_name): | |
144 | """ Type cast an object to another C type from a pointer to a field. | |
145 | params: | |
146 | obj - core.value object representing some C construct in lldb | |
147 | target_type - str : ex 'struct thread' | |
148 | - lldb.SBType : | |
149 | field_name - the field name within the target_type obj is a pointer to | |
150 | """ | |
151 | return containerof(obj, target_type, field_name) | |
152 | ||
153 | def loadLLDB(): | |
154 | """ Util function to load lldb python framework in case not available in common include paths. | |
155 | """ | |
156 | try: | |
157 | import lldb | |
158 | print 'Found LLDB on path' | |
159 | except: | |
160 | platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split()) | |
161 | offset = platdir.find("Contents/Developer") | |
162 | if offset == -1: | |
163 | lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python') | |
164 | else: | |
165 | lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python') | |
166 | if os.path.isdir(lldb_py): | |
167 | sys.path.append(lldb_py) | |
168 | global lldb | |
169 | lldb = __import__('lldb') | |
170 | print 'Found LLDB in SDK' | |
171 | else: | |
172 | print 'Failed to locate lldb.py from', lldb_py | |
173 | sys.exit(-1) | |
174 | return True | |
175 | ||
176 | class Logger(): | |
177 | """ A logging utility """ | |
178 | def __init__(self, log_file_path="/tmp/xnu.log"): | |
179 | self.log_file_handle = open(log_file_path, "w+") | |
180 | self.redirect_to_stdout = False | |
181 | ||
182 | def log_debug(self, *args): | |
183 | current_timestamp = time.time() | |
184 | debug_line_str = "DEBUG:" + str(current_timestamp) + ":" | |
185 | for arg in args: | |
186 | debug_line_str += " " + str(arg).replace("\n", " ") + ", " | |
187 | ||
188 | self.log_file_handle.write(debug_line_str + "\n") | |
189 | if self.redirect_to_stdout : | |
190 | print debug_line_str | |
191 | ||
192 | def write(self, line): | |
193 | self.log_debug(line) | |
194 | ||
195 | ||
196 | def sizeof_fmt(num, unit_str='B'): | |
197 | """ format large number into human readable values. | |
198 | convert any number into Kilo, Mega, Giga, Tera format for human understanding. | |
199 | params: | |
200 | num - int : number to be converted | |
201 | unit_str - str : a suffix for unit. defaults to 'B' for bytes. | |
202 | returns: | |
203 | str - formatted string for printing. | |
204 | """ | |
205 | for x in ['','K','M','G','T']: | |
206 | if num < 1024.0: | |
207 | return "%3.1f%s%s" % (num, x,unit_str) | |
208 | num /= 1024.0 | |
209 | return "%3.1f%s%s" % (num, 'P', unit_str) | |
210 | ||
211 | def WriteStringToMemoryAddress(stringval, addr): | |
212 | """ write a null terminated string to address. | |
213 | params: | |
214 | stringval: str- string to be written to memory. a '\0' will be added at the end | |
215 | addr : int - address where data is to be written | |
216 | returns: | |
217 | bool - True if successfully written | |
218 | """ | |
219 | serr = lldb.SBError() | |
220 | length = len(stringval) + 1 | |
221 | format_string = "%ds" % length | |
222 | sdata = struct.pack(format_string,stringval) | |
223 | numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr) | |
224 | if numbytes == length and serr.Success(): | |
225 | return True | |
226 | return False | |
227 | ||
228 | def WriteInt64ToMemoryAddress(intval, addr): | |
229 | """ write a 64 bit integer at an address. | |
230 | params: | |
231 | intval - int - an integer value to be saved | |
232 | addr - int - address where int is to be written | |
233 | returns: | |
234 | bool - True if successfully written. | |
235 | """ | |
236 | serr = lldb.SBError() | |
237 | sdata = struct.pack('Q', intval) | |
238 | addr = int(hex(addr).rstrip('L'), 16) | |
239 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
240 | if numbytes == 8 and serr.Success(): | |
241 | return True | |
242 | return False | |
243 | ||
244 | def WritePtrDataToMemoryAddress(intval, addr): | |
245 | """ Write data to pointer size memory. | |
246 | This is equivalent of doing *(&((struct pmap *)addr)) = intval | |
247 | It will identify 32/64 bit kernel and write memory accordingly. | |
248 | params: | |
249 | intval - int - an integer value to be saved | |
250 | addr - int - address where int is to be written | |
251 | returns: | |
252 | bool - True if successfully written. | |
253 | """ | |
254 | if kern.ptrsize == 8: | |
255 | return WriteInt64ToMemoryAddress(intval, addr) | |
256 | else: | |
257 | return WriteInt32ToMemoryAddress(intval, addr) | |
258 | ||
259 | def WriteInt32ToMemoryAddress(intval, addr): | |
260 | """ write a 32 bit integer at an address. | |
261 | params: | |
262 | intval - int - an integer value to be saved | |
263 | addr - int - address where int is to be written | |
264 | returns: | |
265 | bool - True if successfully written. | |
266 | """ | |
267 | serr = lldb.SBError() | |
268 | sdata = struct.pack('I', intval) | |
269 | addr = int(hex(addr).rstrip('L'), 16) | |
270 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
271 | if numbytes == 4 and serr.Success(): | |
272 | return True | |
273 | return False | |
274 | ||
275 | def WriteInt16ToMemoryAddress(intval, addr): | |
276 | """ write a 16 bit integer at an address. | |
277 | params: | |
278 | intval - int - an integer value to be saved | |
279 | addr - int - address where int is to be written | |
280 | returns: | |
281 | bool - True if successfully written. | |
282 | """ | |
283 | serr = lldb.SBError() | |
284 | sdata = struct.pack('H', intval) | |
285 | addr = int(hex(addr).rstrip('L'), 16) | |
286 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
287 | if numbytes == 2 and serr.Success(): | |
288 | return True | |
289 | return False | |
290 | ||
291 | def WriteInt8ToMemoryAddress(intval, addr): | |
292 | """ write a 8 bit integer at an address. | |
293 | params: | |
294 | intval - int - an integer value to be saved | |
295 | addr - int - address where int is to be written | |
296 | returns: | |
297 | bool - True if successfully written. | |
298 | """ | |
299 | serr = lldb.SBError() | |
300 | sdata = struct.pack('B', intval) | |
301 | addr = int(hex(addr).rstrip('L'), 16) | |
302 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
303 | if numbytes == 1 and serr.Success(): | |
304 | return True | |
305 | return False | |
306 | ||
307 | _enum_cache = {} | |
308 | def GetEnumValue(enum_name_or_combined, member_name = None): | |
309 | """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION => 0x3 | |
310 | params: | |
311 | enum_name_or_combined: str | |
312 | name of an enum of the format type::name (legacy) | |
313 | name of an enum type | |
314 | member_name: None, or the name of an enum member | |
315 | (then enum_name_or_combined is a type name). | |
316 | returns: | |
317 | int - value of the particular enum. | |
318 | raises: | |
319 | TypeError - if the enum is not found | |
320 | """ | |
321 | global _enum_cache | |
322 | if member_name is None: | |
323 | enum_name, member_name = enum_name_or_combined.strip().split("::") | |
324 | else: | |
325 | enum_name = enum_name_or_combined | |
326 | ||
327 | if enum_name not in _enum_cache: | |
328 | ty = GetType(enum_name) | |
329 | d = {} | |
330 | ||
331 | for e in ty.get_enum_members_array(): | |
332 | if ty.GetTypeFlags() & lldb.eTypeIsSigned: | |
333 | d[e.GetName()] = e.GetValueAsSigned() | |
334 | else: | |
335 | d[e.GetName()] = e.GetValueAsUnsigned() | |
336 | ||
337 | _enum_cache[enum_name] = d | |
338 | ||
339 | return _enum_cache[enum_name][member_name] | |
340 | ||
341 | def ResolveFSPath(path): | |
342 | """ expand ~user directories and return absolute path. | |
343 | params: path - str - eg "~rc/Software" | |
344 | returns: | |
345 | str - abs path with user directories and symlinks expanded. | |
346 | str - if path resolution fails then returns the same string back | |
347 | """ | |
348 | expanded_path = os.path.expanduser(path) | |
349 | norm_path = os.path.normpath(expanded_path) | |
350 | return norm_path | |
351 | ||
352 | _dsymlist = {} | |
353 | uuid_regex = re.compile("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",re.IGNORECASE|re.DOTALL) | |
354 | def addDSYM(uuid, info): | |
355 | """ add a module by dsym into the target modules. | |
356 | params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E | |
357 | info - dict - info dictionary passed from dsymForUUID | |
358 | """ | |
359 | global _dsymlist | |
360 | if "DBGSymbolRichExecutable" not in info: | |
361 | print "Error: Unable to find syms for %s" % uuid | |
362 | return False | |
363 | if not uuid in _dsymlist: | |
364 | # add the dsym itself | |
365 | cmd_str = "target modules add --uuid %s" % uuid | |
366 | debuglog(cmd_str) | |
367 | lldb.debugger.HandleCommand(cmd_str) | |
368 | # set up source path | |
369 | #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"])) | |
370 | # modify the list to show we loaded this | |
371 | _dsymlist[uuid] = True | |
372 | ||
373 | def loadDSYM(uuid, load_address, sections=[]): | |
374 | """ Load an already added symbols to a particular load address | |
375 | params: uuid - str - uuid string | |
376 | load_address - int - address where to load the symbols | |
377 | returns bool: | |
378 | True - if successful | |
379 | False - if failed. possible because uuid is not presently loaded. | |
380 | """ | |
381 | if uuid not in _dsymlist: | |
382 | return False | |
383 | if not sections: | |
384 | cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address) | |
385 | debuglog(cmd_str) | |
386 | else: | |
387 | cmd_str = "target modules load --uuid {} ".format(uuid) | |
388 | sections_str = "" | |
389 | for s in sections: | |
390 | sections_str += " {} {:#0x} ".format(s.name, s.vmaddr) | |
391 | cmd_str += sections_str | |
392 | debuglog(cmd_str) | |
393 | ||
394 | lldb.debugger.HandleCommand(cmd_str) | |
395 | return True | |
396 | ||
397 | ||
398 | def RunShellCommand(command): | |
399 | """ Run a shell command in subprocess. | |
400 | params: command with arguments to run | |
401 | returns: (exit_code, stdout, stderr) | |
402 | """ | |
403 | import shlex, subprocess | |
404 | cmd_args = shlex.split(command) | |
405 | output_str = "" | |
406 | exit_code = 0 | |
407 | try: | |
408 | output_str = subprocess.check_output(cmd_args, stderr=subprocess.STDOUT) | |
409 | except subprocess.CalledProcessError, e: | |
410 | exit_code = e.returncode | |
411 | finally: | |
412 | return (exit_code, output_str, '') | |
413 | ||
414 | def dsymForUUID(uuid): | |
415 | """ Get dsym informaiton by calling dsymForUUID | |
416 | params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E | |
417 | returns: | |
418 | {} - a dictionary holding dsym information printed by dsymForUUID. | |
419 | None - if failed to find information | |
420 | """ | |
421 | import subprocess | |
422 | import plistlib | |
423 | output = subprocess.check_output(["/usr/local/bin/dsymForUUID", "--copyExecutable", uuid]) | |
424 | if output: | |
425 | # because of <rdar://12713712> | |
426 | #plist = plistlib.readPlistFromString(output) | |
427 | #beginworkaround | |
428 | keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL) | |
429 | plist={} | |
430 | plist[uuid] = {} | |
431 | for item in keyvalue_extract_re.findall(output): | |
432 | plist[uuid][item[0]] = item[1] | |
433 | #endworkaround | |
434 | if plist and plist[uuid]: | |
435 | return plist[uuid] | |
436 | return None | |
437 | ||
438 | def debuglog(s): | |
439 | """ Print a object in the debug stream | |
440 | """ | |
441 | global config | |
442 | if config['debug']: | |
443 | print "DEBUG:",s | |
444 | return None | |
445 | ||
446 | def IsAppleInternal(): | |
447 | """ check if apple_internal modules are available | |
448 | returns: True if apple_internal module is present | |
449 | """ | |
450 | import imp | |
451 | try: | |
452 | imp.find_module("apple_internal") | |
453 | retval = True | |
454 | except ImportError: | |
455 | retval = False | |
456 | return retval | |
457 | ||
458 | def print_hex_data(data, begin_offset=0, desc="", marks={}): | |
459 | """ print on stdout "hexdump -C < data" like output | |
460 | params: | |
461 | data - bytearray or array of int where each int < 255 | |
462 | begin_offset - int offset that should be printed in left column | |
463 | desc - str optional description to print on the first line to describe data | |
464 | mark - dictionary of markers | |
465 | """ | |
466 | if desc: | |
467 | print "{}:".format(desc) | |
468 | index = 0 | |
469 | total_len = len(data) | |
470 | hex_buf = "" | |
471 | char_buf = "" | |
472 | while index < total_len: | |
473 | if marks.has_key(begin_offset + index): | |
474 | hex_buf += marks[begin_offset + index] | |
475 | hex_buf += "{:02x}".format(data[index]) | |
476 | else: | |
477 | hex_buf += " {:02x}".format(data[index]) | |
478 | if data[index] < 0x20 or data[index] > 0x7e: | |
479 | char_buf += "." | |
480 | else: | |
481 | char_buf += "{:c}".format(data[index]) | |
482 | index += 1 | |
483 | if index and index < total_len and index % 8 == 0: | |
484 | hex_buf += " " | |
485 | if index > 1 and index < total_len and (index % 16) == 0: | |
486 | print "{:08x} {: <50s} |{: <16s}|".format(begin_offset + index - 16, hex_buf, char_buf) | |
487 | hex_buf = "" | |
488 | char_buf = "" | |
489 | print "{:08x} {: <50s} |{: <16s}|".format(begin_offset + index - 16, hex_buf, char_buf) | |
490 | return | |
491 | ||
492 | def Ones(x): | |
493 | return (1 << x)-1 | |
494 | ||
495 | def StripPAC(x, TySz): | |
496 | sign_mask = 1 << 55 | |
497 | ptr_mask = Ones(64-TySz) | |
498 | pac_mask = ~ptr_mask | |
499 | sign = x & sign_mask | |
500 | if sign: | |
501 | return (x | pac_mask) + 2**64 | |
502 | else: | |
503 | return x & ptr_mask |