# Usage:
#   art/test/run-test --host --gdb [--64] [--interpreter] 004-JniTest
#   'b Java_Main_shortMethod'
#   'r'
#   'command script import host_art_bt.py'
#   'host_art_bt'

import sys
import re

import lldb

def host_art_bt(debugger, command, result, internal_dict):
  prettified_frames = []
  lldb_frame_index = 0
  art_frame_index = 0
  target = debugger.GetSelectedTarget()
  process = target.GetProcess()
  thread = process.GetSelectedThread()
  while lldb_frame_index < thread.GetNumFrames():
    frame = thread.GetFrameAtIndex(lldb_frame_index)
    if frame.GetModule() and re.match(r'JIT\(.*?\)', frame.GetModule().GetFileSpec().GetFilename()):
      # Compiled Java frame

      # Get function/filename/lineno from symbol context
      symbol = frame.GetSymbol()
      if not symbol:
        print 'No symbol info for compiled Java frame: ', frame
        sys.exit(1)
      line_entry = frame.GetLineEntry()
      prettified_frames.append({
        'function': symbol.GetName(),
        'file'    : str(line_entry.GetFileSpec()) if line_entry else None,
        'line'    : line_entry.GetLine() if line_entry else -1
      })

      # Skip art frames
      while True:
        art_stack_visitor = frame.EvaluateExpression("""struct GetStackVisitor : public StackVisitor { GetStackVisitor(int depth_) : StackVisitor(Thread::Current(), NULL), depth(depth_) {} bool VisitFrame() { if (cur_depth_ == depth) { return false; } else { return true; } } int depth; }; GetStackVisitor visitor(""" + str(art_frame_index) + """); visitor.WalkStack(true); visitor""")
        art_method = frame.EvaluateExpression(art_stack_visitor.GetName() + """.GetMethod()""")
        if art_method.GetValueAsUnsigned() != 0:
          art_method_name = frame.EvaluateExpression("""art::PrettyMethod(""" + art_method.GetName() + """, true)""")
          art_method_name_data = frame.EvaluateExpression(art_method_name.GetName() + """.c_str()""").GetValueAsUnsigned()
          art_method_name_size = frame.EvaluateExpression(art_method_name.GetName() + """.length()""").GetValueAsUnsigned()
          error = lldb.SBError()
          art_method_name = process.ReadCStringFromMemory(art_method_name_data, art_method_name_size + 1, error)
          if not error.Success:
            print 'Failed to read method name'
            sys.exit(1)
          if art_method_name != symbol.GetName():
            print 'Function names in native symbol and art runtime stack do not match: ', symbol.GetName(), ' != ', art_method_name
          art_frame_index = art_frame_index + 1
          break
        art_frame_index = art_frame_index + 1

      # Skip native frames
      lldb_frame_index = lldb_frame_index + 1
      if lldb_frame_index < thread.GetNumFrames():
        frame = thread.GetFrameAtIndex(lldb_frame_index)
        if frame.GetModule() and re.match(r'JIT\(.*?\)', frame.GetModule().GetFileSpec().GetFilename()):
          # Another compile Java frame
          # Don't skip; leave it to the next iteration
          continue
        elif frame.GetSymbol() and (frame.GetSymbol().GetName() == 'art_quick_invoke_stub' or frame.GetSymbol().GetName() == 'art_quick_invoke_static_stub'):
          # art_quick_invoke_stub / art_quick_invoke_static_stub
          # Skip until we get past the next ArtMethod::Invoke()
          while True:
            lldb_frame_index = lldb_frame_index + 1
            if lldb_frame_index >= thread.GetNumFrames():
              print 'ArtMethod::Invoke not found below art_quick_invoke_stub/art_quick_invoke_static_stub'
              sys.exit(1)
            frame = thread.GetFrameAtIndex(lldb_frame_index)
            if frame.GetSymbol() and frame.GetSymbol().GetName() == 'art::mirror::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)':
              lldb_frame_index = lldb_frame_index + 1
              break
        else:
          print 'Invalid frame below compiled Java frame: ', frame
    elif frame.GetSymbol() and frame.GetSymbol().GetName() == 'art_quick_generic_jni_trampoline':
      # Interpreted JNI frame for x86_64

      # Skip art frames
      while True:
        art_stack_visitor = frame.EvaluateExpression("""struct GetStackVisitor : public StackVisitor { GetStackVisitor(int depth_) : StackVisitor(Thread::Current(), NULL), depth(depth_) {} bool VisitFrame() { if (cur_depth_ == depth) { return false; } else { return true; } } int depth; }; GetStackVisitor visitor(""" + str(art_frame_index) + """); visitor.WalkStack(true); visitor""")
        art_method = frame.EvaluateExpression(art_stack_visitor.GetName() + """.GetMethod()""")
        if art_method.GetValueAsUnsigned() != 0:
          # Get function/filename/lineno from ART runtime
          art_method_name = frame.EvaluateExpression("""art::PrettyMethod(""" + art_method.GetName() + """, true)""")
          art_method_name_data = frame.EvaluateExpression(art_method_name.GetName() + """.c_str()""").GetValueAsUnsigned()
          art_method_name_size = frame.EvaluateExpression(art_method_name.GetName() + """.length()""").GetValueAsUnsigned()
          error = lldb.SBError()
          function = process.ReadCStringFromMemory(art_method_name_data, art_method_name_size + 1, error)

          prettified_frames.append({
            'function': function,
            'file'    : None,
            'line'    : -1
          })

          art_frame_index = art_frame_index + 1
          break
        art_frame_index = art_frame_index + 1

      # Skip native frames
      lldb_frame_index = lldb_frame_index + 1
      if lldb_frame_index < thread.GetNumFrames():
        frame = thread.GetFrameAtIndex(lldb_frame_index)
        if frame.GetSymbol() and (frame.GetSymbol().GetName() == 'art_quick_invoke_stub' or frame.GetSymbol().GetName() == 'art_quick_invoke_static_stub'):
          # art_quick_invoke_stub / art_quick_invoke_static_stub
          # Skip until we get past the next ArtMethod::Invoke()
          while True:
            lldb_frame_index = lldb_frame_index + 1
            if lldb_frame_index >= thread.GetNumFrames():
              print 'ArtMethod::Invoke not found below art_quick_invoke_stub/art_quick_invoke_static_stub'
              sys.exit(1)
            frame = thread.GetFrameAtIndex(lldb_frame_index)
            if frame.GetSymbol() and frame.GetSymbol().GetName() == 'art::mirror::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)':
              lldb_frame_index = lldb_frame_index + 1
              break
        else:
          print 'Invalid frame below compiled Java frame: ', frame
    elif frame.GetSymbol() and re.search(r'art::interpreter::', frame.GetSymbol().GetName()):
      # Interpreted Java frame

      while True:
        lldb_frame_index = lldb_frame_index + 1
        if lldb_frame_index >= thread.GetNumFrames():
          print 'art::interpreter::Execute not found in interpreter frame'
          sys.exit(1)
        frame = thread.GetFrameAtIndex(lldb_frame_index)
        if frame.GetSymbol() and frame.GetSymbol().GetName() == 'art::interpreter::Execute(art::Thread*, art::MethodHelper&, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue)':
          break

      # Skip art frames
      while True:
        art_stack_visitor = frame.EvaluateExpression("""struct GetStackVisitor : public StackVisitor { GetStackVisitor(int depth_) : StackVisitor(Thread::Current(), NULL), depth(depth_) {} bool VisitFrame() { if (cur_depth_ == depth) { return false; } else { return true; } } int depth; }; GetStackVisitor visitor(""" + str(art_frame_index) + """); visitor.WalkStack(true); visitor""")
        art_method = frame.EvaluateExpression(art_stack_visitor.GetName() + """.GetMethod()""")
        if art_method.GetValueAsUnsigned() != 0:
          # Get function/filename/lineno from ART runtime
          art_method_name = frame.EvaluateExpression("""art::PrettyMethod(""" + art_method.GetName() + """, true)""")
          art_method_name_data = frame.EvaluateExpression(art_method_name.GetName() + """.c_str()""").GetValueAsUnsigned()
          art_method_name_size = frame.EvaluateExpression(art_method_name.GetName() + """.length()""").GetValueAsUnsigned()
          error = lldb.SBError()
          function = process.ReadCStringFromMemory(art_method_name_data, art_method_name_size + 1, error)

          line = frame.EvaluateExpression(art_stack_visitor.GetName() + """.GetMethod()->GetLineNumFromDexPC(""" + art_stack_visitor.GetName() + """.GetDexPc(true))""").GetValueAsUnsigned()
          
          file_name = frame.EvaluateExpression(art_method.GetName() + """->GetDeclaringClassSourceFile()""")
          file_name_data = file_name.GetValueAsUnsigned()
          file_name_size = frame.EvaluateExpression("""(size_t)strlen(""" + file_name.GetName() + """)""").GetValueAsUnsigned()
          error = lldb.SBError()
          file_name = process.ReadCStringFromMemory(file_name_data, file_name_size + 1, error)
          if not error.Success():
            print 'Failed to read source file name'
            sys.exit(1)

          prettified_frames.append({
            'function': function,
            'file'    : file_name,
            'line'    : line
          })

          art_frame_index = art_frame_index + 1
          break
        art_frame_index = art_frame_index + 1

      # Skip native frames
      while True:
        lldb_frame_index = lldb_frame_index + 1
        if lldb_frame_index >= thread.GetNumFrames():
          print 'Can not get past interpreter native frames'
          sys.exit(1)
        frame = thread.GetFrameAtIndex(lldb_frame_index)
        if frame.GetSymbol() and not re.search(r'art::interpreter::', frame.GetSymbol().GetName()):
          break
    else:
      # Other frames. Add them as-is.
      frame = thread.GetFrameAtIndex(lldb_frame_index)
      lldb_frame_index = lldb_frame_index + 1
      if frame.GetModule():
        module_name = frame.GetModule().GetFileSpec().GetFilename()
        if not module_name in ['libartd.so', 'dalvikvm32', 'dalvikvm64', 'libc.so.6']:
          prettified_frames.append({
            'function': frame.GetSymbol().GetName() if frame.GetSymbol() else None,
            'file'    : str(frame.GetLineEntry().GetFileSpec()) if frame.GetLineEntry() else None,
            'line'    : frame.GetLineEntry().GetLine() if frame.GetLineEntry() else -1
          })

  for prettified_frame in prettified_frames:
    print prettified_frame['function'], prettified_frame['file'], prettified_frame['line']

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f host_art_bt.host_art_bt host_art_bt')
