390 lines · python
1# This implements the "diagnose-unwind" command, usually installed2# in the debug session like3# command script import lldb.diagnose4# it is used when lldb's backtrace fails -- it collects and prints5# information about the stack frames, and tries an alternate unwind6# algorithm, that will help to understand why lldb's unwind algorithm7# did not succeed.8 9import optparse10import lldb11import re12import shlex13 14# Print the frame number, pc, frame pointer, module UUID and function name15# Returns the SBModule that contains the PC, if it could be found16 17 18def backtrace_print_frame(target, frame_num, addr, fp):19 process = target.GetProcess()20 addr_for_printing = addr21 addr_width = process.GetAddressByteSize() * 222 if frame_num > 0:23 addr = addr - 124 25 sbaddr = lldb.SBAddress()26 try:27 sbaddr.SetLoadAddress(addr, target)28 module_description = ""29 if sbaddr.GetModule():30 module_filename = ""31 module_uuid_str = sbaddr.GetModule().GetUUIDString()32 if module_uuid_str is None:33 module_uuid_str = ""34 if sbaddr.GetModule().GetFileSpec():35 module_filename = sbaddr.GetModule().GetFileSpec().GetFilename()36 if module_filename is None:37 module_filename = ""38 if module_uuid_str != "" or module_filename != "":39 module_description = "%s %s" % (module_filename, module_uuid_str)40 except Exception:41 print(42 "%2d: pc==0x%-*x fp==0x%-*x"43 % (frame_num, addr_width, addr_for_printing, addr_width, fp)44 )45 return46 47 sym_ctx = target.ResolveSymbolContextForAddress(48 sbaddr, lldb.eSymbolContextEverything49 )50 if sym_ctx.IsValid() and sym_ctx.GetSymbol().IsValid():51 function_start = sym_ctx.GetSymbol().GetStartAddress().GetLoadAddress(target)52 offset = addr - function_start53 print(54 "%2d: pc==0x%-*x fp==0x%-*x %s %s + %d"55 % (56 frame_num,57 addr_width,58 addr_for_printing,59 addr_width,60 fp,61 module_description,62 sym_ctx.GetSymbol().GetName(),63 offset,64 )65 )66 else:67 print(68 "%2d: pc==0x%-*x fp==0x%-*x %s"69 % (70 frame_num,71 addr_width,72 addr_for_printing,73 addr_width,74 fp,75 module_description,76 )77 )78 return sbaddr.GetModule()79 80 81# A simple stack walk algorithm that follows the frame chain.82# Returns a two-element list; the first element is a list of modules83# seen and the second element is a list of addresses seen during the backtrace.84 85 86def simple_backtrace(debugger):87 target = debugger.GetSelectedTarget()88 process = target.GetProcess()89 cur_thread = process.GetSelectedThread()90 91 initial_fp = cur_thread.GetFrameAtIndex(0).GetFP()92 93 # If the pseudoreg "fp" isn't recognized, on arm hardcode to r7 which is94 # correct for Darwin programs.95 if initial_fp == lldb.LLDB_INVALID_ADDRESS and target.triple[0:3] == "arm":96 for reggroup in cur_thread.GetFrameAtIndex(1).registers:97 if reggroup.GetName() == "General Purpose Registers":98 for reg in reggroup:99 if reg.GetName() == "r7":100 initial_fp = int(reg.GetValue(), 16)101 102 module_list = []103 address_list = [cur_thread.GetFrameAtIndex(0).GetPC()]104 this_module = backtrace_print_frame(105 target, 0, cur_thread.GetFrameAtIndex(0).GetPC(), initial_fp106 )107 print_stack_frame(process, initial_fp)108 print("")109 if this_module is not None:110 module_list.append(this_module)111 if cur_thread.GetNumFrames() < 2:112 return [module_list, address_list]113 114 cur_fp = process.ReadPointerFromMemory(initial_fp, lldb.SBError())115 cur_pc = process.ReadPointerFromMemory(116 initial_fp + process.GetAddressByteSize(), lldb.SBError()117 )118 119 frame_num = 1120 121 while (122 cur_pc != 0123 and cur_fp != 0124 and cur_pc != lldb.LLDB_INVALID_ADDRESS125 and cur_fp != lldb.LLDB_INVALID_ADDRESS126 ):127 address_list.append(cur_pc)128 this_module = backtrace_print_frame(target, frame_num, cur_pc, cur_fp)129 print_stack_frame(process, cur_fp)130 print("")131 if this_module is not None:132 module_list.append(this_module)133 frame_num = frame_num + 1134 next_pc = 0135 next_fp = 0136 if (137 target.triple[0:6] == "x86_64"138 or target.triple[0:4] == "i386"139 or target.triple[0:3] == "arm"140 ):141 error = lldb.SBError()142 next_pc = process.ReadPointerFromMemory(143 cur_fp + process.GetAddressByteSize(), error144 )145 if not error.Success():146 next_pc = 0147 next_fp = process.ReadPointerFromMemory(cur_fp, error)148 if not error.Success():149 next_fp = 0150 # Clear the 0th bit for arm frames - this indicates it is a thumb frame151 if target.triple[0:3] == "arm" and (next_pc & 1) == 1:152 next_pc = next_pc & ~1153 cur_pc = next_pc154 cur_fp = next_fp155 this_module = backtrace_print_frame(target, frame_num, cur_pc, cur_fp)156 print_stack_frame(process, cur_fp)157 print("")158 if this_module is not None:159 module_list.append(this_module)160 return [module_list, address_list]161 162 163def print_stack_frame(process, fp):164 if fp == 0 or fp == lldb.LLDB_INVALID_ADDRESS or fp == 1:165 return166 addr_size = process.GetAddressByteSize()167 addr = fp - (2 * addr_size)168 i = 0169 outline = "Stack frame from $fp-%d: " % (2 * addr_size)170 error = lldb.SBError()171 try:172 while i < 5 and error.Success():173 address = process.ReadPointerFromMemory(addr + (i * addr_size), error)174 outline += " 0x%x" % address175 i += 1176 print(outline)177 except Exception:178 return179 180 181def diagnose_unwind(debugger, command, result, dict):182 """183 Gather diagnostic information to help debug incorrect unwind (backtrace)184 behavior in lldb. When there is a backtrace that doesn't look185 correct, run this command with the correct thread selected and a186 large amount of diagnostic information will be printed, it is likely187 to be helpful when reporting the problem.188 """189 190 command_args = shlex.split(command)191 parser = create_diagnose_unwind_options()192 try:193 (options, args) = parser.parse_args(command_args)194 except:195 return196 target = debugger.GetSelectedTarget()197 if target:198 process = target.GetProcess()199 if process:200 thread = process.GetSelectedThread()201 if thread:202 lldb_versions_match = re.search(203 r"[lL][lL][dD][bB]-(\d+)([.](\d+))?([.](\d+))?",204 debugger.GetVersionString(),205 )206 lldb_version = 0207 lldb_minor = 0208 if (209 len(lldb_versions_match.groups()) >= 1210 and lldb_versions_match.groups()[0]211 ):212 lldb_major = int(lldb_versions_match.groups()[0])213 if (214 len(lldb_versions_match.groups()) >= 5215 and lldb_versions_match.groups()[4]216 ):217 lldb_minor = int(lldb_versions_match.groups()[4])218 219 modules_seen = []220 addresses_seen = []221 222 print("LLDB version %s" % debugger.GetVersionString())223 print("Unwind diagnostics for thread %d" % thread.GetIndexID())224 print("")225 print(226 "============================================================================================="227 )228 print("")229 print("OS plugin setting:")230 debugger.HandleCommand(231 "settings show target.process.python-os-plugin-path"232 )233 print("")234 print("Live register context:")235 thread.SetSelectedFrame(0)236 debugger.HandleCommand("register read")237 print("")238 print(239 "============================================================================================="240 )241 print("")242 print("lldb's unwind algorithm:")243 print("")244 frame_num = 0245 for frame in thread.frames:246 if not frame.IsInlined():247 this_module = backtrace_print_frame(248 target, frame_num, frame.GetPC(), frame.GetFP()249 )250 print_stack_frame(process, frame.GetFP())251 print("")252 if this_module is not None:253 modules_seen.append(this_module)254 addresses_seen.append(frame.GetPC())255 frame_num = frame_num + 1256 print("")257 print(258 "============================================================================================="259 )260 print("")261 print("Simple stack walk algorithm:")262 print("")263 (module_list, address_list) = simple_backtrace(debugger)264 if module_list and module_list is not None:265 modules_seen += module_list266 if address_list and address_list is not None:267 addresses_seen = set(addresses_seen)268 addresses_seen.update(set(address_list))269 270 print("")271 print(272 "============================================================================================="273 )274 print("")275 print("Modules seen in stack walks:")276 print("")277 modules_already_seen = set()278 for module in modules_seen:279 if (280 module is not None281 and module.GetFileSpec().GetFilename() is not None282 ):283 if (284 not module.GetFileSpec().GetFilename()285 in modules_already_seen286 ):287 debugger.HandleCommand(288 "image list %s" % module.GetFileSpec().GetFilename()289 )290 modules_already_seen.add(module.GetFileSpec().GetFilename())291 292 print("")293 print(294 "============================================================================================="295 )296 print("")297 print("Disassembly ofaddresses seen in stack walks:")298 print("")299 additional_addresses_to_disassemble = addresses_seen300 for frame in thread.frames:301 if not frame.IsInlined():302 print(303 "--------------------------------------------------------------------------------------"304 )305 print("")306 print(307 "Disassembly of %s, frame %d, address 0x%x"308 % (309 frame.GetFunctionName(),310 frame.GetFrameID(),311 frame.GetPC(),312 )313 )314 print("")315 if (316 target.triple[0:6] == "x86_64"317 or target.triple[0:4] == "i386"318 ):319 debugger.HandleCommand(320 "disassemble -F att -a 0x%x" % frame.GetPC()321 )322 else:323 debugger.HandleCommand(324 "disassemble -a 0x%x" % frame.GetPC()325 )326 if frame.GetPC() in additional_addresses_to_disassemble:327 additional_addresses_to_disassemble.remove(frame.GetPC())328 329 for address in list(additional_addresses_to_disassemble):330 print(331 "--------------------------------------------------------------------------------------"332 )333 print("")334 print("Disassembly of 0x%x" % address)335 print("")336 if target.triple[0:6] == "x86_64" or target.triple[0:4] == "i386":337 debugger.HandleCommand("disassemble -F att -a 0x%x" % address)338 else:339 debugger.HandleCommand("disassemble -a 0x%x" % address)340 341 print("")342 print(343 "============================================================================================="344 )345 print("")346 additional_addresses_to_show_unwind = addresses_seen347 for frame in thread.frames:348 if not frame.IsInlined():349 print(350 "--------------------------------------------------------------------------------------"351 )352 print("")353 print(354 "Unwind instructions for %s, frame %d"355 % (frame.GetFunctionName(), frame.GetFrameID())356 )357 print("")358 debugger.HandleCommand(359 'image show-unwind -a "0x%x"' % frame.GetPC()360 )361 if frame.GetPC() in additional_addresses_to_show_unwind:362 additional_addresses_to_show_unwind.remove(frame.GetPC())363 364 for address in list(additional_addresses_to_show_unwind):365 print(366 "--------------------------------------------------------------------------------------"367 )368 print("")369 print("Unwind instructions for 0x%x" % address)370 print("")371 debugger.HandleCommand('image show-unwind -a "0x%x"' % address)372 373 374def create_diagnose_unwind_options():375 usage = "usage: %prog"376 description = """Print diagnostic information about a thread backtrace which will help to debug unwind problems"""377 parser = optparse.OptionParser(378 description=description, prog="diagnose_unwind", usage=usage379 )380 return parser381 382 383def __lldb_init_module(debugger, internal_dict):384 debugger.HandleCommand(385 "command script add -o -f %s.diagnose_unwind diagnose-unwind" % __name__386 )387 print(388 'The "diagnose-unwind" command has been installed, type "help diagnose-unwind" for detailed help.'389 )390