]>
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() | |
30 | return retval | |
31 | ||
32 | def EnableLLDBAPILogging(): | |
33 | """ Enable file based logging for lldb and also provide essential information about what information | |
34 | to include when filing a bug with lldb or xnu. | |
35 | """ | |
36 | logfile_name = "/tmp/lldb.%d.log" % int(time.time()) | |
37 | enable_log_base_cmd = "log enable --file %s " % logfile_name | |
38 | cmd_str = enable_log_base_cmd + ' lldb api' | |
39 | print cmd_str | |
40 | print lldb_run_command(cmd_str) | |
41 | cmd_str = enable_log_base_cmd + ' gdb-remote packets' | |
42 | print cmd_str | |
43 | print lldb_run_command(cmd_str) | |
44 | cmd_str = enable_log_base_cmd + ' kdp-remote packets' | |
45 | print cmd_str | |
46 | print lldb_run_command(cmd_str) | |
47 | print lldb_run_command("verison") | |
48 | 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 | |
49 | print "Please make sure to provide the output of 'verison', 'image list' and output of command that failed." | |
50 | return | |
51 | ||
52 | def GetConnectionProtocol(): | |
53 | """ Returns a string representing what kind of connection is used for debugging the target. | |
54 | params: None | |
55 | returns: | |
56 | str - connection type. One of ("core","kdp","gdb", "unknown") | |
57 | """ | |
58 | retval = "unknown" | |
59 | process_plugin_name = LazyTarget.GetProcess().GetPluginName().lower() | |
60 | if "kdp" in process_plugin_name: | |
61 | retval = "kdp" | |
62 | elif "gdb" in process_plugin_name: | |
63 | retval = "gdb" | |
64 | elif "mach-o" in process_plugin_name and "core" in process_plugin_name: | |
65 | retval = "core" | |
66 | return retval | |
67 | ||
68 | def SBValueToPointer(sbval): | |
69 | """ Helper function for getting pointer value from an object of pointer type. | |
70 | ex. void *astring = 0x12345 | |
71 | use SBValueToPointer(astring_val) to get 0x12345 | |
72 | params: sbval - value object of type '<type> *' | |
73 | returns: int - pointer value as an int. | |
74 | """ | |
75 | if type(sbval) == core.value: | |
76 | sbval = sbval.GetSBValue() | |
77 | if sbval.IsPointerType(): | |
78 | return sbval.GetValueAsUnsigned() | |
79 | else: | |
80 | return int(sbval.GetAddress()) | |
81 | ||
82 | def ArgumentStringToInt(arg_string): | |
83 | """ convert '1234' or '0x123' to int | |
84 | params: | |
85 | arg_string: str - typically string passed from commandline. ex '1234' or '0xA12CD' | |
86 | returns: | |
87 | int - integer representation of the string | |
88 | """ | |
89 | arg_string = arg_string.strip() | |
90 | if arg_string.find('0x') >=0: | |
91 | return int(arg_string, 16) | |
92 | else: | |
93 | return int(arg_string) | |
94 | ||
95 | def GetLongestMatchOption(searchstr, options=[], ignore_case=True): | |
96 | """ Get longest matched string from set of options. | |
97 | params: | |
98 | searchstr : string of chars to be matched | |
99 | options : array of strings that are to be matched | |
100 | returns: | |
101 | [] - array of matched options. The order of options is same as the arguments. | |
102 | empty array is returned if searchstr does not match any option. | |
103 | example: | |
104 | subcommand = LongestMatch('Rel', ['decode', 'enable', 'reload'], ignore_case=True) | |
105 | print subcommand # prints ['reload'] | |
106 | """ | |
107 | if ignore_case: | |
108 | searchstr = searchstr.lower() | |
109 | found_options = [] | |
110 | for o in options: | |
111 | so = o | |
112 | if ignore_case: | |
113 | so = o.lower() | |
114 | if so.find(searchstr) >=0 : | |
115 | found_options.append(o) | |
116 | return found_options | |
117 | ||
118 | def GetType(target_type): | |
119 | """ type cast an object to new type. | |
120 | params: | |
121 | target_type - str, ex. 'char', 'uint32_t' etc | |
122 | returns: | |
123 | lldb.SBType - a new Type that can be used as param to lldb.SBValue.Cast() | |
124 | raises: | |
125 | NameError - Incase the type is not identified | |
126 | """ | |
127 | return gettype(target_type) | |
128 | ||
129 | ||
130 | def Cast(obj, target_type): | |
131 | """ Type cast an object to another C type. | |
132 | params: | |
133 | obj - core.value object representing some C construct in lldb | |
134 | target_type - str : ex 'char *' | |
135 | - lldb.SBType : | |
136 | """ | |
137 | return cast(obj, target_type) | |
138 | ||
139 | ||
140 | def loadLLDB(): | |
141 | """ Util function to load lldb python framework in case not available in common include paths. | |
142 | """ | |
143 | try: | |
144 | import lldb | |
145 | print 'Found LLDB on path' | |
146 | except: | |
147 | platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split()) | |
148 | offset = platdir.find("Contents/Developer") | |
149 | if offset == -1: | |
150 | lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python') | |
151 | else: | |
152 | lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python') | |
153 | if os.path.isdir(lldb_py): | |
154 | sys.path.append(lldb_py) | |
155 | global lldb | |
156 | lldb = __import__('lldb') | |
157 | print 'Found LLDB in SDK' | |
158 | else: | |
159 | print 'Failed to locate lldb.py from', lldb_py | |
160 | sys.exit(-1) | |
161 | return True | |
162 | ||
163 | class Logger(): | |
164 | """ A logging utility """ | |
165 | def __init__(self, log_file_path="/tmp/xnu.log"): | |
166 | self.log_file_handle = open(log_file_path, "w+") | |
167 | self.redirect_to_stdout = False | |
168 | ||
169 | def log_debug(self, *args): | |
170 | current_timestamp = time.time() | |
171 | debug_line_str = "DEBUG:" + str(current_timestamp) + ":" | |
172 | for arg in args: | |
173 | debug_line_str += " " + str(arg).replace("\n", " ") + ", " | |
174 | ||
175 | self.log_file_handle.write(debug_line_str + "\n") | |
176 | if self.redirect_to_stdout : | |
177 | print debug_line_str | |
178 | ||
179 | def write(self, line): | |
180 | self.log_debug(line) | |
181 | ||
182 | ||
183 | def sizeof_fmt(num, unit_str='B'): | |
184 | """ format large number into human readable values. | |
185 | convert any number into Kilo, Mega, Giga, Tera format for human understanding. | |
186 | params: | |
187 | num - int : number to be converted | |
188 | unit_str - str : a suffix for unit. defaults to 'B' for bytes. | |
189 | returns: | |
190 | str - formatted string for printing. | |
191 | """ | |
192 | for x in ['','K','M','G','T']: | |
193 | if num < 1024.0: | |
194 | return "%3.1f%s%s" % (num, x,unit_str) | |
195 | num /= 1024.0 | |
196 | return "%3.1f%s%s" % (num, 'P', unit_str) | |
197 | ||
198 | def WriteStringToMemoryAddress(stringval, addr): | |
199 | """ write a null terminated string to address. | |
200 | params: | |
201 | stringval: str- string to be written to memory. a '\0' will be added at the end | |
202 | addr : int - address where data is to be written | |
203 | returns: | |
204 | bool - True if successfully written | |
205 | """ | |
206 | serr = lldb.SBError() | |
207 | length = len(stringval) + 1 | |
208 | format_string = "%ds" % length | |
209 | sdata = struct.pack(format_string,stringval) | |
210 | numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr) | |
211 | if numbytes == length and serr.Success(): | |
212 | return True | |
213 | return False | |
214 | ||
215 | def WriteInt64ToMemoryAddress(intval, addr): | |
216 | """ write a 64 bit integer at an address. | |
217 | params: | |
218 | intval - int - an integer value to be saved | |
219 | addr - int - address where int is to be written | |
220 | returns: | |
221 | bool - True if successfully written. | |
222 | """ | |
223 | serr = lldb.SBError() | |
224 | sdata = struct.pack('Q', intval) | |
225 | addr = int(hex(addr).rstrip('L'), 16) | |
226 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
227 | if numbytes == 8 and serr.Success(): | |
228 | return True | |
229 | return False | |
230 | ||
231 | def WritePtrDataToMemoryAddress(intval, addr): | |
232 | """ Write data to pointer size memory. | |
233 | This is equivalent of doing *(&((struct pmap *)addr)) = intval | |
234 | It will identify 32/64 bit kernel and write memory accordingly. | |
235 | params: | |
236 | intval - int - an integer value to be saved | |
237 | addr - int - address where int is to be written | |
238 | returns: | |
239 | bool - True if successfully written. | |
240 | """ | |
241 | if kern.ptrsize == 8: | |
242 | return WriteInt64ToMemoryAddress(intval, addr) | |
243 | else: | |
244 | return WriteInt32ToMemoryAddress(intval, addr) | |
245 | ||
246 | def WriteInt32ToMemoryAddress(intval, addr): | |
247 | """ write a 32 bit integer at an address. | |
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 | serr = lldb.SBError() | |
255 | sdata = struct.pack('I', intval) | |
256 | addr = int(hex(addr).rstrip('L'), 16) | |
257 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
258 | if numbytes == 4 and serr.Success(): | |
259 | return True | |
260 | return False | |
261 | ||
262 | def WriteInt16ToMemoryAddress(intval, addr): | |
263 | """ write a 16 bit integer at an address. | |
264 | params: | |
265 | intval - int - an integer value to be saved | |
266 | addr - int - address where int is to be written | |
267 | returns: | |
268 | bool - True if successfully written. | |
269 | """ | |
270 | serr = lldb.SBError() | |
271 | sdata = struct.pack('H', intval) | |
272 | addr = int(hex(addr).rstrip('L'), 16) | |
273 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
274 | if numbytes == 2 and serr.Success(): | |
275 | return True | |
276 | return False | |
277 | ||
278 | def WriteInt8ToMemoryAddress(intval, addr): | |
279 | """ write a 8 bit integer at an address. | |
280 | params: | |
281 | intval - int - an integer value to be saved | |
282 | addr - int - address where int is to be written | |
283 | returns: | |
284 | bool - True if successfully written. | |
285 | """ | |
286 | serr = lldb.SBError() | |
287 | sdata = struct.pack('B', intval) | |
288 | addr = int(hex(addr).rstrip('L'), 16) | |
289 | numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) | |
290 | if numbytes == 1 and serr.Success(): | |
291 | return True | |
292 | return False | |
293 | ||
294 | _enum_cache = {} | |
295 | def GetEnumValue(name): | |
296 | """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION => 0x3 | |
297 | params: | |
298 | name : str - name of enum in the format type::name | |
299 | returns: | |
300 | int - value of the particular enum. | |
301 | raises: | |
302 | TypeError - if the enum is not found | |
303 | """ | |
304 | name = name.strip() | |
305 | global _enum_cache | |
306 | if name not in _enum_cache: | |
307 | res = lldb.SBCommandReturnObject() | |
308 | lldb.debugger.GetCommandInterpreter().HandleCommand("p/x (`%s`)" % name, res) | |
309 | if not res.Succeeded(): | |
310 | raise TypeError("Enum not found with name: " + name) | |
311 | # the result is of format '(int) $481 = 0x00000003\n' | |
312 | _enum_cache[name] = int( res.GetOutput().split('=')[-1].strip(), 16) | |
313 | return _enum_cache[name] | |
314 | ||
315 | def ResolveFSPath(path): | |
316 | """ expand ~user directories and return absolute path. | |
317 | params: path - str - eg "~rc/Software" | |
318 | returns: | |
319 | str - abs path with user directories and symlinks expanded. | |
320 | str - if path resolution fails then returns the same string back | |
321 | """ | |
322 | expanded_path = os.path.expanduser(path) | |
323 | norm_path = os.path.normpath(expanded_path) | |
324 | return norm_path | |
325 | ||
326 | _dsymlist = {} | |
327 | 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) | |
328 | def addDSYM(uuid, info): | |
329 | """ add a module by dsym into the target modules. | |
330 | params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E | |
331 | info - dict - info dictionary passed from dsymForUUID | |
332 | """ | |
333 | global _dsymlist | |
334 | if "DBGSymbolRichExecutable" not in info: | |
335 | print "Error: Unable to find syms for %s" % uuid | |
336 | return False | |
337 | if not uuid in _dsymlist: | |
338 | # add the dsym itself | |
339 | cmd_str = "target modules add --uuid %s" % uuid | |
340 | debuglog(cmd_str) | |
341 | lldb.debugger.HandleCommand(cmd_str) | |
342 | # set up source path | |
343 | #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"])) | |
344 | # modify the list to show we loaded this | |
345 | _dsymlist[uuid] = True | |
346 | ||
347 | def loadDSYM(uuid, load_address): | |
348 | """ Load an already added symbols to a particular load address | |
349 | params: uuid - str - uuid string | |
350 | load_address - int - address where to load the symbols | |
351 | returns bool: | |
352 | True - if successful | |
353 | False - if failed. possible because uuid is not presently loaded. | |
354 | """ | |
355 | if uuid not in _dsymlist: | |
356 | return False | |
357 | cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address) | |
358 | debuglog(cmd_str) | |
359 | lldb.debugger.HandleCommand(cmd_str) | |
360 | ||
361 | def dsymForUUID(uuid): | |
362 | """ Get dsym informaiton by calling dsymForUUID | |
363 | params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E | |
364 | returns: | |
365 | {} - a dictionary holding dsym information printed by dsymForUUID. | |
366 | None - if failed to find information | |
367 | """ | |
368 | import subprocess | |
369 | import plistlib | |
370 | output = subprocess.check_output(["/usr/local/bin/dsymForUUID", uuid]) | |
371 | if output: | |
372 | # because of <rdar://12713712> | |
373 | #plist = plistlib.readPlistFromString(output) | |
374 | #beginworkaround | |
375 | keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL) | |
376 | plist={} | |
377 | plist[uuid] = {} | |
378 | for item in keyvalue_extract_re.findall(output): | |
379 | plist[uuid][item[0]] = item[1] | |
380 | #endworkaround | |
381 | if plist and plist[uuid]: | |
382 | return plist[uuid] | |
383 | return None | |
384 | ||
385 | def debuglog(s): | |
386 | """ Print a object in the debug stream | |
387 | """ | |
388 | global config | |
389 | if config['debug']: | |
390 | print "DEBUG:",s | |
391 | return None |