1# This implements the "diagnose-unwind" command, usually installed 2# in the debug session like 3# command script import lldb.diagnose 4# it is used when lldb's backtrace fails -- it collects and prints 5# information about the stack frames, and tries an alternate unwind 6# algorithm, that will help to understand why lldb's unwind algorithm 7# did not succeed. 8 9import optparse 10import lldb 11import re 12import shlex 13 14# Print the frame number, pc, frame pointer, module UUID and function name 15# Returns the SBModule that contains the PC, if it could be found 16 17 18def backtrace_print_frame(target, frame_num, addr, fp): 19 process = target.GetProcess() 20 addr_for_printing = addr 21 addr_width = process.GetAddressByteSize() * 2 22 if frame_num > 0: 23 addr = addr - 1 24 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 return 46 47 sym_ctx = target.ResolveSymbolContextForAddress( 48 sbaddr, lldb.eSymbolContextEverything 49 ) 50 if sym_ctx.IsValid() and sym_ctx.GetSymbol().IsValid(): 51 function_start = sym_ctx.GetSymbol().GetStartAddress().GetLoadAddress(target) 52 offset = addr - function_start 53 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 modules 83# 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 is 94 # 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_fp 106 ) 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 = 1 120 121 while ( 122 cur_pc != 0 123 and cur_fp != 0 124 and cur_pc != lldb.LLDB_INVALID_ADDRESS 125 and cur_fp != lldb.LLDB_INVALID_ADDRESS 126 ): 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 + 1 134 next_pc = 0 135 next_fp = 0 136 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(), error 144 ) 145 if not error.Success(): 146 next_pc = 0 147 next_fp = process.ReadPointerFromMemory(cur_fp, error) 148 if not error.Success(): 149 next_fp = 0 150 # Clear the 0th bit for arm frames - this indicates it is a thumb frame 151 if target.triple[0:3] == "arm" and (next_pc & 1) == 1: 152 next_pc = next_pc & ~1 153 cur_pc = next_pc 154 cur_fp = next_fp 155 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 return 166 addr_size = process.GetAddressByteSize() 167 addr = fp - (2 * addr_size) 168 i = 0 169 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" % address 175 i += 1 176 print(outline) 177 except Exception: 178 return 179 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 look 185 correct, run this command with the correct thread selected and a 186 large amount of diagnostic information will be printed, it is likely 187 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 return 196 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 = 0 207 lldb_minor = 0 208 if ( 209 len(lldb_versions_match.groups()) >= 1 210 and lldb_versions_match.groups()[0] 211 ): 212 lldb_major = int(lldb_versions_match.groups()[0]) 213 if ( 214 len(lldb_versions_match.groups()) >= 5 215 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 = 0 245 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 + 1 256 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_list 266 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 None 281 and module.GetFileSpec().GetFilename() is not None 282 ): 283 if ( 284 not module.GetFileSpec().GetFilename() 285 in modules_already_seen 286 ): 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_seen 300 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_seen 347 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=usage 379 ) 380 return parser 381 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