1# This implements the "diagnose-unwind" command, usually installed in the debug session like
2#   script import lldb.macosx
3# it is used when lldb's backtrace fails -- it collects and prints information about the stack frames,
4# and tries an alternate unwind algorithm, that will help to understand why lldb's unwind algorithm did
5# not succeed.
6
7import optparse
8import lldb
9import re
10import shlex
11
12# Print the frame number, pc, frame pointer, module UUID and function name
13def backtrace_print_frame (target, frame_num, addr, fp):
14  process = target.GetProcess()
15  addr_for_printing = addr
16  addr_width = process.GetAddressByteSize() * 2
17  if frame_num > 0:
18    addr = addr - 1
19
20  sbaddr = lldb.SBAddress()
21  try:
22    sbaddr.SetLoadAddress(addr, target)
23    module_description = ""
24    if sbaddr.GetModule():
25      module_filename = ""
26      module_uuid_str = sbaddr.GetModule().GetUUIDString()
27      if module_uuid_str == None:
28        module_uuid_str = ""
29      if sbaddr.GetModule().GetFileSpec():
30        module_filename = sbaddr.GetModule().GetFileSpec().GetFilename()
31        if module_filename == None:
32          module_filename = ""
33      if module_uuid_str != "" or module_filename != "":
34        module_description = '%s %s' % (module_filename, module_uuid_str)
35  except Exception:
36    print '%2d: pc==0x%-*x fp==0x%-*x' % (frame_num, addr_width, addr_for_printing, addr_width, fp)
37    return
38
39  sym_ctx = target.ResolveSymbolContextForAddress(sbaddr, lldb.eSymbolContextEverything)
40  if sym_ctx.IsValid() and sym_ctx.GetSymbol().IsValid():
41    function_start = sym_ctx.GetSymbol().GetStartAddress().GetLoadAddress(target)
42    offset = addr - function_start
43    print '%2d: pc==0x%-*x fp==0x%-*x %s %s + %d' % (frame_num, addr_width, addr_for_printing, addr_width, fp, module_description, sym_ctx.GetSymbol().GetName(), offset)
44  else:
45    print '%2d: pc==0x%-*x fp==0x%-*x %s' % (frame_num, addr_width, addr_for_printing, addr_width, fp, module_description)
46
47# A simple stack walk algorithm that follows the frame chain after the first two frames.
48def simple_backtrace(debugger):
49  target = debugger.GetSelectedTarget()
50  process = target.GetProcess()
51  cur_thread = process.GetSelectedThread()
52
53  backtrace_print_frame (target, 0, cur_thread.GetFrameAtIndex(0).GetPC(), cur_thread.GetFrameAtIndex(0).GetFP())
54  if cur_thread.GetNumFrames() < 2:
55    return
56
57  cur_fp = cur_thread.GetFrameAtIndex(1).GetFP()
58  cur_pc = cur_thread.GetFrameAtIndex(1).GetPC()
59
60  # If the pseudoreg "fp" isn't recognized, on arm hardcode to r7 which is correct for Darwin programs.
61  if cur_fp == lldb.LLDB_INVALID_ADDRESS and target.triple[0:3] == "arm":
62    for reggroup in cur_thread.GetFrameAtIndex(1).registers:
63      if reggroup.GetName() == "General Purpose Registers":
64        for reg in reggroup:
65          if reg.GetName() == "r7":
66            cur_fp = int (reg.GetValue(), 16)
67
68  frame_num = 1
69
70  while cur_pc != 0 and cur_fp != 0 and cur_pc != lldb.LLDB_INVALID_ADDRESS and cur_fp != lldb.LLDB_INVALID_ADDRESS:
71    backtrace_print_frame (target, frame_num, cur_pc, cur_fp)
72    frame_num = frame_num + 1
73    next_pc = 0
74    next_fp = 0
75    if target.triple[0:6] == "x86_64" or target.triple[0:4] == "i386" or target.triple[0:3] == "arm":
76      error = lldb.SBError()
77      next_pc = process.ReadPointerFromMemory(cur_fp + process.GetAddressByteSize(), error)
78      if not error.Success():
79        next_pc = 0
80      next_fp = process.ReadPointerFromMemory(cur_fp, error)
81      if not error.Success():
82        next_fp = 0
83    # Clear the 0th bit for arm frames - this indicates it is a thumb frame
84    if target.triple[0:3] == "arm" and (next_pc & 1) == 1:
85      next_pc = next_pc & ~1
86    cur_pc = next_pc
87    cur_fp = next_fp
88  backtrace_print_frame (target, frame_num, cur_pc, cur_fp)
89
90def diagnose_unwind(debugger, command, result, dict):
91  # Use the Shell Lexer to properly parse up command options just like a
92  # shell would
93  command_args = shlex.split(command)
94  parser = create_diagnose_unwind_options()
95  try:
96    (options, args) = parser.parse_args(command_args)
97  except:
98   return
99  target = debugger.GetSelectedTarget()
100  if target:
101    process = target.GetProcess()
102    if process:
103      thread = process.GetSelectedThread()
104      if thread:
105        lldb_versions_match = re.search(r'[lL][lL][dD][bB]-(\d+)([.](\d+))?([.](\d+))?', debugger.GetVersionString())
106        lldb_version = 0
107        lldb_minor = 0
108        if len(lldb_versions_match.groups()) >= 1 and lldb_versions_match.groups()[0]:
109          lldb_major = int(lldb_versions_match.groups()[0])
110        if len(lldb_versions_match.groups()) >= 5 and lldb_versions_match.groups()[4]:
111          lldb_minor = int(lldb_versions_match.groups()[4])
112
113        print 'Unwind diagnostics for thread %d' % thread.GetIndexID()
114        print ""
115        print "lldb's unwind algorithm:"
116        print ""
117        frame_num = 0
118        for frame in thread.frames:
119          if not frame.IsInlined():
120            backtrace_print_frame (target, frame_num, frame.GetPC(), frame.GetFP())
121            frame_num = frame_num + 1
122        print ""
123        print "============================================================================================="
124        print ""
125        print "Simple stack walk algorithm:"
126        print ""
127        simple_backtrace(debugger)
128        print ""
129        print "============================================================================================="
130        print ""
131        for frame in thread.frames:
132          if not frame.IsInlined():
133            print "--------------------------------------------------------------------------------------"
134            print ""
135            print "Disassembly of %s, frame %d" % (frame.GetFunctionName(), frame.GetFrameID())
136            print ""
137            if lldb_major > 300 or (lldb_major == 300 and lldb_minor >= 18):
138                if target.triple[0:6] == "x86_64" or target.triple[0:4] == "i386":
139                  debugger.HandleCommand('disassemble -F att -a 0x%x' % frame.GetPC())
140                else:
141                  debugger.HandleCommand('disassemble -a 0x%x' % frame.GetPC())
142            else:
143              debugger.HandleCommand('disassemble -n "%s"' % frame.GetFunctionName())
144        print ""
145        print "============================================================================================="
146        print ""
147        for frame in thread.frames:
148          if not frame.IsInlined():
149            print "--------------------------------------------------------------------------------------"
150            print ""
151            print "Unwind instructions for %s, frame %d" % (frame.GetFunctionName(), frame.GetFrameID())
152            print ""
153            if lldb_major > 300 or (lldb_major == 300 and lldb_minor >= 20):
154              debugger.HandleCommand('image show-unwind -a "0x%x"' % frame.GetPC())
155            else:
156              debugger.HandleCommand('image show-unwind -n "%s"' % frame.GetFunctionName())
157
158def create_diagnose_unwind_options():
159  usage = "usage: %prog"
160  description='''Print diagnostic information about a thread backtrace which will help to debug unwind problems'''
161  parser = optparse.OptionParser(description=description, prog='diagnose_unwind',usage=usage)
162  return parser
163
164lldb.debugger.HandleCommand('command script add -f %s.diagnose_unwind diagnose-unwind' % __name__)
165print 'The "diagnose-unwind" command has been installed, type "help diagnose-unwind" for detailed help.'
166