]>
Commit | Line | Data |
---|---|---|
39236c6e A |
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() | |
fe8ab488 A |
30 | else: |
31 | retval = "ERROR:" + res.GetError() | |
39236c6e A |
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) | |
fe8ab488 | 49 | print lldb_run_command("version") |
39236c6e | 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 |
fe8ab488 | 51 | print "Please make sure to provide the output of 'version', 'image list' and output of command that failed." |
39236c6e A |
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() | |
fe8ab488 A |
116 | if so == searchstr: |
117 | return [o] | |
39236c6e A |
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 | ||
144 | def loadLLDB(): | |
145 | """ Util function to load lldb python framework in case not available in common include paths. | |
146 | """ | |
147 | try: | |
148 | import lldb | |
149 | print 'Found LLDB on path' | |
150 | except: | |
151 | platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split()) | |
152 | offset = platdir.find("Contents/Developer") | |
153 | if offset == -1: | |
154 | lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python') | |
155 | else: | |
156 | lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python') | |
157 | if os.path.isdir(lldb_py): | |
158 | sys.path.append(lldb_py) | |
159 | global lldb | |
160 | lldb = __import__('lldb') | |
161 | print 'Found LLDB in SDK' | |
162 | else: | |
163 | print 'Failed to locate lldb.py from', lldb_py | |
164 | sys.exit(-1) | |
165 | return True | |
166 | ||
167 | class Logger(): | |
168 | """ A logging utility """ | |
169 | def __init__(self, log_file_path="/tmp/xnu.log"): | |
170 | self.log_file_handle = open(log_file_path, "w+") | |
171 | self.redirect_to_stdout = False | |
172 | ||
173 | def log_debug(self, *args): | |
174 | current_timestamp = time.time() | |
175 | debug_line_str = "DEBUG:" + str(current_timestamp) + ":" | |
176 | for arg in args: | |
177 | debug_line_str += " " + str(arg).replace("\n", " ") + ", " | |
178 | ||
179 | self.log_file_handle.write(debug_line_str + "\n") | |
180 | if self.redirect_to_stdout : | |
181 | print debug_line_str | |
182 | ||
183 | def write(self, line): | |
184 | self.log_debug(line) | |
185 | ||
186 | ||
187 | def sizeof_fmt(num, unit_str='B'): | |
188 | """ format large number into human readable values. | |
189 | convert any number into Kilo, Mega, Giga, Tera format for human understanding. | |
190 | params: | |
191 | num - int : number to be converted | |
192 | unit_str - str : a suffix for unit. defaults to 'B' for bytes. | |
193 | returns: | |
194 | str - formatted string for printing. | |
195 | """ | |
196 | for x in ['','K','M','G','T']: | |
197 | if num < 1024.0: | |
198 | return "%3.1f%s%s" % (num, x,unit_str) | |
199 | num /= 1024.0 | |
200 | return "%3.1f%s%s" % (num, 'P', unit_str) | |
201 | ||
202 | def WriteStringToMemoryAddress(stringval, addr): | |
203 | """ write a null terminated string to address. | |
204 | params: | |
205 | stringval: str- string to be written to memory. a '\0' will be added at the end | |
206 | addr : int - address where data is to be written | |
207 | returns: | |
208 | bool - True if successfully written | |
209 | """ | |
210 | serr = lldb.SBError() | |
211 | length = len(stringval) + 1 | |
212 | format_string = "%ds" % length | |
213 | sdata = struct.pack(format_string,stringval) | |
214 | numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr) | |
215 | if numbytes == length and serr.Success(): | |
216 | return True | |
217 | return False | |
218 | ||
219 | def WriteInt64ToMemoryAddress(intval, addr): | |
220 | """ write a 64 bit integer at an address. | |
221 | params: | |
222 | intval - int - an integer value to be saved | |
223 | addr - int - address where int is to be written | |
224 | returns: | |
225 | bool - True if successfully written. | |
226 | """ | |
227 | serr = lldb.SBError() | |
228 | sdata = struct.pack('Q', intval) | |
229 | addr = int(hex(addr).rstrip('L'), 16) | |
230 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
231 | if numbytes == 8 and serr.Success(): | |
232 | return True | |
233 | return False | |
234 | ||
235 | def WritePtrDataToMemoryAddress(intval, addr): | |
236 | """ Write data to pointer size memory. | |
237 | This is equivalent of doing *(&((struct pmap *)addr)) = intval | |
238 | It will identify 32/64 bit kernel and write memory accordingly. | |
239 | params: | |
240 | intval - int - an integer value to be saved | |
241 | addr - int - address where int is to be written | |
242 | returns: | |
243 | bool - True if successfully written. | |
244 | """ | |
245 | if kern.ptrsize == 8: | |
246 | return WriteInt64ToMemoryAddress(intval, addr) | |
247 | else: | |
248 | return WriteInt32ToMemoryAddress(intval, addr) | |
249 | ||
250 | def WriteInt32ToMemoryAddress(intval, addr): | |
251 | """ write a 32 bit integer at an address. | |
252 | params: | |
253 | intval - int - an integer value to be saved | |
254 | addr - int - address where int is to be written | |
255 | returns: | |
256 | bool - True if successfully written. | |
257 | """ | |
258 | serr = lldb.SBError() | |
259 | sdata = struct.pack('I', intval) | |
260 | addr = int(hex(addr).rstrip('L'), 16) | |
261 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
262 | if numbytes == 4 and serr.Success(): | |
263 | return True | |
264 | return False | |
265 | ||
266 | def WriteInt16ToMemoryAddress(intval, addr): | |
267 | """ write a 16 bit integer at an address. | |
268 | params: | |
269 | intval - int - an integer value to be saved | |
270 | addr - int - address where int is to be written | |
271 | returns: | |
272 | bool - True if successfully written. | |
273 | """ | |
274 | serr = lldb.SBError() | |
275 | sdata = struct.pack('H', intval) | |
276 | addr = int(hex(addr).rstrip('L'), 16) | |
277 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
278 | if numbytes == 2 and serr.Success(): | |
279 | return True | |
280 | return False | |
281 | ||
282 | def WriteInt8ToMemoryAddress(intval, addr): | |
283 | """ write a 8 bit integer at an address. | |
284 | params: | |
285 | intval - int - an integer value to be saved | |
286 | addr - int - address where int is to be written | |
287 | returns: | |
288 | bool - True if successfully written. | |
289 | """ | |
290 | serr = lldb.SBError() | |
291 | sdata = struct.pack('B', intval) | |
292 | addr = int(hex(addr).rstrip('L'), 16) | |
293 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
294 | if numbytes == 1 and serr.Success(): | |
295 | return True | |
296 | return False | |
297 | ||
298 | _enum_cache = {} | |
299 | def GetEnumValue(name): | |
300 | """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION => 0x3 | |
301 | params: | |
302 | name : str - name of enum in the format type::name | |
303 | returns: | |
304 | int - value of the particular enum. | |
305 | raises: | |
306 | TypeError - if the enum is not found | |
307 | """ | |
308 | name = name.strip() | |
309 | global _enum_cache | |
310 | if name not in _enum_cache: | |
311 | res = lldb.SBCommandReturnObject() | |
312 | lldb.debugger.GetCommandInterpreter().HandleCommand("p/x (`%s`)" % name, res) | |
313 | if not res.Succeeded(): | |
314 | raise TypeError("Enum not found with name: " + name) | |
315 | # the result is of format '(int) $481 = 0x00000003\n' | |
316 | _enum_cache[name] = int( res.GetOutput().split('=')[-1].strip(), 16) | |
317 | return _enum_cache[name] | |
318 | ||
319 | def ResolveFSPath(path): | |
320 | """ expand ~user directories and return absolute path. | |
321 | params: path - str - eg "~rc/Software" | |
322 | returns: | |
323 | str - abs path with user directories and symlinks expanded. | |
324 | str - if path resolution fails then returns the same string back | |
325 | """ | |
326 | expanded_path = os.path.expanduser(path) | |
327 | norm_path = os.path.normpath(expanded_path) | |
328 | return norm_path | |
329 | ||
330 | _dsymlist = {} | |
331 | 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) | |
332 | def addDSYM(uuid, info): | |
333 | """ add a module by dsym into the target modules. | |
334 | params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E | |
335 | info - dict - info dictionary passed from dsymForUUID | |
336 | """ | |
337 | global _dsymlist | |
338 | if "DBGSymbolRichExecutable" not in info: | |
339 | print "Error: Unable to find syms for %s" % uuid | |
340 | return False | |
341 | if not uuid in _dsymlist: | |
342 | # add the dsym itself | |
343 | cmd_str = "target modules add --uuid %s" % uuid | |
344 | debuglog(cmd_str) | |
345 | lldb.debugger.HandleCommand(cmd_str) | |
346 | # set up source path | |
347 | #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"])) | |
348 | # modify the list to show we loaded this | |
349 | _dsymlist[uuid] = True | |
350 | ||
351 | def loadDSYM(uuid, load_address): | |
352 | """ Load an already added symbols to a particular load address | |
353 | params: uuid - str - uuid string | |
354 | load_address - int - address where to load the symbols | |
355 | returns bool: | |
356 | True - if successful | |
357 | False - if failed. possible because uuid is not presently loaded. | |
358 | """ | |
359 | if uuid not in _dsymlist: | |
360 | return False | |
361 | cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address) | |
362 | debuglog(cmd_str) | |
363 | lldb.debugger.HandleCommand(cmd_str) | |
364 | ||
365 | def dsymForUUID(uuid): | |
366 | """ Get dsym informaiton by calling dsymForUUID | |
367 | params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E | |
368 | returns: | |
369 | {} - a dictionary holding dsym information printed by dsymForUUID. | |
370 | None - if failed to find information | |
371 | """ | |
372 | import subprocess | |
373 | import plistlib | |
374 | output = subprocess.check_output(["/usr/local/bin/dsymForUUID", uuid]) | |
375 | if output: | |
376 | # because of <rdar://12713712> | |
377 | #plist = plistlib.readPlistFromString(output) | |
378 | #beginworkaround | |
379 | keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL) | |
380 | plist={} | |
381 | plist[uuid] = {} | |
382 | for item in keyvalue_extract_re.findall(output): | |
383 | plist[uuid][item[0]] = item[1] | |
384 | #endworkaround | |
385 | if plist and plist[uuid]: | |
386 | return plist[uuid] | |
387 | return None | |
388 | ||
389 | def debuglog(s): | |
390 | """ Print a object in the debug stream | |
391 | """ | |
392 | global config | |
393 | if config['debug']: | |
394 | print "DEBUG:",s | |
395 | return None |