Merge "Add method tracing JVMTI callbacks"
diff --git a/runtime/Android.bp b/runtime/Android.bp
index d1e124f..26e52e0 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -245,7 +245,6 @@
         "entrypoints/quick/quick_entrypoints_enum.cc",
         "entrypoints/quick/quick_field_entrypoints.cc",
         "entrypoints/quick/quick_fillarray_entrypoints.cc",
-        "entrypoints/quick/quick_instrumentation_entrypoints.cc",
         "entrypoints/quick/quick_jni_entrypoints.cc",
         "entrypoints/quick/quick_lock_entrypoints.cc",
         "entrypoints/quick/quick_math_entrypoints.cc",
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 31a7f6a..307f9f0 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1630,8 +1630,10 @@
     @ preserve r0 (not normally an arg) knowing there is a spare slot in kSaveRefsAndArgs.
     str   r0, [sp, #4]
     mov   r2, r9         @ pass Thread::Current
-    mov   r3, lr         @ pass LR
-    blx   artInstrumentationMethodEntryFromCode  @ (Method*, Object*, Thread*, LR)
+    mov   r3, sp         @ pass SP
+    blx   artInstrumentationMethodEntryFromCode  @ (Method*, Object*, Thread*, SP)
+    cbz   r0, .Ldeliver_instrumentation_entry_exception
+                         @ Deliver exception if we got nullptr as function.
     mov   r12, r0        @ r12 holds reference to code
     ldr   r0, [sp, #4]   @ restore r0
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
@@ -1647,19 +1649,13 @@
     .cfi_adjust_cfa_offset 8
     .cfi_rel_offset r0, 0
     .cfi_rel_offset r1, 4
+    mov   r2, sp         @ store gpr_res pointer.
     vpush {d0}           @ save fp return value
     .cfi_adjust_cfa_offset 8
-    sub   sp, #8         @ space for return value argument. Note: AAPCS stack alignment is 8B, no
-                         @ need to align by 16.
-    .cfi_adjust_cfa_offset 8
-    vstr  d0, [sp]       @ d0 -> [sp] for fpr_res
-    mov   r2, r0         @ pass return value as gpr_res
-    mov   r3, r1
-    mov   r0, r9         @ pass Thread::Current
+    mov   r3, sp         @ store fpr_res pointer
     mov   r1, r12        @ pass SP
-    blx   artInstrumentationMethodExitFromCode  @ (Thread*, SP, gpr_res, fpr_res)
-    add   sp, #8
-    .cfi_adjust_cfa_offset -8
+    mov   r0, r9         @ pass Thread::Current
+    blx   artInstrumentationMethodExitFromCode  @ (Thread*, SP, gpr_res*, fpr_res*)
 
     mov   r2, r0         @ link register saved by instrumentation
     mov   lr, r1         @ r1 is holding link register if we're to bounce to deoptimize
@@ -1669,9 +1665,16 @@
     .cfi_adjust_cfa_offset -8
     .cfi_restore r0
     .cfi_restore r1
-    add sp, #32          @ remove callee save frame
-    .cfi_adjust_cfa_offset -32
-    bx    r2             @ return
+    RESTORE_SAVE_REFS_ONLY_FRAME
+    cbz   r2, .Ldo_deliver_instrumentation_exception
+                         @ Deliver exception if we got nullptr as function.
+    bx    r2             @ Otherwise, return
+.Ldeliver_instrumentation_entry_exception:
+    @ Deliver exception for art_quick_instrumentation_entry placed after
+    @ art_quick_instrumentation_exit so that the fallthrough works.
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+.Ldo_deliver_instrumentation_exception:
+    DELIVER_PENDING_EXCEPTION
 END art_quick_instrumentation_entry
 
     /*
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 18015b5..c9ead54 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2168,15 +2168,19 @@
     mov   x20, x0             // Preserve method reference in a callee-save.
 
     mov   x2, xSELF
-    mov   x3, xLR
-    bl    artInstrumentationMethodEntryFromCode  // (Method*, Object*, Thread*, LR)
+    mov   x3, sp  // Pass SP
+    bl    artInstrumentationMethodEntryFromCode  // (Method*, Object*, Thread*, SP)
 
     mov   xIP0, x0            // x0 = result of call.
     mov   x0, x20             // Reload method reference.
 
     RESTORE_SAVE_REFS_AND_ARGS_FRAME  // Note: will restore xSELF
+    cbz   xIP0, 1f            // Deliver the pending exception if method is null.
     adr   xLR, art_quick_instrumentation_exit
     br    xIP0                // Tail-call method with lr set to art_quick_instrumentation_exit.
+
+1:
+    DELIVER_PENDING_EXCEPTION
 END art_quick_instrumentation_entry
 
     .extern artInstrumentationMethodExitFromCode
@@ -2185,30 +2189,28 @@
 
     SETUP_SAVE_REFS_ONLY_FRAME
 
-    // We need to save x0 and d0. We could use a callee-save from SETUP_REF_ONLY, but then
-    // we would need to fully restore it. As there are a lot of callee-save registers, it seems
-    // easier to have an extra small stack area.
-
     str x0, [sp, #-16]!       // Save integer result.
     .cfi_adjust_cfa_offset 16
-    str d0,  [sp, #8]         // Save floating-point result.
+    str d0, [sp, #8]          // Save floating-point result.
 
+    add   x3, sp, #8          // Pass floating-point result pointer.
+    mov   x2, sp              // Pass integer result pointer.
     add   x1, sp, #16         // Pass SP.
-    mov   x2, x0              // Pass integer result.
-    fmov  x3, d0              // Pass floating-point result.
     mov   x0, xSELF           // Pass Thread.
-    bl   artInstrumentationMethodExitFromCode    // (Thread*, SP, gpr_res, fpr_res)
+    bl   artInstrumentationMethodExitFromCode    // (Thread*, SP, gpr_res*, fpr_res*)
 
     mov   xIP0, x0            // Return address from instrumentation call.
     mov   xLR, x1             // r1 is holding link register if we're to bounce to deoptimize
 
     ldr   d0, [sp, #8]        // Restore floating-point result.
     ldr   x0, [sp], #16       // Restore integer result, and drop stack area.
-    .cfi_adjust_cfa_offset 16
+    .cfi_adjust_cfa_offset -16
 
-    POP_SAVE_REFS_ONLY_FRAME
-
+    RESTORE_SAVE_REFS_ONLY_FRAME
+    cbz   xIP0, 1f            // Handle error
     br    xIP0                // Tail-call out.
+1:
+    DELIVER_PENDING_EXCEPTION
 END art_quick_instrumentation_exit
 
     /*
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 2222f5c..4fb3fe5 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -1948,17 +1948,23 @@
 DEFINE_FUNCTION art_quick_instrumentation_entry
     SETUP_SAVE_REFS_AND_ARGS_FRAME ebx, edx
     PUSH eax                      // Save eax which will be clobbered by the callee-save method.
-    subl LITERAL(12), %esp        // Align stack.
-    CFI_ADJUST_CFA_OFFSET(12)
-    pushl FRAME_SIZE_SAVE_REFS_AND_ARGS-4+16(%esp)  // Pass LR.
-    CFI_ADJUST_CFA_OFFSET(4)
+    subl LITERAL(16), %esp        // Align stack (12 bytes) and reserve space for the SP argument
+    CFI_ADJUST_CFA_OFFSET(16)     // (4 bytes). We lack the scratch registers to calculate the SP
+                                  // right now, so we will just fill it in later.
     pushl %fs:THREAD_SELF_OFFSET  // Pass Thread::Current().
     CFI_ADJUST_CFA_OFFSET(4)
     PUSH ecx                      // Pass receiver.
     PUSH eax                      // Pass Method*.
-    call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, LR)
+    leal 32(%esp), %eax           // Put original SP into eax
+    movl %eax, 12(%esp)           // set SP
+    call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, SP)
+
     addl LITERAL(28), %esp        // Pop arguments upto saved Method*.
     CFI_ADJUST_CFA_OFFSET(-28)
+
+    testl %eax, %eax
+    jz 1f                         // Test for null return (indicating exception) and handle it.
+
     movl 60(%esp), %edi           // Restore edi.
     movl %eax, 60(%esp)           // Place code* over edi, just under return pc.
     movl SYMBOL(art_quick_instrumentation_exit)@GOT(%ebx), %ebx
@@ -1980,6 +1986,12 @@
     addl LITERAL(60), %esp        // Wind stack back upto code*.
     CFI_ADJUST_CFA_OFFSET(-60)
     ret                           // Call method (and pop).
+1:
+    // Make caller handle exception
+    addl LITERAL(4), %esp
+    CFI_ADJUST_CFA_OFFSET(-4)
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    DELIVER_PENDING_EXCEPTION
 END_FUNCTION art_quick_instrumentation_entry
 
 DEFINE_FUNCTION art_quick_instrumentation_exit
@@ -1992,18 +2004,19 @@
     movq %xmm0, (%esp)
     PUSH edx                      // Save gpr return value.
     PUSH eax
-    subl LITERAL(16), %esp        // Align stack
-    CFI_ADJUST_CFA_OFFSET(16)
-    movq %xmm0, (%esp)            // Pass float return value.
-    PUSH edx                      // Pass gpr return value.
-    PUSH eax
+    leal 8(%esp), %eax            // Get pointer to fpr_result
+    movl %esp, %edx               // Get pointer to gpr_result
+    PUSH eax                      // Pass fpr_result
+    PUSH edx                      // Pass gpr_result
     PUSH ecx                      // Pass SP.
     pushl %fs:THREAD_SELF_OFFSET  // Pass Thread::Current.
     CFI_ADJUST_CFA_OFFSET(4)
-    call SYMBOL(artInstrumentationMethodExitFromCode)  // (Thread*, SP, gpr_result, fpr_result)
+    call SYMBOL(artInstrumentationMethodExitFromCode)  // (Thread*, SP, gpr_result*, fpr_result*)
+    testl %eax, %eax              // Check if we returned error.
+    jz 1f
     mov   %eax, %ecx              // Move returned link register.
-    addl LITERAL(32), %esp        // Pop arguments.
-    CFI_ADJUST_CFA_OFFSET(-32)
+    addl LITERAL(16), %esp        // Pop arguments.
+    CFI_ADJUST_CFA_OFFSET(-16)
     movl %edx, %ebx               // Move returned link register for deopt
                                   // (ebx is pretending to be our LR).
     POP eax                       // Restore gpr return value.
@@ -2015,6 +2028,11 @@
     addl LITERAL(4), %esp         // Remove fake return pc.
     CFI_ADJUST_CFA_OFFSET(-4)
     jmp   *%ecx                   // Return.
+1:
+    addl LITERAL(32), %esp
+    CFI_ADJUST_CFA_OFFSET(-32)
+    RESTORE_SAVE_REFS_ONLY_FRAME
+    DELIVER_PENDING_EXCEPTION
 END_FUNCTION art_quick_instrumentation_exit
 
     /*
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index 41651d8..46d4f41 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -1920,19 +1920,24 @@
     movq %rdi, %r12               // Preserve method pointer in a callee-save.
 
     movq %gs:THREAD_SELF_OFFSET, %rdx   // Pass thread.
-    movq FRAME_SIZE_SAVE_REFS_AND_ARGS-8(%rsp), %rcx   // Pass return PC.
+    movq %rsp, %rcx                     // Pass SP.
 
-    call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, LR)
+    call SYMBOL(artInstrumentationMethodEntryFromCode) // (Method*, Object*, Thread*, SP)
 
                                   // %rax = result of call.
-    movq %r12, %rdi               // Reload method pointer.
+    testq %rax, %rax
+    jz 1f
 
+    movq %r12, %rdi               // Reload method pointer.
     leaq art_quick_instrumentation_exit(%rip), %r12   // Set up return through instrumentation
     movq %r12, FRAME_SIZE_SAVE_REFS_AND_ARGS-8(%rsp)  // exit.
 
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
 
     jmp *%rax                     // Tail call to intended method.
+1:
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    DELIVER_PENDING_EXCEPTION
 #endif  // __APPLE__
 END_FUNCTION art_quick_instrumentation_entry
 
@@ -1948,15 +1953,16 @@
     movq  %rsp, %rsi                          // Pass SP.
 
     PUSH rax                  // Save integer result.
+    movq %rsp, %rdx           // Pass integer result pointer.
+
     subq LITERAL(8), %rsp     // Save floating-point result.
     CFI_ADJUST_CFA_OFFSET(8)
     movq %xmm0, (%rsp)
+    movq %rsp, %rcx           // Pass floating-point result pointer.
 
     movq  %gs:THREAD_SELF_OFFSET, %rdi        // Pass Thread.
-    movq  %rax, %rdx                          // Pass integer result.
-    movq  %xmm0, %rcx                         // Pass floating-point result.
 
-    call SYMBOL(artInstrumentationMethodExitFromCode)   // (Thread*, SP, gpr_res, fpr_res)
+    call SYMBOL(artInstrumentationMethodExitFromCode)   // (Thread*, SP, gpr_res*, fpr_res*)
 
     movq  %rax, %rdi          // Store return PC
     movq  %rdx, %rsi          // Store second return PC in hidden arg.
@@ -1968,9 +1974,15 @@
 
     RESTORE_SAVE_REFS_ONLY_FRAME
 
+    testq %rdi, %rdi          // Check if we have a return-pc to go to. If we don't then there was
+                              // an exception
+    jz 1f
+
     addq LITERAL(8), %rsp     // Drop fake return pc.
 
     jmp   *%rdi               // Return.
+1:
+    DELIVER_PENDING_EXCEPTION
 END_FUNCTION art_quick_instrumentation_exit
 
     /*
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 7de8916..d591e09 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -664,7 +664,9 @@
     }
     if (existing_entry_point == GetQuickInstrumentationEntryPoint()) {
       // We are running the generic jni stub, but the method is being instrumented.
-      DCHECK_EQ(pc, 0u) << "Should be a downcall";
+      // NB We would normally expect the pc to be zero but we can have non-zero pc's if
+      // instrumentation is installed or removed during the call which is using the generic jni
+      // trampoline.
       DCHECK(IsNative());
       return nullptr;
     }
diff --git a/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc b/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc
deleted file mode 100644
index 0d2f6d0..0000000
--- a/runtime/entrypoints/quick/quick_instrumentation_entrypoints.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "art_method.h"
-#include "base/callee_save_type.h"
-#include "base/enums.h"
-#include "callee_save_frame.h"
-#include "entrypoints/runtime_asm_entrypoints.h"
-#include "instrumentation.h"
-#include "mirror/object-inl.h"
-#include "runtime.h"
-#include "thread-current-inl.h"
-
-namespace art {
-
-extern "C" const void* artInstrumentationMethodEntryFromCode(ArtMethod* method,
-                                                             mirror::Object* this_object,
-                                                             Thread* self,
-                                                             uintptr_t lr)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Instrumentation changes the stack. Thus, when exiting, the stack cannot be verified, so skip
-  // that part.
-  ScopedQuickEntrypointChecks sqec(self, kIsDebugBuild, false);
-  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
-  const void* result;
-  if (instrumentation->IsDeoptimized(method)) {
-    result = GetQuickToInterpreterBridge();
-  } else {
-    result = instrumentation->GetQuickCodeFor(method, kRuntimePointerSize);
-    DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(result));
-  }
-  bool interpreter_entry = (result == GetQuickToInterpreterBridge());
-  instrumentation->PushInstrumentationStackFrame(self, method->IsStatic() ? nullptr : this_object,
-                                                 method, lr, interpreter_entry);
-  CHECK(result != nullptr) << method->PrettyMethod();
-  return result;
-}
-
-extern "C" TwoWordReturn artInstrumentationMethodExitFromCode(Thread* self, ArtMethod** sp,
-                                                              uint64_t gpr_result,
-                                                              uint64_t fpr_result)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Instrumentation exit stub must not be entered with a pending exception.
-  CHECK(!self->IsExceptionPending()) << "Enter instrumentation exit stub with pending exception "
-                                     << self->GetException()->Dump();
-  // Compute address of return PC and sanity check that it currently holds 0.
-  size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, CalleeSaveType::kSaveRefsOnly);
-  uintptr_t* return_pc = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) +
-                                                      return_pc_offset);
-  CHECK_EQ(*return_pc, 0U);
-
-  // Pop the frame filling in the return pc. The low half of the return value is 0 when
-  // deoptimization shouldn't be performed with the high-half having the return address. When
-  // deoptimization should be performed the low half is zero and the high-half the address of the
-  // deoptimization entry point.
-  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
-  TwoWordReturn return_or_deoptimize_pc = instrumentation->PopInstrumentationStackFrame(
-      self, return_pc, gpr_result, fpr_result);
-  return return_or_deoptimize_pc;
-}
-
-}  // namespace art
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index 6fcb711..b7cd39f 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -28,6 +28,7 @@
 #include "imt_conflict_table.h"
 #include "imtable-inl.h"
 #include "interpreter/interpreter.h"
+#include "instrumentation.h"
 #include "linear_alloc.h"
 #include "method_bss_mapping.h"
 #include "method_handles.h"
@@ -895,7 +896,6 @@
     soa_->Env()->DeleteLocalRef(pair.first);
   }
 }
-
 // Handler for invocation on proxy methods. On entry a frame will exist for the proxy object method
 // which is responsible for recording callee save registers. We explicitly place into jobjects the
 // incoming reference arguments (so they survive GC). We invoke the invocation handler, which is a
@@ -988,6 +988,77 @@
   }
 }
 
+extern "C" const void* artInstrumentationMethodEntryFromCode(ArtMethod* method,
+                                                             mirror::Object* this_object,
+                                                             Thread* self,
+                                                             ArtMethod** sp)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  const void* result;
+  // Instrumentation changes the stack. Thus, when exiting, the stack cannot be verified, so skip
+  // that part.
+  ScopedQuickEntrypointChecks sqec(self, kIsDebugBuild, false);
+  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+  if (instrumentation->IsDeoptimized(method)) {
+    result = GetQuickToInterpreterBridge();
+  } else {
+    result = instrumentation->GetQuickCodeFor(method, kRuntimePointerSize);
+    DCHECK(!Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(result));
+  }
+
+  bool interpreter_entry = (result == GetQuickToInterpreterBridge());
+  bool is_static = method->IsStatic();
+  uint32_t shorty_len;
+  const char* shorty =
+      method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(&shorty_len);
+
+  ScopedObjectAccessUnchecked soa(self);
+  RememberForGcArgumentVisitor visitor(sp, is_static, shorty, shorty_len, &soa);
+  visitor.VisitArguments();
+
+  instrumentation->PushInstrumentationStackFrame(self,
+                                                 is_static ? nullptr : this_object,
+                                                 method,
+                                                 QuickArgumentVisitor::GetCallingPc(sp),
+                                                 interpreter_entry);
+
+  visitor.FixupReferences();
+  if (UNLIKELY(self->IsExceptionPending())) {
+    return nullptr;
+  }
+  CHECK(result != nullptr) << method->PrettyMethod();
+  return result;
+}
+
+extern "C" TwoWordReturn artInstrumentationMethodExitFromCode(Thread* self,
+                                                              ArtMethod** sp,
+                                                              uint64_t* gpr_result,
+                                                              uint64_t* fpr_result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  DCHECK_EQ(reinterpret_cast<uintptr_t>(self), reinterpret_cast<uintptr_t>(Thread::Current()));
+  CHECK(gpr_result != nullptr);
+  CHECK(fpr_result != nullptr);
+  // Instrumentation exit stub must not be entered with a pending exception.
+  CHECK(!self->IsExceptionPending()) << "Enter instrumentation exit stub with pending exception "
+                                     << self->GetException()->Dump();
+  // Compute address of return PC and sanity check that it currently holds 0.
+  size_t return_pc_offset = GetCalleeSaveReturnPcOffset(kRuntimeISA, CalleeSaveType::kSaveRefsOnly);
+  uintptr_t* return_pc = reinterpret_cast<uintptr_t*>(reinterpret_cast<uint8_t*>(sp) +
+                                                      return_pc_offset);
+  CHECK_EQ(*return_pc, 0U);
+
+  // Pop the frame filling in the return pc. The low half of the return value is 0 when
+  // deoptimization shouldn't be performed with the high-half having the return address. When
+  // deoptimization should be performed the low half is zero and the high-half the address of the
+  // deoptimization entry point.
+  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+  TwoWordReturn return_or_deoptimize_pc = instrumentation->PopInstrumentationStackFrame(
+      self, return_pc, gpr_result, fpr_result);
+  if (self->IsExceptionPending()) {
+    return GetTwoWordFailureValue();
+  }
+  return return_or_deoptimize_pc;
+}
+
 // Lazily resolve a method for quick. Called by stub code.
 extern "C" const void* artQuickResolutionTrampoline(
     ArtMethod* called, mirror::Object* receiver, Thread* self, ArtMethod** sp)
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 9b9c70f..8120cc4 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -1123,25 +1123,40 @@
 void Instrumentation::PushInstrumentationStackFrame(Thread* self, mirror::Object* this_object,
                                                     ArtMethod* method,
                                                     uintptr_t lr, bool interpreter_entry) {
-  // We have a callee-save frame meaning this value is guaranteed to never be 0.
-  size_t frame_id = StackVisitor::ComputeNumFrames(self, kInstrumentationStackWalk);
+  DCHECK(!self->IsExceptionPending());
   std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack();
   if (kVerboseInstrumentation) {
     LOG(INFO) << "Entering " << ArtMethod::PrettyMethod(method) << " from PC "
               << reinterpret_cast<void*>(lr);
   }
-  instrumentation::InstrumentationStackFrame instrumentation_frame(this_object, method, lr,
+
+  // We send the enter event before pushing the instrumentation frame to make cleanup easier. If the
+  // event causes an exception we can simply send the unwind event and return.
+  StackHandleScope<1> hs(self);
+  Handle<mirror::Object> h_this(hs.NewHandle(this_object));
+  if (!interpreter_entry) {
+    MethodEnterEvent(self, h_this.Get(), method, 0);
+    if (self->IsExceptionPending()) {
+      MethodUnwindEvent(self, h_this.Get(), method, 0);
+      return;
+    }
+  }
+
+  // We have a callee-save frame meaning this value is guaranteed to never be 0.
+  DCHECK(!self->IsExceptionPending());
+  size_t frame_id = StackVisitor::ComputeNumFrames(self, kInstrumentationStackWalk);
+
+  instrumentation::InstrumentationStackFrame instrumentation_frame(h_this.Get(), method, lr,
                                                                    frame_id, interpreter_entry);
   stack->push_front(instrumentation_frame);
-
-  if (!interpreter_entry) {
-    MethodEnterEvent(self, this_object, method, 0);
-  }
 }
 
-TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintptr_t* return_pc,
-                                                            uint64_t gpr_result,
-                                                            uint64_t fpr_result) {
+TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
+                                                            uintptr_t* return_pc,
+                                                            uint64_t* gpr_result,
+                                                            uint64_t* fpr_result) {
+  DCHECK(gpr_result != nullptr);
+  DCHECK(fpr_result != nullptr);
   // Do the pop.
   std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack();
   CHECK_GT(stack->size(), 0U);
@@ -1157,13 +1172,20 @@
   uint32_t length;
   const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
   char return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
+  bool is_ref = return_shorty == '[' || return_shorty == 'L';
+  StackHandleScope<1> hs(self);
+  MutableHandle<mirror::Object> res(hs.NewHandle<mirror::Object>(nullptr));
   JValue return_value;
   if (return_shorty == 'V') {
     return_value.SetJ(0);
   } else if (return_shorty == 'F' || return_shorty == 'D') {
-    return_value.SetJ(fpr_result);
+    return_value.SetJ(*fpr_result);
   } else {
-    return_value.SetJ(gpr_result);
+    return_value.SetJ(*gpr_result);
+  }
+  if (is_ref) {
+    // Take a handle to the return value so we won't lose it if we suspend.
+    res.Assign(return_value.GetL());
   }
   // TODO: improve the dex pc information here, requires knowledge of current PC as opposed to
   //       return_pc.
@@ -1180,6 +1202,10 @@
   bool deoptimize = (visitor.caller != nullptr) &&
                     (interpreter_stubs_installed_ || IsDeoptimized(visitor.caller) ||
                     Dbg::IsForcedInterpreterNeededForUpcall(self, visitor.caller));
+  if (is_ref) {
+    // Restore the return value if it's a reference since it might have moved.
+    *reinterpret_cast<mirror::Object**>(gpr_result) = res.Get();
+  }
   if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) {
     if (kVerboseInstrumentation) {
       LOG(INFO) << "Deoptimizing "
@@ -1214,9 +1240,8 @@
   // Do the pop.
   std::deque<instrumentation::InstrumentationStackFrame>* stack = self->GetInstrumentationStack();
   CHECK_GT(stack->size(), 0U);
+  size_t idx = stack->size();
   InstrumentationStackFrame instrumentation_frame = stack->front();
-  // TODO: bring back CheckStackDepth(self, instrumentation_frame, 2);
-  stack->pop_front();
 
   ArtMethod* method = instrumentation_frame.method_;
   if (is_deoptimization) {
@@ -1234,6 +1259,10 @@
     uint32_t dex_pc = DexFile::kDexNoIndex;
     MethodUnwindEvent(self, instrumentation_frame.this_object_, method, dex_pc);
   }
+  // TODO: bring back CheckStackDepth(self, instrumentation_frame, 2);
+  CHECK_EQ(stack->size(), idx);
+  DCHECK(instrumentation_frame.method_ == stack->front().method_);
+  stack->pop_front();
   return instrumentation_frame.return_pc_;
 }
 
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 363985f..90b5def 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -432,9 +432,13 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Called when an instrumented method is exited. Removes the pushed instrumentation frame
-  // returning the intended link register. Generates method exit events.
+  // returning the intended link register. Generates method exit events. The gpr_result and
+  // fpr_result pointers are pointers to the locations where the integer/pointer and floating point
+  // result values of the function are stored. Both pointers must always be valid but the values
+  // held there will only be meaningful if interpreted as the appropriate type given the function
+  // being returned from.
   TwoWordReturn PopInstrumentationStackFrame(Thread* self, uintptr_t* return_pc,
-                                             uint64_t gpr_result, uint64_t fpr_result)
+                                             uint64_t* gpr_result, uint64_t* fpr_result)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_);
 
   // Pops an instrumentation frame from the current thread and generate an unwind event.
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 4bc0f2f..85cf73b 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -254,6 +254,13 @@
     if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
       instrumentation->MethodEnterEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
                                         method, 0);
+      if (UNLIKELY(self->IsExceptionPending())) {
+        instrumentation->MethodUnwindEvent(self,
+                                           shadow_frame.GetThisObject(code_item->ins_size_),
+                                           method,
+                                           0);
+        return JValue();
+      }
     }
 
     if (!stay_in_interpreter) {
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index 32a2378..45788e7 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -26,13 +26,13 @@
 namespace art {
 namespace interpreter {
 
-#define HANDLE_PENDING_EXCEPTION()                                                              \
+#define HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instr)                                    \
   do {                                                                                          \
     DCHECK(self->IsExceptionPending());                                                         \
     self->AllowThreadSuspension();                                                              \
     uint32_t found_dex_pc = FindNextInstructionFollowingException(self, shadow_frame,           \
                                                                   inst->GetDexPc(insns),        \
-                                                                  instrumentation);             \
+                                                                  instr);                       \
     if (found_dex_pc == DexFile::kDexNoIndex) {                                                 \
       /* Structured locking is to be enforced for abnormal termination, too. */                 \
       DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame);                        \
@@ -47,6 +47,8 @@
     }                                                                                           \
   } while (false)
 
+#define HANDLE_PENDING_EXCEPTION() HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instrumentation)
+
 #define POSSIBLY_HANDLE_PENDING_EXCEPTION(_is_exception_pending, _next_function)  \
   do {                                                                            \
     if (UNLIKELY(_is_exception_pending)) {                                        \
@@ -218,6 +220,10 @@
           instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
                                            shadow_frame.GetMethod(), inst->GetDexPc(insns),
                                            result);
+          if (UNLIKELY(self->IsExceptionPending())) {
+            // Don't send another method exit event.
+            HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
+          }
         }
         if (interpret_one_instruction) {
           /* Signal mterp to return to caller */
@@ -235,6 +241,10 @@
           instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
                                            shadow_frame.GetMethod(), inst->GetDexPc(insns),
                                            result);
+          if (UNLIKELY(self->IsExceptionPending())) {
+            // Don't send another method exit event.
+            HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
+          }
         }
         if (interpret_one_instruction) {
           /* Signal mterp to return to caller */
@@ -253,6 +263,10 @@
           instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
                                            shadow_frame.GetMethod(), inst->GetDexPc(insns),
                                            result);
+          if (UNLIKELY(self->IsExceptionPending())) {
+            // Don't send another method exit event.
+            HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
+          }
         }
         if (interpret_one_instruction) {
           /* Signal mterp to return to caller */
@@ -270,6 +284,10 @@
           instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
                                            shadow_frame.GetMethod(), inst->GetDexPc(insns),
                                            result);
+          if (UNLIKELY(self->IsExceptionPending())) {
+            // Don't send another method exit event.
+            HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
+          }
         }
         if (interpret_one_instruction) {
           /* Signal mterp to return to caller */
@@ -307,6 +325,10 @@
           instrumentation->MethodExitEvent(self, shadow_frame.GetThisObject(code_item->ins_size_),
                                            shadow_frame.GetMethod(), inst->GetDexPc(insns),
                                            result);
+          if (UNLIKELY(self->IsExceptionPending())) {
+            // Don't send another method exit event.
+            HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(nullptr);
+          }
           // Re-load since it might have moved during the MethodExitEvent.
           result.SetL(shadow_frame.GetVRegReference(ref_idx));
         }
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 45773fd..3ec5b32 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1731,6 +1731,7 @@
 }
 
 extern "C" bool ArtPlugin_Deinitialize() {
+  gEventHandler.Shutdown();
   PhaseUtil::Unregister();
   ThreadUtil::Unregister();
   ClassUtil::Unregister();
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 4b83b7c..af85fb0 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -218,8 +218,8 @@
     .can_redefine_any_class                          = 0,
     .can_get_current_thread_cpu_time                 = 0,
     .can_get_thread_cpu_time                         = 0,
-    .can_generate_method_entry_events                = 0,
-    .can_generate_method_exit_events                 = 0,
+    .can_generate_method_entry_events                = 1,
+    .can_generate_method_exit_events                 = 1,
     .can_generate_all_class_hook_events              = 0,
     .can_generate_compiled_method_load_events        = 0,
     .can_generate_monitor_events                     = 0,
diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h
index 57abf31..cb7e6a9 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/runtime/openjdkjvmti/events-inl.h
@@ -20,6 +20,7 @@
 #include <array>
 
 #include "events.h"
+#include "ScopedLocalRef.h"
 
 #include "art_jvmti.h"
 
@@ -135,6 +136,8 @@
       continue;
     }
     if (ShouldDispatch<kEvent>(env, thread)) {
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
       jint new_len = 0;
       unsigned char* new_data = nullptr;
       auto callback = impl::GetCallback<kEvent>(env);
@@ -148,6 +151,9 @@
                current_class_data,
                &new_len,
                &new_data);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
       if (new_data != nullptr && new_data != current_class_data) {
         // Destroy the data the last transformer made. We skip this if the previous state was the
         // initial one since we don't know here which jvmtiEnv allocated it.
@@ -180,6 +186,25 @@
   }
 }
 
+// Events with JNIEnvs need to stash pending exceptions since they can cause new ones to be thrown.
+// In accordance with the JVMTI specification we allow exceptions originating from events to
+// overwrite the current exception, including exceptions originating from earlier events.
+// TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list of the
+// newest exception.
+template <ArtJvmtiEvent kEvent, typename ...Args>
+inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const {
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr) {
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
+      DispatchEvent<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
+    }
+  }
+}
+
 template <ArtJvmtiEvent kEvent, typename ...Args>
 inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const {
   using FnType = void(jvmtiEnv*, Args...);
diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc
index 320c59c..90bc122 100644
--- a/runtime/openjdkjvmti/events.cc
+++ b/runtime/openjdkjvmti/events.cc
@@ -32,19 +32,24 @@
 #include "events-inl.h"
 
 #include "art_jvmti.h"
+#include "art_method-inl.h"
 #include "base/logging.h"
 #include "gc/allocation_listener.h"
 #include "gc/gc_pause_listener.h"
 #include "gc/heap.h"
+#include "gc/scoped_gc_critical_section.h"
 #include "handle_scope-inl.h"
 #include "instrumentation.h"
 #include "jni_env_ext-inl.h"
+#include "jni_internal.h"
 #include "mirror/class.h"
 #include "mirror/object-inl.h"
 #include "runtime.h"
 #include "ScopedLocalRef.h"
 #include "scoped_thread_state_change-inl.h"
-#include "thread-current-inl.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
 
 namespace openjdkjvmti {
 
@@ -294,6 +299,222 @@
   }
 }
 
+template<typename Type>
+static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj);
+}
+
+class JvmtiMethodTraceListener FINAL : public art::instrumentation::InstrumentationListener {
+ public:
+  explicit JvmtiMethodTraceListener(EventHandler* handler) : event_handler_(handler) {}
+
+  template<ArtJvmtiEvent kEvent, typename ...Args>
+  void RunEventCallback(art::Thread* self, art::JNIEnvExt* jnienv, Args... args)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer()));
+    // Just give the event a good sized JNI frame. 100 should be fine.
+    jnienv->PushFrame(100);
+    {
+      // Need to do trampoline! :(
+      art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+      event_handler_->DispatchEvent<kEvent>(self,
+                                            static_cast<JNIEnv*>(jnienv),
+                                            thread_jni.get(),
+                                            args...);
+    }
+    jnienv->PopFrame();
+  }
+
+  // Call-back for when a method is entered.
+  void MethodEntered(art::Thread* self,
+                     art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                     art::ArtMethod* method,
+                     uint32_t dex_pc ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (!method->IsRuntimeMethod() &&
+        event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodEntry)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      RunEventCallback<ArtJvmtiEvent::kMethodEntry>(self,
+                                                    jnienv,
+                                                    art::jni::EncodeArtMethod(method));
+    }
+  }
+
+  // Callback for when a method is exited with a reference return value.
+  void MethodExited(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc ATTRIBUTE_UNUSED,
+                    art::Handle<art::mirror::Object> return_value)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (!method->IsRuntimeMethod() &&
+        event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) {
+      DCHECK_EQ(method->GetReturnTypePrimitive(), art::Primitive::kPrimNot)
+          << method->PrettyMethod();
+      DCHECK(!self->IsExceptionPending());
+      jvalue val;
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      ScopedLocalRef<jobject> return_jobj(jnienv, AddLocalRef<jobject>(jnienv, return_value.Get()));
+      val.l = return_jobj.get();
+      RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          /*was_popped_by_exception*/ static_cast<jboolean>(JNI_FALSE),
+          val);
+    }
+  }
+
+  // Call-back for when a method is exited.
+  void MethodExited(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc ATTRIBUTE_UNUSED,
+                    const art::JValue& return_value)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (!method->IsRuntimeMethod() &&
+        event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) {
+      DCHECK_NE(method->GetReturnTypePrimitive(), art::Primitive::kPrimNot)
+          << method->PrettyMethod();
+      DCHECK(!self->IsExceptionPending());
+      jvalue val;
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      // 64bit integer is the largest value in the union so we should be fine simply copying it into
+      // the union.
+      val.j = return_value.GetJ();
+      RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          /*was_popped_by_exception*/ static_cast<jboolean>(JNI_FALSE),
+          val);
+    }
+  }
+
+  // Call-back for when a method is popped due to an exception throw. A method will either cause a
+  // MethodExited call-back or a MethodUnwind call-back when its activation is removed.
+  void MethodUnwind(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (!method->IsRuntimeMethod() &&
+        event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) {
+      jvalue val;
+      // Just set this to 0xffffffffffffffff so it's not uninitialized.
+      val.j = static_cast<jlong>(-1);
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      art::StackHandleScope<1> hs(self);
+      art::Handle<art::mirror::Throwable> old_exception(hs.NewHandle(self->GetException()));
+      CHECK(!old_exception.IsNull());
+      self->ClearException();
+      RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          /*was_popped_by_exception*/ static_cast<jboolean>(JNI_TRUE),
+          val);
+      // Match RI behavior of just throwing away original exception if a new one is thrown.
+      if (LIKELY(!self->IsExceptionPending())) {
+        self->SetException(old_exception.Get());
+      }
+    }
+  }
+
+  // Call-back for when the dex pc moves in a method. We don't currently have any events associated
+  // with this.
+  void DexPcMoved(art::Thread* self ATTRIBUTE_UNUSED,
+                  art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                  art::ArtMethod* method ATTRIBUTE_UNUSED,
+                  uint32_t new_dex_pc ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+  // Call-back for when we read from a field.
+  void FieldRead(art::Thread* self ATTRIBUTE_UNUSED,
+                 art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                 art::ArtMethod* method ATTRIBUTE_UNUSED,
+                 uint32_t dex_pc ATTRIBUTE_UNUSED,
+                 art::ArtField* field ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+  // Call-back for when we write into a field.
+  void FieldWritten(art::Thread* self ATTRIBUTE_UNUSED,
+                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                    art::ArtMethod* method ATTRIBUTE_UNUSED,
+                    uint32_t dex_pc ATTRIBUTE_UNUSED,
+                    art::ArtField* field ATTRIBUTE_UNUSED,
+                    const art::JValue& field_value ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+  // Call-back when an exception is caught.
+  void ExceptionCaught(art::Thread* self ATTRIBUTE_UNUSED,
+                       art::Handle<art::mirror::Throwable> exception_object ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+  // Call-back for when we execute a branch.
+  void Branch(art::Thread* self ATTRIBUTE_UNUSED,
+              art::ArtMethod* method ATTRIBUTE_UNUSED,
+              uint32_t dex_pc ATTRIBUTE_UNUSED,
+              int32_t dex_pc_offset ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+  // Call-back for when we get an invokevirtual or an invokeinterface.
+  void InvokeVirtualOrInterface(art::Thread* self ATTRIBUTE_UNUSED,
+                                art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                                art::ArtMethod* caller ATTRIBUTE_UNUSED,
+                                uint32_t dex_pc ATTRIBUTE_UNUSED,
+                                art::ArtMethod* callee ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+ private:
+  EventHandler* const event_handler_;
+};
+
+static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) {
+  switch (event) {
+    case ArtJvmtiEvent::kMethodEntry:
+      return art::instrumentation::Instrumentation::kMethodEntered;
+    case ArtJvmtiEvent::kMethodExit:
+      return art::instrumentation::Instrumentation::kMethodExited |
+             art::instrumentation::Instrumentation::kMethodUnwind;
+    default:
+      LOG(FATAL) << "Unknown event ";
+      return 0;
+  }
+}
+
+static void SetupMethodTraceListener(JvmtiMethodTraceListener* listener,
+                                     ArtJvmtiEvent event,
+                                     bool enable) {
+  uint32_t new_events = GetInstrumentationEventsFor(event);
+  art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
+  art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(),
+                                       art::gc::kGcCauseInstrumentation,
+                                       art::gc::kCollectorTypeInstrumentation);
+  art::ScopedSuspendAll ssa("jvmti method tracing installation");
+  if (enable) {
+    if (!instr->AreAllMethodsDeoptimized()) {
+      instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true);
+    }
+    instr->AddListener(listener, new_events);
+  } else {
+    instr->RemoveListener(listener, new_events);
+  }
+}
+
 // Handle special work for the given event type, if necessary.
 void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
   switch (event) {
@@ -306,6 +527,11 @@
       SetupGcPauseTracking(gc_pause_listener_.get(), event, enable);
       return;
 
+    case ArtJvmtiEvent::kMethodEntry:
+    case ArtJvmtiEvent::kMethodExit:
+      SetupMethodTraceListener(method_trace_listener_.get(), event, enable);
+      return;
+
     default:
       break;
   }
@@ -419,9 +645,21 @@
   return ERR(NONE);
 }
 
+void EventHandler::Shutdown() {
+  // Need to remove the method_trace_listener_ if it's there.
+  art::Thread* self = art::Thread::Current();
+  art::gc::ScopedGCCriticalSection gcs(self,
+                                       art::gc::kGcCauseInstrumentation,
+                                       art::gc::kCollectorTypeInstrumentation);
+  art::ScopedSuspendAll ssa("jvmti method tracing uninstallation");
+  // Just remove every possible event.
+  art::Runtime::Current()->GetInstrumentation()->RemoveListener(method_trace_listener_.get(), ~0);
+}
+
 EventHandler::EventHandler() {
   alloc_listener_.reset(new JvmtiAllocationListener(this));
   gc_pause_listener_.reset(new JvmtiGcPauseListener(this));
+  method_trace_listener_.reset(new JvmtiMethodTraceListener(this));
 }
 
 EventHandler::~EventHandler() {
diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h
index b9e3cf0..5f37dcf 100644
--- a/runtime/openjdkjvmti/events.h
+++ b/runtime/openjdkjvmti/events.h
@@ -29,6 +29,7 @@
 struct ArtJvmTiEnv;
 class JvmtiAllocationListener;
 class JvmtiGcPauseListener;
+class JvmtiMethodTraceListener;
 
 // an enum for ArtEvents. This differs from the JVMTI events only in that we distinguish between
 // retransformation capable and incapable loading
@@ -137,6 +138,9 @@
   EventHandler();
   ~EventHandler();
 
+  // do cleanup for the event handler.
+  void Shutdown();
+
   // Register an env. It is assumed that this happens on env creation, that is, no events are
   // enabled, yet.
   void RegisterArtJvmTiEnv(ArtJvmTiEnv* env);
@@ -160,6 +164,12 @@
   template <ArtJvmtiEvent kEvent, typename ...Args>
   ALWAYS_INLINE
   inline void DispatchEvent(art::Thread* thread, Args... args) const;
+  // Dispatch event to all registered environments stashing exceptions as needed. This works since
+  // JNIEnv* is always the second argument if it is passed to an event. Needed since C++ does not
+  // allow partial template function specialization.
+  template <ArtJvmtiEvent kEvent, typename ...Args>
+  ALWAYS_INLINE
+  void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const;
   // Dispatch event to the given environment, only.
   template <ArtJvmtiEvent kEvent, typename ...Args>
   ALWAYS_INLINE
@@ -211,6 +221,7 @@
 
   std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
   std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
+  std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
 };
 
 }  // namespace openjdkjvmti
diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt
new file mode 100644
index 0000000..8c67d66
--- /dev/null
+++ b/test/988-method-trace/expected.txt
@@ -0,0 +1,276 @@
+<= public static native void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null>
+=> art.Test988$IterOp()
+.=> public java.lang.Object()
+.<= public java.lang.Object() -> <null: null>
+<= art.Test988$IterOp() -> <null: null>
+=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator)
+.=> public int art.Test988$IterOp.applyAsInt(int)
+..=> static int art.Test988.iter_fibonacci(int)
+..<= static int art.Test988.iter_fibonacci(int) -> <class java.lang.Integer: 832040>
+.<= public int art.Test988$IterOp.applyAsInt(int) -> <class java.lang.Integer: 832040>
+.=> public art.Test988$FibResult(java.lang.String,int,int)
+..=> public java.lang.Object()
+..<= public java.lang.Object() -> <null: null>
+.<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
+.=> public boolean java.util.ArrayList.add(java.lang.Object)
+..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+fibonacci(30)=832040
+.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
+=> art.Test988$RecurOp()
+.=> public java.lang.Object()
+.<= public java.lang.Object() -> <null: null>
+<= art.Test988$RecurOp() -> <null: null>
+=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator)
+.=> public int art.Test988$RecurOp.applyAsInt(int)
+..=> static int art.Test988.fibonacci(int)
+...=> static int art.Test988.fibonacci(int)
+....=> static int art.Test988.fibonacci(int)
+.....=> static int art.Test988.fibonacci(int)
+......=> static int art.Test988.fibonacci(int)
+......<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1>
+......=> static int art.Test988.fibonacci(int)
+......<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 0>
+.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1>
+.....=> static int art.Test988.fibonacci(int)
+.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1>
+....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 2>
+....=> static int art.Test988.fibonacci(int)
+.....=> static int art.Test988.fibonacci(int)
+.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1>
+.....=> static int art.Test988.fibonacci(int)
+.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 0>
+....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1>
+...<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 3>
+...=> static int art.Test988.fibonacci(int)
+....=> static int art.Test988.fibonacci(int)
+.....=> static int art.Test988.fibonacci(int)
+.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1>
+.....=> static int art.Test988.fibonacci(int)
+.....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 0>
+....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1>
+....=> static int art.Test988.fibonacci(int)
+....<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 1>
+...<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 2>
+..<= static int art.Test988.fibonacci(int) -> <class java.lang.Integer: 5>
+.<= public int art.Test988$RecurOp.applyAsInt(int) -> <class java.lang.Integer: 5>
+.=> public art.Test988$FibResult(java.lang.String,int,int)
+..=> public java.lang.Object()
+..<= public java.lang.Object() -> <null: null>
+.<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
+.=> public boolean java.util.ArrayList.add(java.lang.Object)
+..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+fibonacci(5)=5
+.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
+=> art.Test988$IterOp()
+.=> public java.lang.Object()
+.<= public java.lang.Object() -> <null: null>
+<= art.Test988$IterOp() -> <null: null>
+=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator)
+.=> public int art.Test988$IterOp.applyAsInt(int)
+..=> static int art.Test988.iter_fibonacci(int)
+...=> public java.lang.StringBuilder()
+....=> java.lang.AbstractStringBuilder(int)
+.....=> public java.lang.Object()
+.....<= public java.lang.Object() -> <null: null>
+....<= java.lang.AbstractStringBuilder(int) -> <null: null>
+...<= public java.lang.StringBuilder() -> <null: null>
+...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)
+....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String)
+.....=> public int java.lang.String.length()
+.....<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
+.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
+.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
+.....=> public void java.lang.String.getChars(int,int,char[],int)
+......=> public int java.lang.String.length()
+......<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
+......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int)
+......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null>
+.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null>
+....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...=> public java.lang.StringBuilder java.lang.StringBuilder.append(int)
+....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int)
+.....=> static int java.lang.Integer.stringSize(int)
+.....<= static int java.lang.Integer.stringSize(int) -> <class java.lang.Integer: 2>
+.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
+......=> private int java.lang.AbstractStringBuilder.newCapacity(int)
+......<= private int java.lang.AbstractStringBuilder.newCapacity(int) -> <class java.lang.Integer: 34>
+......=> public static char[] java.util.Arrays.copyOf(char[],int)
+.......=> public static int java.lang.Math.min(int,int)
+.......<= public static int java.lang.Math.min(int,int) -> <class java.lang.Integer: 16>
+.......=> public static void java.lang.System.arraycopy(char[],int,char[],int,int)
+.......<= public static void java.lang.System.arraycopy(char[],int,char[],int,int) -> <null: null>
+......<= public static char[] java.util.Arrays.copyOf(char[],int) -> <class [C: [B, a, d,  , a, r, g, u, m, e, n, t, :,  , -, 1, 9,  , <,  , 0, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>]>
+.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
+.....=> static void java.lang.Integer.getChars(int,int,char[])
+.....<= static void java.lang.Integer.getChars(int,int,char[]) -> <null: null>
+....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...<= public java.lang.StringBuilder java.lang.StringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)
+....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String)
+.....=> public int java.lang.String.length()
+.....<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
+.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
+.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
+.....=> public void java.lang.String.getChars(int,int,char[],int)
+......=> public int java.lang.String.length()
+......<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
+......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int)
+......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null>
+.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null>
+....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...=> public java.lang.String java.lang.StringBuilder.toString()
+....=> static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[])
+....<= static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0>
+...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0>
+...=> public java.lang.Error(java.lang.String)
+....=> public java.lang.Throwable(java.lang.String)
+.....=> public java.lang.Object()
+.....<= public java.lang.Object() -> <null: null>
+.....=> public static final java.util.List java.util.Collections.emptyList()
+.....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []>
+.....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace()
+......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
+......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
+.....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0
+	at art.Test988.iter_fibonacci(Test988.java:203)
+	at art.Test988$IterOp.applyAsInt(Test988.java:198)
+	at art.Test988.doFibTest(Test988.java:291)
+	at art.Test988.run(Test988.java:261)
+	at Main.main(Main.java:19)
+>
+....<= public java.lang.Throwable(java.lang.String) -> <null: null>
+...<= public java.lang.Error(java.lang.String) -> <null: null>
+..<= static int art.Test988.iter_fibonacci(int) EXCEPTION
+.<= public int art.Test988$IterOp.applyAsInt(int) EXCEPTION
+.=> public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable)
+..=> public java.lang.Object()
+..<= public java.lang.Object() -> <null: null>
+.<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
+.=> public boolean java.util.ArrayList.add(java.lang.Object)
+..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0
+	at art.Test988.iter_fibonacci(Test988.java:203)
+	at art.Test988$IterOp.applyAsInt(Test988.java:198)
+	at art.Test988.doFibTest(Test988.java:291)
+	at art.Test988.run(Test988.java:261)
+	at Main.main(Main.java:19)
+
+.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
+=> art.Test988$RecurOp()
+.=> public java.lang.Object()
+.<= public java.lang.Object() -> <null: null>
+<= art.Test988$RecurOp() -> <null: null>
+=> public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator)
+.=> public int art.Test988$RecurOp.applyAsInt(int)
+..=> static int art.Test988.fibonacci(int)
+...=> public java.lang.StringBuilder()
+....=> java.lang.AbstractStringBuilder(int)
+.....=> public java.lang.Object()
+.....<= public java.lang.Object() -> <null: null>
+....<= java.lang.AbstractStringBuilder(int) -> <null: null>
+...<= public java.lang.StringBuilder() -> <null: null>
+...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)
+....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String)
+.....=> public int java.lang.String.length()
+.....<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
+.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
+.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
+.....=> public void java.lang.String.getChars(int,int,char[],int)
+......=> public int java.lang.String.length()
+......<= public int java.lang.String.length() -> <class java.lang.Integer: 14>
+......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int)
+......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null>
+.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null>
+....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...=> public java.lang.StringBuilder java.lang.StringBuilder.append(int)
+....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int)
+.....=> static int java.lang.Integer.stringSize(int)
+.....<= static int java.lang.Integer.stringSize(int) -> <class java.lang.Integer: 2>
+.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
+......=> private int java.lang.AbstractStringBuilder.newCapacity(int)
+......<= private int java.lang.AbstractStringBuilder.newCapacity(int) -> <class java.lang.Integer: 34>
+......=> public static char[] java.util.Arrays.copyOf(char[],int)
+.......=> public static int java.lang.Math.min(int,int)
+.......<= public static int java.lang.Math.min(int,int) -> <class java.lang.Integer: 16>
+.......=> public static void java.lang.System.arraycopy(char[],int,char[],int,int)
+.......<= public static void java.lang.System.arraycopy(char[],int,char[],int,int) -> <null: null>
+......<= public static char[] java.util.Arrays.copyOf(char[],int) -> <class [C: [B, a, d,  , a, r, g, u, m, e, n, t, :,  , -, 1, 9,  , <,  , 0, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>, <control-0000>]>
+.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
+.....=> static void java.lang.Integer.getChars(int,int,char[])
+.....<= static void java.lang.Integer.getChars(int,int,char[]) -> <null: null>
+....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...<= public java.lang.StringBuilder java.lang.StringBuilder.append(int) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...=> public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)
+....=> public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String)
+.....=> public int java.lang.String.length()
+.....<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
+.....=> private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int)
+.....<= private void java.lang.AbstractStringBuilder.ensureCapacityInternal(int) -> <null: null>
+.....=> public void java.lang.String.getChars(int,int,char[],int)
+......=> public int java.lang.String.length()
+......<= public int java.lang.String.length() -> <class java.lang.Integer: 4>
+......=> native void java.lang.String.getCharsNoCheck(int,int,char[],int)
+......<= native void java.lang.String.getCharsNoCheck(int,int,char[],int) -> <null: null>
+.....<= public void java.lang.String.getChars(int,int,char[],int) -> <null: null>
+....<= public java.lang.AbstractStringBuilder java.lang.AbstractStringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...<= public java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String) -> <class java.lang.StringBuilder: Bad argument: -19 < 0>
+...=> public java.lang.String java.lang.StringBuilder.toString()
+....=> static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[])
+....<= static native java.lang.String java.lang.StringFactory.newStringFromChars(int,int,char[]) -> <class java.lang.String: Bad argument: -19 < 0>
+...<= public java.lang.String java.lang.StringBuilder.toString() -> <class java.lang.String: Bad argument: -19 < 0>
+...=> public java.lang.Error(java.lang.String)
+....=> public java.lang.Throwable(java.lang.String)
+.....=> public java.lang.Object()
+.....<= public java.lang.Object() -> <null: null>
+.....=> public static final java.util.List java.util.Collections.emptyList()
+.....<= public static final java.util.List java.util.Collections.emptyList() -> <class java.util.Collections$EmptyList: []>
+.....=> public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace()
+......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
+......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
+.....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0
+	at art.Test988.fibonacci(Test988.java:225)
+	at art.Test988$RecurOp.applyAsInt(Test988.java:220)
+	at art.Test988.doFibTest(Test988.java:291)
+	at art.Test988.run(Test988.java:262)
+	at Main.main(Main.java:19)
+>
+....<= public java.lang.Throwable(java.lang.String) -> <null: null>
+...<= public java.lang.Error(java.lang.String) -> <null: null>
+..<= static int art.Test988.fibonacci(int) EXCEPTION
+.<= public int art.Test988$RecurOp.applyAsInt(int) EXCEPTION
+.=> public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable)
+..=> public java.lang.Object()
+..<= public java.lang.Object() -> <null: null>
+.<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
+.=> public boolean java.util.ArrayList.add(java.lang.Object)
+..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0
+	at art.Test988.fibonacci(Test988.java:225)
+	at art.Test988$RecurOp.applyAsInt(Test988.java:220)
+	at art.Test988.doFibTest(Test988.java:291)
+	at art.Test988.run(Test988.java:262)
+	at Main.main(Main.java:19)
+
+.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
+=> public static native java.lang.Thread java.lang.Thread.currentThread()
+<= public static native java.lang.Thread java.lang.Thread.currentThread() -> <class java.lang.Thread: <non-deterministic>>
+=> public static native void art.Trace.disableMethodTracing(java.lang.Thread)
diff --git a/test/988-method-trace/info.txt b/test/988-method-trace/info.txt
new file mode 100644
index 0000000..f0a200d
--- /dev/null
+++ b/test/988-method-trace/info.txt
@@ -0,0 +1,15 @@
+Tests method tracing in JVMTI
+
+This test is sensitive to the internal implementations of:
+ * java.lang.Error
+ * java.lang.Integer
+ * java.lang.Math
+ * java.lang.String
+ * java.lang.System
+ * java.util.ArrayList
+ * java.util.Arrays
+ * java.util.StringBuilder
+ * all super-classes and super-interfaces of the above types.
+
+Changes to the internal implementation of these classes might (or might not)
+change the output of this test.
diff --git a/test/988-method-trace/run b/test/988-method-trace/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/988-method-trace/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/988-method-trace/src/Main.java b/test/988-method-trace/src/Main.java
new file mode 100644
index 0000000..9dd1142
--- /dev/null
+++ b/test/988-method-trace/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test988.run();
+  }
+}
diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java
new file mode 100644
index 0000000..6ac7b11
--- /dev/null
+++ b/test/988-method-trace/src/art/Test988.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.function.IntUnaryOperator;
+import java.util.function.Function;
+
+public class Test988 {
+
+    // Methods with non-deterministic output that should not be printed.
+    static Set<Method> NON_DETERMINISTIC_OUTPUT_METHODS = new HashSet<>();
+
+    static {
+      try {
+        NON_DETERMINISTIC_OUTPUT_METHODS.add(
+            Throwable.class.getDeclaredMethod("nativeFillInStackTrace"));
+      } catch (Exception e) {}
+      try {
+        NON_DETERMINISTIC_OUTPUT_METHODS.add(Thread.class.getDeclaredMethod("currentThread"));
+      } catch (Exception e) {}
+    }
+
+    static interface Printable {
+        public void Print();
+    }
+
+    static final class MethodEntry implements Printable {
+        private Object m;
+        private int cnt;
+        public MethodEntry(Object m, int cnt) {
+            this.m = m;
+            this.cnt = cnt;
+        }
+        @Override
+        public void Print() {
+            System.out.println(whitespace(cnt) + "=> " + m);
+        }
+    }
+
+    private static String genericToString(Object val) {
+      if (val == null) {
+        return "null";
+      } else if (val.getClass().isArray()) {
+        return arrayToString(val);
+      } else if (val instanceof Throwable) {
+        StringWriter w = new StringWriter();
+        ((Throwable) val).printStackTrace(new PrintWriter(w));
+        return w.toString();
+      } else {
+        return val.toString();
+      }
+    }
+
+    private static String charArrayToString(char[] src) {
+      String[] res = new String[src.length];
+      for (int i = 0; i < src.length; i++) {
+        if (Character.isISOControl(src[i])) {
+          res[i] = Character.getName(src[i]);
+        } else {
+          res[i] = Character.toString(src[i]);
+        }
+      }
+      return Arrays.toString(res);
+    }
+
+    private static String arrayToString(Object val) {
+      Class<?> klass = val.getClass();
+      if ((new Object[0]).getClass().isAssignableFrom(klass)) {
+        return Arrays.toString(
+            Arrays.stream((Object[])val).map(Test988::genericToString).toArray());
+      } else if ((new byte[0]).getClass().isAssignableFrom(klass)) {
+        return Arrays.toString((byte[])val);
+      } else if ((new char[0]).getClass().isAssignableFrom(klass)) {
+        return charArrayToString((char[])val);
+      } else if ((new short[0]).getClass().isAssignableFrom(klass)) {
+        return Arrays.toString((short[])val);
+      } else if ((new int[0]).getClass().isAssignableFrom(klass)) {
+        return Arrays.toString((int[])val);
+      } else if ((new long[0]).getClass().isAssignableFrom(klass)) {
+        return Arrays.toString((long[])val);
+      } else if ((new float[0]).getClass().isAssignableFrom(klass)) {
+        return Arrays.toString((float[])val);
+      } else if ((new double[0]).getClass().isAssignableFrom(klass)) {
+        return Arrays.toString((double[])val);
+      } else {
+        throw new Error("Unknown type " + klass);
+      }
+    }
+
+    static final class MethodReturn implements Printable {
+        private Object m;
+        private Object val;
+        private int cnt;
+        public MethodReturn(Object m, Object val, int cnt) {
+            this.m = m;
+            this.val = val;
+            this.cnt = cnt;
+        }
+        @Override
+        public void Print() {
+            String print;
+            if (NON_DETERMINISTIC_OUTPUT_METHODS.contains(m)) {
+                print = "<non-deterministic>";
+            } else {
+                print = genericToString(val);
+            }
+            Class<?> klass = null;
+            if (val != null) {
+              klass = val.getClass();
+            }
+            System.out.println(
+                whitespace(cnt) + "<= " + m + " -> <" + klass + ": " + print + ">");
+        }
+    }
+
+    static final class MethodThrownThrough implements Printable {
+        private Object m;
+        private int cnt;
+        public MethodThrownThrough(Object m, int cnt) {
+            this.m = m;
+            this.cnt = cnt;
+        }
+        @Override
+        public void Print() {
+            System.out.println(whitespace(cnt) + "<= " + m + " EXCEPTION");
+        }
+    }
+
+    private static String whitespace(int n) {
+      String out = "";
+      while (n > 0) {
+        n--;
+        out += ".";
+      }
+      return out;
+    }
+
+    static final class FibThrow implements Printable {
+        private String format;
+        private int arg;
+        private Throwable res;
+        public FibThrow(String format, int arg, Throwable res) {
+            this.format = format;
+            this.arg = arg;
+            this.res = res;
+        }
+
+        @Override
+        public void Print() {
+            System.out.printf(format, arg, genericToString(res));
+        }
+    }
+
+    static final class FibResult implements Printable {
+        private String format;
+        private int arg;
+        private int res;
+        public FibResult(String format, int arg, int res) {
+            this.format = format;
+            this.arg = arg;
+            this.res = res;
+        }
+
+        @Override
+        public void Print() {
+            System.out.printf(format, arg, res);
+        }
+    }
+
+    private static List<Printable> results = new ArrayList<>();
+    private static int cnt = 1;
+
+    // Iterative version
+    static final class IterOp implements IntUnaryOperator {
+      public int applyAsInt(int x) {
+        return iter_fibonacci(x);
+      }
+    }
+    static int iter_fibonacci(int n) {
+        if (n < 0) {
+            throw new Error("Bad argument: " + n + " < 0");
+        } else if (n == 0) {
+            return 0;
+        }
+        int x = 1;
+        int y = 1;
+        for (int i = 3; i <= n; i++) {
+            int z = x + y;
+            x = y;
+            y = z;
+        }
+        return y;
+    }
+
+    // Recursive version
+    static final class RecurOp implements IntUnaryOperator {
+      public int applyAsInt(int x) {
+        return fibonacci(x);
+      }
+    }
+    static int fibonacci(int n) {
+        if (n < 0) {
+            throw new Error("Bad argument: " + n + " < 0");
+        } else if ((n == 0) || (n == 1)) {
+            return n;
+        } else {
+            return fibonacci(n - 1) + (fibonacci(n - 2));
+        }
+    }
+
+    public static void notifyMethodEntry(Object m) {
+        // Called by native code when a method is entered. This method is ignored by the native
+        // entry and exit hooks.
+        results.add(new MethodEntry(m, cnt));
+        cnt++;
+    }
+
+    public static void notifyMethodExit(Object m, boolean exception, Object result) {
+        cnt--;
+        if (exception) {
+            results.add(new MethodThrownThrough(m, cnt));
+        } else {
+            results.add(new MethodReturn(m, result, cnt));
+        }
+    }
+
+    public static void run() throws Exception {
+        // call this here so it is linked. It doesn't actually do anything here.
+        loadAllClasses();
+        Trace.disableMethodTracing(Thread.currentThread());
+        Trace.enableMethodTracing(
+            Test988.class,
+            Test988.class.getDeclaredMethod("notifyMethodEntry", Object.class),
+            Test988.class.getDeclaredMethod(
+                "notifyMethodExit", Object.class, Boolean.TYPE, Object.class),
+            Thread.currentThread());
+        doFibTest(30, new IterOp());
+        doFibTest(5, new RecurOp());
+        doFibTest(-19, new IterOp());
+        doFibTest(-19, new RecurOp());
+        // Turn off method tracing so we don't have to deal with print internals.
+        Trace.disableMethodTracing(Thread.currentThread());
+        printResults();
+    }
+
+    // This ensures that all classes we touch are loaded before we start recording traces. This
+    // eliminates a major source of divergence between the RI and ART.
+    public static void loadAllClasses() {
+      MethodThrownThrough.class.toString();
+      MethodEntry.class.toString();
+      MethodReturn.class.toString();
+      FibResult.class.toString();
+      FibThrow.class.toString();
+      Printable.class.toString();
+      ArrayList.class.toString();
+      RecurOp.class.toString();
+      IterOp.class.toString();
+      StringBuilder.class.toString();
+    }
+
+    public static void printResults() {
+        for (Printable p : results) {
+            p.Print();
+        }
+    }
+
+    public static void doFibTest(int x, IntUnaryOperator op) {
+      try {
+        int y = op.applyAsInt(x);
+        results.add(new FibResult("fibonacci(%d)=%d\n", x, y));
+      } catch (Throwable t) {
+        results.add(new FibThrow("fibonacci(%d) -> %s\n", x, t));
+      }
+    }
+}
diff --git a/test/988-method-trace/src/art/Trace.java b/test/988-method-trace/src/art/Trace.java
new file mode 100644
index 0000000..3370996
--- /dev/null
+++ b/test/988-method-trace/src/art/Trace.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+
+public class Trace {
+  public static native void enableMethodTracing(
+      Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr);
+  public static native void disableMethodTracing(Thread thr);
+}
diff --git a/test/989-method-trace-throw/expected.txt b/test/989-method-trace-throw/expected.txt
new file mode 100644
index 0000000..0911bc3
--- /dev/null
+++ b/test/989-method-trace-throw/expected.txt
@@ -0,0 +1,188 @@
+Normal: Entering public static void art.Test989.doNothing()
+Normal: Leaving public static void art.Test989.doNothing() returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$doNothingClass].
+Normal: Entering public static native void art.Test989.doNothingNative()
+Normal: Leaving public static native void art.Test989.doNothingNative() returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$doNothingNativeClass].
+Normal: Entering public static void art.Test989.throwA()
+Normal: Leaving public static void art.Test989.throwA() returned <exception>
+Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwAClass] - art.Test989$ErrorA: Throwing Error A
+Normal: Entering public static native void art.Test989.throwANative()
+Normal: Leaving public static native void art.Test989.throwANative() returned <exception>
+Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorA: Throwing Error A
+Normal: Entering public static java.lang.Object art.Test989.returnValue()
+Normal: Leaving public static java.lang.Object art.Test989.returnValue() returned TestObject(0)
+returnValue returned: TestObject(0)
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnValueClass].
+Normal: Entering public static native java.lang.Object art.Test989.returnValueNative()
+Normal: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(1)
+returnValueNative returned: TestObject(1)
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnValueNativeClass].
+Normal: Entering public static void art.Test989.acceptValue(java.lang.Object)
+Recieved TestObject(2)
+Normal: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$acceptValueClass].
+Normal: Entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+Recieved TestObject(3)
+Normal: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$acceptValueNativeClass].
+Normal: Entering public static void art.Test989.tryCatchExit()
+Normal: Leaving public static void art.Test989.tryCatchExit() returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$tryCatchExitClass].
+Normal: Entering public static float art.Test989.returnFloat()
+Normal: Leaving public static float art.Test989.returnFloat() returned 1.618
+returnFloat returned: 1.618
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnFloatClass].
+Normal: Entering public static native float art.Test989.returnFloatNative()
+Normal: Leaving public static native float art.Test989.returnFloatNative() returned 1.618
+returnFloatNative returned: 1.618
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnFloatNativeClass].
+Normal: Entering public static double art.Test989.returnDouble()
+Normal: Leaving public static double art.Test989.returnDouble() returned 3.14159628
+returnDouble returned: 3.14159628
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnDoubleClass].
+Normal: Entering public static native double art.Test989.returnDoubleNative()
+Normal: Leaving public static native double art.Test989.returnDoubleNative() returned 3.14159628
+returnDoubleNative returned: 3.14159628
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnDoubleNativeClass].
+ThrowEnter: Entering public static void art.Test989.doNothing()
+ThrowEnter: Leaving public static void art.Test989.doNothing() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$doNothingClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.doNothing()
+ThrowEnter: Entering public static native void art.Test989.doNothingNative()
+ThrowEnter: Leaving public static native void art.Test989.doNothingNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.doNothingNative()
+ThrowEnter: Entering public static void art.Test989.throwA()
+ThrowEnter: Leaving public static void art.Test989.throwA() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwAClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.throwA()
+ThrowEnter: Entering public static native void art.Test989.throwANative()
+ThrowEnter: Leaving public static native void art.Test989.throwANative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.throwANative()
+ThrowEnter: Entering public static java.lang.Object art.Test989.returnValue()
+ThrowEnter: Leaving public static java.lang.Object art.Test989.returnValue() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnValueClass] - art.Test989$ErrorB: Throwing error while entering public static java.lang.Object art.Test989.returnValue()
+ThrowEnter: Entering public static native java.lang.Object art.Test989.returnValueNative()
+ThrowEnter: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native java.lang.Object art.Test989.returnValueNative()
+ThrowEnter: Entering public static void art.Test989.acceptValue(java.lang.Object)
+ThrowEnter: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.acceptValue(java.lang.Object)
+ThrowEnter: Entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+ThrowEnter: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+ThrowEnter: Entering public static void art.Test989.tryCatchExit()
+ThrowEnter: Leaving public static void art.Test989.tryCatchExit() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.tryCatchExit()
+ThrowEnter: Entering public static float art.Test989.returnFloat()
+ThrowEnter: Leaving public static float art.Test989.returnFloat() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorB: Throwing error while entering public static float art.Test989.returnFloat()
+ThrowEnter: Entering public static native float art.Test989.returnFloatNative()
+ThrowEnter: Leaving public static native float art.Test989.returnFloatNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native float art.Test989.returnFloatNative()
+ThrowEnter: Entering public static double art.Test989.returnDouble()
+ThrowEnter: Leaving public static double art.Test989.returnDouble() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorB: Throwing error while entering public static double art.Test989.returnDouble()
+ThrowEnter: Entering public static native double art.Test989.returnDoubleNative()
+ThrowEnter: Leaving public static native double art.Test989.returnDoubleNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native double art.Test989.returnDoubleNative()
+ThrowExit: Entering public static void art.Test989.doNothing()
+ThrowExit: Leaving public static void art.Test989.doNothing() returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$doNothingClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.doNothing() returned null
+ThrowExit: Entering public static native void art.Test989.doNothingNative()
+ThrowExit: Leaving public static native void art.Test989.doNothingNative() returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.doNothingNative() returned null
+ThrowExit: Entering public static void art.Test989.throwA()
+ThrowExit: Leaving public static void art.Test989.throwA() returned <exception>
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwAClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.throwA() returned <exception>
+ThrowExit: Entering public static native void art.Test989.throwANative()
+ThrowExit: Leaving public static native void art.Test989.throwANative() returned <exception>
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.throwANative() returned <exception>
+ThrowExit: Entering public static java.lang.Object art.Test989.returnValue()
+ThrowExit: Leaving public static java.lang.Object art.Test989.returnValue() returned TestObject(7)
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnValueClass] - art.Test989$ErrorB: Throwing error while exit public static java.lang.Object art.Test989.returnValue() returned TestObject(7)
+ThrowExit: Entering public static native java.lang.Object art.Test989.returnValueNative()
+ThrowExit: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(8)
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(8)
+ThrowExit: Entering public static void art.Test989.acceptValue(java.lang.Object)
+Recieved TestObject(9)
+ThrowExit: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.acceptValue(java.lang.Object) returned null
+ThrowExit: Entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+Recieved TestObject(10)
+ThrowExit: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.acceptValueNative(java.lang.Object) returned null
+ThrowExit: Entering public static void art.Test989.tryCatchExit()
+ThrowExit: Leaving public static void art.Test989.tryCatchExit() returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.tryCatchExit() returned null
+ThrowExit: Entering public static float art.Test989.returnFloat()
+ThrowExit: Leaving public static float art.Test989.returnFloat() returned 1.618
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorB: Throwing error while exit public static float art.Test989.returnFloat() returned 1.618
+ThrowExit: Entering public static native float art.Test989.returnFloatNative()
+ThrowExit: Leaving public static native float art.Test989.returnFloatNative() returned 1.618
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native float art.Test989.returnFloatNative() returned 1.618
+ThrowExit: Entering public static double art.Test989.returnDouble()
+ThrowExit: Leaving public static double art.Test989.returnDouble() returned 3.14159628
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorB: Throwing error while exit public static double art.Test989.returnDouble() returned 3.14159628
+ThrowExit: Entering public static native double art.Test989.returnDoubleNative()
+ThrowExit: Leaving public static native double art.Test989.returnDoubleNative() returned 3.14159628
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native double art.Test989.returnDoubleNative() returned 3.14159628
+ThrowBoth: Entering public static void art.Test989.doNothing()
+ThrowBoth: Leaving public static void art.Test989.doNothing() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$doNothingClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.doNothing() returned <exception>
+ThrowBoth: Entering public static native void art.Test989.doNothingNative()
+ThrowBoth: Leaving public static native void art.Test989.doNothingNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.doNothingNative() returned <exception>
+ThrowBoth: Entering public static void art.Test989.throwA()
+ThrowBoth: Leaving public static void art.Test989.throwA() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwAClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.throwA() returned <exception>
+ThrowBoth: Entering public static native void art.Test989.throwANative()
+ThrowBoth: Leaving public static native void art.Test989.throwANative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.throwANative() returned <exception>
+ThrowBoth: Entering public static java.lang.Object art.Test989.returnValue()
+ThrowBoth: Leaving public static java.lang.Object art.Test989.returnValue() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnValueClass] - art.Test989$ErrorC: Throwing error while exit public static java.lang.Object art.Test989.returnValue() returned <exception>
+ThrowBoth: Entering public static native java.lang.Object art.Test989.returnValueNative()
+ThrowBoth: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native java.lang.Object art.Test989.returnValueNative() returned <exception>
+ThrowBoth: Entering public static void art.Test989.acceptValue(java.lang.Object)
+ThrowBoth: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.acceptValue(java.lang.Object) returned <exception>
+ThrowBoth: Entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+ThrowBoth: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception>
+ThrowBoth: Entering public static void art.Test989.tryCatchExit()
+ThrowBoth: Leaving public static void art.Test989.tryCatchExit() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.tryCatchExit() returned <exception>
+ThrowBoth: Entering public static float art.Test989.returnFloat()
+ThrowBoth: Leaving public static float art.Test989.returnFloat() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorC: Throwing error while exit public static float art.Test989.returnFloat() returned <exception>
+ThrowBoth: Entering public static native float art.Test989.returnFloatNative()
+ThrowBoth: Leaving public static native float art.Test989.returnFloatNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native float art.Test989.returnFloatNative() returned <exception>
+ThrowBoth: Entering public static double art.Test989.returnDouble()
+ThrowBoth: Leaving public static double art.Test989.returnDouble() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorC: Throwing error while exit public static double art.Test989.returnDouble() returned <exception>
+ThrowBoth: Entering public static native double art.Test989.returnDoubleNative()
+ThrowBoth: Leaving public static native double art.Test989.returnDoubleNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native double art.Test989.returnDoubleNative() returned <exception>
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$doNothingClass].
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$doNothingNativeClass].
+Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwAClass] - art.Test989$ErrorA: Throwing Error A
+Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorA: Throwing Error A
+returnValue returned: TestObject(14)
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnValueClass].
+returnValueNative returned: TestObject(15)
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnValueNativeClass].
+Recieved TestObject(16)
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$acceptValueClass].
+Recieved TestObject(17)
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$acceptValueNativeClass].
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$tryCatchExitClass].
+returnFloat returned: 1.618
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnFloatClass].
+returnFloatNative returned: 1.618
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnFloatNativeClass].
+returnDouble returned: 3.14159628
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnDoubleClass].
+returnDoubleNative returned: 3.14159628
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnDoubleNativeClass].
+Finished!
diff --git a/test/989-method-trace-throw/info.txt b/test/989-method-trace-throw/info.txt
new file mode 100644
index 0000000..f0a200d
--- /dev/null
+++ b/test/989-method-trace-throw/info.txt
@@ -0,0 +1,15 @@
+Tests method tracing in JVMTI
+
+This test is sensitive to the internal implementations of:
+ * java.lang.Error
+ * java.lang.Integer
+ * java.lang.Math
+ * java.lang.String
+ * java.lang.System
+ * java.util.ArrayList
+ * java.util.Arrays
+ * java.util.StringBuilder
+ * all super-classes and super-interfaces of the above types.
+
+Changes to the internal implementation of these classes might (or might not)
+change the output of this test.
diff --git a/test/989-method-trace-throw/method_trace.cc b/test/989-method-trace-throw/method_trace.cc
new file mode 100644
index 0000000..554784e
--- /dev/null
+++ b/test/989-method-trace-throw/method_trace.cc
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <memory>
+#include <stdio.h>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+
+// Test infrastructure
+#include "jni_binder.h"
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test989StackTraceThrow {
+
+extern "C" JNIEXPORT
+jfloat JNICALL Java_art_Test989_returnFloatNative(JNIEnv* env, jclass klass) {
+  jmethodID targetMethod = env->GetStaticMethodID(klass, "doGetFloat", "()F");
+  return env->CallStaticFloatMethod(klass, targetMethod);
+}
+extern "C" JNIEXPORT
+jdouble JNICALL Java_art_Test989_returnDoubleNative(JNIEnv* env, jclass klass) {
+  jmethodID targetMethod = env->GetStaticMethodID(klass, "doGetDouble", "()D");
+  return env->CallStaticDoubleMethod(klass, targetMethod);
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_art_Test989_returnValueNative(JNIEnv* env, jclass klass) {
+  jmethodID targetMethod = env->GetStaticMethodID(klass, "mkTestObject", "()Ljava/lang/Object;");
+  return env->CallStaticObjectMethod(klass, targetMethod);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test989_doNothingNative(JNIEnv* env ATTRIBUTE_UNUSED,
+                                                                   jclass klass ATTRIBUTE_UNUSED) {
+  return;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test989_throwANative(JNIEnv* env,
+                                                                jclass klass) {
+  jmethodID targetMethod = env->GetStaticMethodID(klass, "doThrowA", "()V");
+  env->CallStaticVoidMethod(klass, targetMethod);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test989_acceptValueNative(JNIEnv* env,
+                                                                     jclass klass,
+                                                                     jobject arg) {
+  jmethodID targetMethod = env->GetStaticMethodID(klass, "printObject", "(Ljava/lang/Object;)V");
+  env->CallStaticVoidMethod(klass, targetMethod, arg);
+}
+
+}  // namespace Test989StackTraceThrow
+}  // namespace art
+
diff --git a/test/989-method-trace-throw/run b/test/989-method-trace-throw/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/989-method-trace-throw/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/989-method-trace-throw/src/Main.java b/test/989-method-trace-throw/src/Main.java
new file mode 100644
index 0000000..29b9de1
--- /dev/null
+++ b/test/989-method-trace-throw/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test989.run();
+  }
+}
diff --git a/test/989-method-trace-throw/src/art/Test989.java b/test/989-method-trace-throw/src/art/Test989.java
new file mode 100644
index 0000000..18421bd
--- /dev/null
+++ b/test/989-method-trace-throw/src/art/Test989.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+import java.util.Set;
+import java.util.HashSet;
+
+public class Test989 {
+  static boolean PRINT_STACK_TRACE = false;
+  static Set<Method> testMethods = new HashSet<>();
+
+  static MethodTracer currentTracer = new MethodTracer() {
+    public void methodEntry(Object o) { return; }
+    public void methodExited(Object o, boolean e, Object r) { return; }
+  };
+
+  private static boolean DISABLE_TRACING = false;
+
+  static {
+    try {
+      testMethods.add(Test989.class.getDeclaredMethod("doNothing"));
+      testMethods.add(Test989.class.getDeclaredMethod("doNothingNative"));
+      testMethods.add(Test989.class.getDeclaredMethod("throwA"));
+      testMethods.add(Test989.class.getDeclaredMethod("throwANative"));
+      testMethods.add(Test989.class.getDeclaredMethod("returnFloat"));
+      testMethods.add(Test989.class.getDeclaredMethod("returnFloatNative"));
+      testMethods.add(Test989.class.getDeclaredMethod("returnDouble"));
+      testMethods.add(Test989.class.getDeclaredMethod("returnDoubleNative"));
+      testMethods.add(Test989.class.getDeclaredMethod("returnValue"));
+      testMethods.add(Test989.class.getDeclaredMethod("returnValueNative"));
+      testMethods.add(Test989.class.getDeclaredMethod("acceptValue", Object.class));
+      testMethods.add(Test989.class.getDeclaredMethod("acceptValueNative", Object.class));
+      testMethods.add(Test989.class.getDeclaredMethod("tryCatchExit"));
+    } catch (Exception e) {
+      throw new Error("Bad static!", e);
+    }
+  }
+
+  // Disables tracing only on RI. Used to work around an annoying piece of behavior where in the
+  // RI throwing an exception in an exit hook causes the exit hook to be re-executed. This leads
+  // to an infinite loop on the RI.
+  private static void disableTraceForRI() {
+    if (!System.getProperty("java.vm.name").equals("Dalvik")) {
+      Trace.disableMethodTracing(Thread.currentThread());
+    }
+  }
+
+  private static String getInfo(Object m, boolean exception, Object result) {
+    String out = m.toString() + " returned ";
+    if (exception) {
+      out += "<exception>";
+    } else {
+      out += result;
+    }
+    return out;
+  }
+
+  public static interface MethodTracer {
+    public void methodEntry(Object m);
+    public void methodExited(Object m, boolean exception, Object result);
+    public default Class<?> entryException() { return null; }
+    public default Class<?> exitException() { return null; }
+  }
+
+  public static class NormalTracer implements MethodTracer {
+    public void methodEntry(Object m) {
+      if (testMethods.contains(m)) {
+        System.out.println("Normal: Entering " + m);
+      }
+    }
+    public void methodExited(Object m, boolean exception, Object result) {
+      if (testMethods.contains(m)) {
+        System.out.println("Normal: Leaving " + getInfo(m, exception, result));
+      }
+    }
+  }
+
+  public static class ThrowEnterTracer implements MethodTracer {
+    public void methodEntry(Object m) {
+      if (testMethods.contains(m)) {
+        System.out.println("ThrowEnter: Entering " + m);
+        throw new ErrorB("Throwing error while entering " + m);
+      }
+    }
+    public void methodExited(Object m, boolean exception, Object result) {
+      if (testMethods.contains(m)) {
+        System.out.println("ThrowEnter: Leaving " + getInfo(m, exception, result));
+      }
+    }
+    public Class<?> entryException() { return ErrorB.class; }
+  }
+
+  public static class ThrowExitTracer implements MethodTracer {
+    public void methodEntry(Object m) {
+      if (testMethods.contains(m)) {
+        System.out.println("ThrowExit: Entering " + m);
+      }
+    }
+    public void methodExited(Object m, boolean exception, Object result) {
+      if (testMethods.contains(m)) {
+        // The RI goes into an infinite loop if we throw exceptions in an ExitHook. See
+        // disableTraceForRI for explanation.
+        disableTraceForRI();
+        System.out.println("ThrowExit: Leaving " + getInfo(m, exception, result));
+        throw new ErrorB("Throwing error while exit " + getInfo(m, exception, result));
+      }
+    }
+    public Class<?> exitException() { return ErrorB.class; }
+  }
+
+  public static class ThrowBothTracer implements MethodTracer {
+    public void methodEntry(Object m) {
+      if (testMethods.contains(m)) {
+        System.out.println("ThrowBoth: Entering " + m);
+        throw new ErrorB("Throwing error while entering " + m);
+      }
+    }
+    public void methodExited(Object m, boolean exception, Object result) {
+      if (testMethods.contains(m)) {
+        // The RI goes into an infinite loop if we throw exceptions in an ExitHook. See
+        // disableTraceForRI for explanation.
+        disableTraceForRI();
+        System.out.println("ThrowBoth: Leaving " + getInfo(m, exception, result));
+        throw new ErrorC("Throwing error while exit " + getInfo(m, exception, result));
+      }
+    }
+    public Class<?> entryException() { return ErrorB.class; }
+    public Class<?> exitException() { return ErrorC.class; }
+  }
+
+  public static class ForceGCTracer implements MethodTracer {
+    public void methodEntry(Object m) {
+      if (System.getProperty("java.vm.name").equals("Dalvik")) {
+        System.gc();
+      }
+    }
+    public void methodExited(Object m, boolean exception, Object result) {
+      if (System.getProperty("java.vm.name").equals("Dalvik")) {
+        System.gc();
+      }
+    }
+  }
+
+  private static void maybeDisableTracing() throws Exception {
+    if (DISABLE_TRACING) {
+      Trace.disableMethodTracing(Thread.currentThread());
+    }
+  }
+
+  public static void baseNotifyMethodEntry(Object o) {
+    currentTracer.methodEntry(o);
+  }
+  public static void baseNotifyMethodExit(Object o, boolean exception, Object res) {
+    currentTracer.methodExited(o, exception, res);
+  }
+
+  private static void setupTracing() throws Exception {
+    Trace.enableMethodTracing(
+        Test989.class,
+        Test989.class.getDeclaredMethod("baseNotifyMethodEntry", Object.class),
+        Test989.class.getDeclaredMethod(
+            "baseNotifyMethodExit", Object.class, Boolean.TYPE, Object.class),
+        Thread.currentThread());
+  }
+  private static void setEntry(MethodTracer type) throws Exception {
+    if (DISABLE_TRACING || !System.getProperty("java.vm.name").equals("Dalvik")) {
+      Trace.disableMethodTracing(Thread.currentThread());
+      setupTracing();
+    }
+    currentTracer = type;
+  }
+
+  private static String testDescription(MethodTracer type, Runnable test) {
+    return "test[" + type.getClass() + ", " + test.getClass() + "]";
+  }
+
+  private static Class<?> getExpectedError(MethodTracer t, MyRunnable r) {
+    if (t.exitException() != null) {
+      return t.exitException();
+    } else if (t.entryException() != null) {
+      return t.entryException();
+    } else {
+      return r.expectedThrow();
+    }
+  }
+
+  private static void doTest(MethodTracer type, MyRunnable test) throws Exception {
+    Class<?> expected = getExpectedError(type, test);
+
+    setEntry(type);
+    try {
+      test.run();
+      // Disabling method tracing just makes this test somewhat faster.
+      maybeDisableTracing();
+      if (expected == null) {
+        System.out.println(
+            "Received no exception as expected for " + testDescription(type, test) + ".");
+        return;
+      }
+    } catch (Error t) {
+      // Disabling method tracing just makes this test somewhat faster.
+      maybeDisableTracing();
+      if (expected == null) {
+        throw new Error("Unexpected error occured: " + t + " for " + testDescription(type, test), t);
+      } else if (!expected.isInstance(t)) {
+        throw new Error("Expected error of type " + expected + " not " + t +
+            " for " + testDescription(type, test), t);
+      } else {
+        System.out.println(
+            "Received expected error for " + testDescription(type, test) + " - " + t);
+        if (PRINT_STACK_TRACE) {
+          t.printStackTrace();
+        }
+        return;
+      }
+    }
+    System.out.println("Expected an error of type " + expected + " but got no exception for "
+        + testDescription(type, test));
+    // throw new Error("Expected an error of type " + expected + " but got no exception for "
+    //     + testDescription(type, test));
+  }
+
+  public static interface MyRunnable extends Runnable {
+    public default Class<?> expectedThrow() {
+      return null;
+    }
+  }
+
+  public static void run() throws Exception {
+    MyRunnable[] testCases = new MyRunnable[] {
+      new doNothingClass(),
+      new doNothingNativeClass(),
+      new throwAClass(),
+      new throwANativeClass(),
+      new returnValueClass(),
+      new returnValueNativeClass(),
+      new acceptValueClass(),
+      new acceptValueNativeClass(),
+      new tryCatchExitClass(),
+      new returnFloatClass(),
+      new returnFloatNativeClass(),
+      new returnDoubleClass(),
+      new returnDoubleNativeClass(),
+    };
+    MethodTracer[] tracers = new MethodTracer[] {
+      new NormalTracer(),
+      new ThrowEnterTracer(),
+      new ThrowExitTracer(),
+      new ThrowBothTracer(),
+      new ForceGCTracer(),
+    };
+
+    setupTracing();
+    for (MethodTracer t : tracers) {
+      for (MyRunnable r : testCases) {
+        doTest(t, r);
+      }
+    }
+
+    maybeDisableTracing();
+    System.out.println("Finished!");
+    Trace.disableMethodTracing(Thread.currentThread());
+  }
+
+  private static final class throwAClass implements MyRunnable {
+    public void run() {
+      throwA();
+    }
+    @Override
+    public Class<?> expectedThrow() {
+      return ErrorA.class;
+    }
+  }
+
+  private static final class throwANativeClass implements MyRunnable {
+    public void run() {
+      throwANative();
+    }
+    @Override
+    public Class<?> expectedThrow() {
+      return ErrorA.class;
+    }
+  }
+
+  private static final class tryCatchExitClass implements MyRunnable {
+    public void run() {
+      tryCatchExit();
+    }
+  }
+
+  private static final class doNothingClass implements MyRunnable {
+    public void run() {
+      doNothing();
+    }
+  }
+
+  private static final class doNothingNativeClass implements MyRunnable {
+    public void run() {
+      doNothingNative();
+    }
+  }
+
+  private static final class acceptValueClass implements MyRunnable {
+    public void run() {
+      acceptValue(mkTestObject());
+    }
+  }
+
+  private static final class acceptValueNativeClass implements MyRunnable {
+    public void run() {
+      acceptValueNative(mkTestObject());
+    }
+  }
+
+  private static final class returnValueClass implements MyRunnable {
+    public void run() {
+      Object o = returnValue();
+      System.out.println("returnValue returned: " + o);
+    }
+  }
+
+  private static final class returnValueNativeClass implements MyRunnable {
+    public void run() {
+      Object o = returnValueNative();
+      System.out.println("returnValueNative returned: " + o);
+    }
+  }
+
+  private static final class returnFloatClass implements MyRunnable {
+    public void run() {
+      float d = returnFloat();
+      System.out.println("returnFloat returned: " + d);
+    }
+  }
+
+  private static final class returnFloatNativeClass implements MyRunnable {
+    public void run() {
+      float d = returnFloatNative();
+      System.out.println("returnFloatNative returned: " + d);
+    }
+  }
+
+  private static final class returnDoubleClass implements MyRunnable {
+    public void run() {
+      double d = returnDouble();
+      System.out.println("returnDouble returned: " + d);
+    }
+  }
+
+  private static final class returnDoubleNativeClass implements MyRunnable {
+    public void run() {
+      double d = returnDoubleNative();
+      System.out.println("returnDoubleNative returned: " + d);
+    }
+  }
+
+  private static class ErrorA extends Error {
+    private static final long serialVersionUID = 0;
+    public ErrorA(String s) { super(s); }
+  }
+
+  private static class ErrorB extends Error {
+    private static final long serialVersionUID = 1;
+    public ErrorB(String s) { super(s); }
+  }
+
+  private static class ErrorC extends Error {
+    private static final long serialVersionUID = 2;
+    public ErrorC(String s) { super(s); }
+  }
+
+  // Does nothing.
+  public static void doNothing() { }
+
+  public static void tryCatchExit() {
+    try {
+      Object o = mkTestObject();
+      return;
+    } catch (ErrorB b) {
+      System.out.println("ERROR: Caught " + b);
+      b.printStackTrace();
+    } catch (ErrorC c) {
+      System.out.println("ERROR: Caught " + c);
+      c.printStackTrace();
+    }
+  }
+
+  public static float returnFloat() {
+    return doGetFloat();
+  }
+
+  public static double returnDouble() {
+    return doGetDouble();
+  }
+
+  // Throws an ErrorA.
+  public static void throwA() {
+    doThrowA();
+  }
+
+  public static void doThrowA() {
+    throw new ErrorA("Throwing Error A");
+  }
+
+  static final class TestObject {
+    private int idx;
+    public TestObject(int v) {
+      this.idx = v;
+    }
+    @Override
+    public String toString() {
+      return "TestObject(" + idx + ")";
+    }
+  }
+
+  static int counter = 0;
+  public static Object mkTestObject() {
+    return new TestObject(counter++);
+  }
+
+  public static void printObject(Object o) {
+    System.out.println("Recieved " + o);
+  }
+
+  // Returns a newly allocated value.
+  public static Object returnValue() {
+    return mkTestObject();
+  }
+
+  public static void acceptValue(Object o) {
+    printObject(o);
+  }
+
+  public static float doGetFloat() {
+    return 1.618f;
+  }
+
+  public static double doGetDouble() {
+    return 3.14159628;
+  }
+
+  // Calls mkTestObject from native code and returns it.
+  public static native Object returnValueNative();
+  // Calls printObject from native code.
+  public static native void acceptValueNative(Object t);
+  public static native void doNothingNative();
+  public static native void throwANative();
+  public static native float returnFloatNative();
+  public static native double returnDoubleNative();
+}
diff --git a/test/989-method-trace-throw/src/art/Trace.java b/test/989-method-trace-throw/src/art/Trace.java
new file mode 100644
index 0000000..3370996
--- /dev/null
+++ b/test/989-method-trace-throw/src/art/Trace.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+
+public class Trace {
+  public static native void enableMethodTracing(
+      Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr);
+  public static native void disableMethodTracing(Thread thr);
+}
diff --git a/test/Android.bp b/test/Android.bp
index 599b011..35c3d9c 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -278,6 +278,7 @@
         "984-obsolete-invoke/obsolete_invoke.cc",
         "986-native-method-bind/native_bind.cc",
         "987-agent-bind/agent_bind.cc",
+        "989-method-trace-throw/method_trace.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 7677025..8aacc8c 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -63,6 +63,8 @@
 TEST_IS_NDEBUG="n"
 APP_IMAGE="y"
 JVMTI_STRESS="n"
+JVMTI_TRACE_STRESS="n"
+JVMTI_REDEFINE_STRESS="n"
 VDEX_FILTER=""
 PROFILE="n"
 RANDOM_PROFILE="n"
@@ -151,10 +153,15 @@
     elif [ "x$1" = "x--prebuild" ]; then
         PREBUILD="y"
         shift
-    elif [ "x$1" = "x--jvmti-stress" ]; then
-        # APP_IMAGE doesn't really work with jvmti-torture
+    elif [ "x$1" = "x--jvmti-redefine-stress" ]; then
+        # APP_IMAGE doesn't really work with jvmti redefine stress
         APP_IMAGE="n"
         JVMTI_STRESS="y"
+        JVMTI_REDEFINE_STRESS="y"
+        shift
+    elif [ "x$1" = "x--jvmti-trace-stress" ]; then
+        JVMTI_STRESS="y"
+        JVMTI_TRACE_STRESS="y"
         shift
     elif [ "x$1" = "x--no-app-image" ]; then
         APP_IMAGE="n"
@@ -397,13 +404,25 @@
     plugin=libopenjdkjvmti.so
   fi
 
-  file_1=$(mktemp --tmpdir=${DEX_LOCATION})
-  file_2=$(mktemp --tmpdir=${DEX_LOCATION})
+  # Just give it a default start so we can always add ',' to it.
+  agent_args="jvmti-stress"
+  if [[ "$JVMTI_REDEFINE_STRESS" = "y" ]]; then
+    # We really cannot do this on RI so don't both passing it in that case.
+    if [[ "$USE_JVM" = "n" ]]; then
+      file_1=$(mktemp --tmpdir=${DEX_LOCATION})
+      file_2=$(mktemp --tmpdir=${DEX_LOCATION})
+      # TODO Remove need for DEXTER_BINARY!
+      agent_args="${agent_args},redefine,${DEXTER_BINARY},${file_1},${file_2}"
+    fi
+  fi
+  if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then
+    agent_args="${agent_args},trace"
+  fi
+  # In the future add onto this;
   if [[ "$USE_JVM" = "y" ]]; then
-    FLAGS="${FLAGS} -agentpath:${ANDROID_HOST_OUT}/nativetest64/${agent}=/bin/false,${file_1},${file_2}"
+    FLAGS="${FLAGS} -agentpath:${ANDROID_HOST_OUT}/nativetest64/${agent}=${agent_args}"
   else
-    # TODO Remove need for DEXTER_BINARY!
-    FLAGS="${FLAGS} -agentpath:${agent}=${DEXTER_BINARY},${file_1},${file_2}"
+    FLAGS="${FLAGS} -agentpath:${agent}=${agent_args}"
     if [ "$IS_JVMTI_TEST" = "n" ]; then
       FLAGS="${FLAGS} -Xplugin:${plugin}"
       FLAGS="${FLAGS} -Xcompiler-option --debuggable"
diff --git a/test/knownfailures.json b/test/knownfailures.json
index dee3b8d..b1b767c 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -511,7 +511,7 @@
             "645-checker-abs-simd",
             "706-checker-scheduler"],
         "description": ["Checker tests are not compatible with jvmti."],
-        "variant": "jvmti-stress"
+        "variant": "jvmti-stress | redefine-stress | trace-stress"
     },
     {
         "tests": [
@@ -519,7 +519,7 @@
             "964-default-iface-init-gen"
         ],
         "description": ["Tests that just take too long with jvmti-stress"],
-        "variant": "jvmti-stress"
+        "variant": "jvmti-stress | redefine-stress | trace-stress"
     },
     {
         "tests": [
@@ -539,7 +539,7 @@
             "dexter/slicer."
         ],
         "bug": "b/37272822",
-        "variant": "jvmti-stress"
+        "variant": "jvmti-stress | redefine-stress"
     },
     {
         "tests": [
@@ -550,7 +550,7 @@
             "981-dedup-original-dex"
         ],
         "description": ["Tests that require exact knowledge of the number of plugins and agents."],
-        "variant": "jvmti-stress"
+        "variant": "jvmti-stress | redefine-stress | trace-stress"
     },
     {
         "tests": [
@@ -564,7 +564,7 @@
         "description": [
             "Tests that use illegal dex files or otherwise break dexter assumptions"
         ],
-        "variant": "jvmti-stress"
+        "variant": "jvmti-stress | redefine-stress"
     },
     {
         "tests": [
@@ -581,7 +581,7 @@
             "Tests that use custom class loaders or other features not supported ",
             "by our JVMTI implementation"
         ],
-        "variant": "jvmti-stress"
+        "variant": "jvmti-stress | redefine-stress"
     },
     {
         "tests": [
@@ -592,7 +592,7 @@
             "Tests that use annotations and debug data that is not kept around by dexter."
         ],
         "bug": "b/37239009",
-        "variant": "jvmti-stress"
+        "variant": "jvmti-stress | redefine-stress"
     },
     {
         "tests": [
@@ -701,6 +701,11 @@
         "env_vars": {"SANITIZE_HOST": "address"}
     },
     {
+        "tests": ["988-method-trace"],
+        "variant": "redefine-stress | jvmti-stress",
+        "description": "Test disabled due to redefine-stress disabling intrinsics which changes the trace output slightly."
+    },
+    {
         "tests": "137-cfi",
         "description": [ "ASan is reporting out-of-bounds reads in libunwind."],
         "variant": "host",
diff --git a/test/run-test b/test/run-test
index 08be213..41a0dc2 100755
--- a/test/run-test
+++ b/test/run-test
@@ -137,7 +137,8 @@
 basic_verify="false"
 gc_verify="false"
 gc_stress="false"
-jvmti_stress="false"
+jvmti_trace_stress="false"
+jvmti_redefine_stress="false"
 strace="false"
 always_clean="no"
 never_clean="no"
@@ -234,8 +235,11 @@
         basic_verify="true"
         gc_stress="true"
         shift
-    elif [ "x$1" = "x--jvmti-stress" ]; then
-        jvmti_stress="true"
+    elif [ "x$1" = "x--jvmti-redefine-stress" ]; then
+        jvmti_redefine_stress="true"
+        shift
+    elif [ "x$1" = "x--jvmti-trace-stress" ]; then
+        jvmti_trace_stress="true"
         shift
     elif [ "x$1" = "x--suspend-timeout" ]; then
         shift
@@ -447,8 +451,11 @@
 if [ "$gc_stress" = "true" ]; then
   run_args="${run_args} --gc-stress --runtime-option -Xgc:gcstress --runtime-option -Xms2m --runtime-option -Xmx16m"
 fi
-if [ "$jvmti_stress" = "true" ]; then
-    run_args="${run_args} --no-app-image --jvmti-stress"
+if [ "$jvmti_redefine_stress" = "true" ]; then
+    run_args="${run_args} --no-app-image --jvmti-redefine-stress"
+fi
+if [ "$jvmti_trace_stress" = "true" ]; then
+    run_args="${run_args} --no-app-image --jvmti-trace-stress"
 fi
 if [ "$trace" = "true" ]; then
     run_args="${run_args} --runtime-option -Xmethod-trace --runtime-option -Xmethod-trace-file-size:2000000"
@@ -658,7 +665,9 @@
         echo "    --stream              Run method tracing in streaming mode (requires --trace)"
         echo "    --gcstress            Run with gc stress testing"
         echo "    --gcverify            Run with gc verification"
-        echo "    --jvmti-stress        Run with jvmti stress testing"
+        echo "    --jvmti-trace-stress  Run with jvmti method tracing stress testing"
+        echo "    --jvmti-redefine-stress"
+        echo "                          Run with jvmti method redefinition stress testing"
         echo "    --always-clean        Delete the test files even if the test fails."
         echo "    --never-clean         Keep the test files even if the test succeeds."
         echo "    --android-root [path] The path on target for the android root. (/system by default)."
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 77ef25a..3445071 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -147,7 +147,7 @@
   VARIANT_TYPE_DICT['relocate'] = {'relocate-npatchoat', 'relocate', 'no-relocate'}
   VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'}
   VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'}
-  VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress'}
+  VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress'}
   VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'optimizing',
                               'regalloc_gc', 'speed-profile'}
 
@@ -437,7 +437,11 @@
         options_test += ' --debuggable'
 
       if jvmti == 'jvmti-stress':
-        options_test += ' --jvmti-stress'
+        options_test += ' --jvmti-trace-stress --jvmti-redefine-stress'
+      elif jvmti == 'trace-stress':
+        options_test += ' --jvmti-trace-stress'
+      elif jvmti == 'redefine-stress':
+        options_test += ' --jvmti-redefine-stress'
 
       if address_size == '64':
         options_test += ' --64'
@@ -954,6 +958,10 @@
     IMAGE_TYPES.add('multipicimage')
   if options['jvmti_stress']:
     JVMTI_TYPES.add('jvmti-stress')
+  if options['redefine_stress']:
+    JVMTI_TYPES.add('redefine-stress')
+  if options['trace_stress']:
+    JVMTI_TYPES.add('trace-stress')
   if options['no_jvmti']:
     JVMTI_TYPES.add('no-jvmti')
   if options['verbose']:
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index bfd4d25..6eaa5c3 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -69,6 +69,214 @@
   env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str());
 }
 
+namespace common_trace {
+
+// Taken from art/runtime/modifiers.h
+static constexpr uint32_t kAccStatic =       0x0008;  // field, method, ic
+
+struct TraceData {
+  jclass test_klass;
+  jmethodID enter_method;
+  jmethodID exit_method;
+  bool in_callback;
+};
+
+static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) {
+  jint mods = 0;
+  if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodModifiers(m, &mods))) {
+    return nullptr;
+  }
+
+  bool is_static = (mods & kAccStatic) != 0;
+  jclass method_klass = nullptr;
+  if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodDeclaringClass(m, &method_klass))) {
+    return nullptr;
+  }
+  jobject res = env->ToReflectedMethod(method_klass, m, is_static);
+  env->DeleteLocalRef(method_klass);
+  return res;
+}
+
+static jobject GetJavaValue(jvmtiEnv* jvmtienv,
+                            JNIEnv* env,
+                            jmethodID m,
+                            jvalue value) {
+  char *fname, *fsig, *fgen;
+  if (JvmtiErrorToException(env, jvmtienv, jvmtienv->GetMethodName(m, &fname, &fsig, &fgen))) {
+    return nullptr;
+  }
+  std::string type(fsig);
+  type = type.substr(type.find(")") + 1);
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen));
+  std::string name;
+  switch (type[0]) {
+    case 'V':
+      return nullptr;
+    case '[':
+    case 'L':
+      return value.l;
+    case 'Z':
+      name = "java/lang/Boolean";
+      break;
+    case 'B':
+      name = "java/lang/Byte";
+      break;
+    case 'C':
+      name = "java/lang/Character";
+      break;
+    case 'S':
+      name = "java/lang/Short";
+      break;
+    case 'I':
+      name = "java/lang/Integer";
+      break;
+    case 'J':
+      name = "java/lang/Long";
+      break;
+    case 'F':
+      name = "java/lang/Float";
+      break;
+    case 'D':
+      name = "java/lang/Double";
+      break;
+    default:
+      LOG(FATAL) << "Unable to figure out type!";
+      return nullptr;
+  }
+  std::ostringstream oss;
+  oss << "(" << type[0] << ")L" << name << ";";
+  std::string args = oss.str();
+  jclass target = env->FindClass(name.c_str());
+  jmethodID valueOfMethod = env->GetStaticMethodID(target, "valueOf", args.c_str());
+
+  CHECK(valueOfMethod != nullptr) << args;
+  jobject res = env->CallStaticObjectMethodA(target, valueOfMethod, &value);
+  env->DeleteLocalRef(target);
+  return res;
+}
+
+static void methodExitCB(jvmtiEnv* jvmti,
+                         JNIEnv* jnienv,
+                         jthread thr ATTRIBUTE_UNUSED,
+                         jmethodID method,
+                         jboolean was_popped_by_exception,
+                         jvalue return_value) {
+  TraceData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  if (method == data->exit_method || method == data->enter_method || data->in_callback) {
+    // Don't do callback for either of these to prevent an infinite loop.
+    return;
+  }
+  data->in_callback = true;
+  jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
+  jobject result =
+      was_popped_by_exception ? nullptr : GetJavaValue(jvmti, jnienv, method, return_value);
+  if (jnienv->ExceptionCheck()) {
+    data->in_callback = false;
+    return;
+  }
+  jnienv->CallStaticVoidMethod(data->test_klass,
+                               data->exit_method,
+                               method_arg,
+                               was_popped_by_exception,
+                               result);
+  jnienv->DeleteLocalRef(method_arg);
+  data->in_callback = false;
+}
+
+static void methodEntryCB(jvmtiEnv* jvmti,
+                          JNIEnv* jnienv,
+                          jthread thr ATTRIBUTE_UNUSED,
+                          jmethodID method) {
+  TraceData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  if (method == data->exit_method || method == data->enter_method || data->in_callback) {
+    // Don't do callback for either of these to prevent an infinite loop.
+    return;
+  }
+  data->in_callback = true;
+  jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
+  if (jnienv->ExceptionCheck()) {
+    return;
+  }
+  jnienv->CallStaticVoidMethod(data->test_klass, data->enter_method, method_arg);
+  jnienv->DeleteLocalRef(method_arg);
+  data->in_callback = false;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableMethodTracing(
+    JNIEnv* env,
+    jclass trace ATTRIBUTE_UNUSED,
+    jclass klass,
+    jobject enter,
+    jobject exit,
+    jthread thr) {
+  TraceData* data = nullptr;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->Allocate(sizeof(TraceData),
+                                                reinterpret_cast<unsigned char**>(&data)))) {
+    return;
+  }
+  memset(data, 0, sizeof(TraceData));
+  data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass));
+  data->enter_method = env->FromReflectedMethod(enter);
+  data->exit_method = env->FromReflectedMethod(exit);
+  data->in_callback = false;
+
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
+    return;
+  }
+
+  jvmtiEventCallbacks cb;
+  memset(&cb, 0, sizeof(cb));
+  cb.MethodEntry = methodEntryCB;
+  cb.MethodExit = methodExitCB;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                JVMTI_EVENT_METHOD_ENTRY,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                JVMTI_EVENT_METHOD_EXIT,
+                                                                thr))) {
+    return;
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableMethodTracing(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_METHOD_ENTRY,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_METHOD_EXIT,
+                                                                thr))) {
+    return;
+  }
+}
+
+}  // namespace common_trace
+
 namespace common_redefine {
 
 static void throwRedefinitionError(jvmtiEnv* jvmti,
diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc
index e8e3cc7..497db1c 100644
--- a/test/ti-stress/stress.cc
+++ b/test/ti-stress/stress.cc
@@ -20,6 +20,7 @@
 #include <fstream>
 #include <stdio.h>
 #include <sstream>
+#include <strstream>
 
 #include "jvmti.h"
 #include "exec_utils.h"
@@ -35,6 +36,8 @@
   std::string out_temp_dex;
   std::string in_temp_dex;
   bool vm_class_loader_initialized;
+  bool trace_stress;
+  bool redefine_stress;
 };
 
 static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) {
@@ -95,7 +98,6 @@
   if (thread == nullptr) {
     info.name = const_cast<char*>("<NULLPTR>");
   } else if (jvmtienv->GetThreadInfo(thread, &info) != JVMTI_ERROR_NONE) {
-    LOG(WARNING) << "Unable to get thread info!";
     info.name = const_cast<char*>("<UNKNOWN THREAD>");
   }
   char *fname, *fsig, *fgen;
@@ -115,8 +117,8 @@
     env->DeleteLocalRef(klass);
     return;
   }
-  LOG(INFO) << "Loading native method \"" << cname << "->" << fname << fsig << "\". Thread is "
-            << info.name;
+  LOG(INFO) << "Loading native method \"" << cname << "->" << fname << fsig << "\". Thread is \""
+            << info.name << "\"";
   jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname));
   jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen));
   jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname));
@@ -126,6 +128,151 @@
   return;
 }
 
+static std::string GetName(jvmtiEnv* jvmtienv, JNIEnv* jnienv, jobject obj) {
+  jclass klass = jnienv->GetObjectClass(obj);
+  char *cname, *cgen;
+  if (jvmtienv->GetClassSignature(klass, &cname, &cgen) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Unable to get class name!";
+    jnienv->DeleteLocalRef(klass);
+    return "<UNKNOWN>";
+  }
+  std::string name(cname);
+  if (name == "Ljava/lang/String;") {
+    jstring str = reinterpret_cast<jstring>(obj);
+    const char* val = jnienv->GetStringUTFChars(str, nullptr);
+    if (val == nullptr) {
+      name += " (unable to get value)";
+    } else {
+      std::ostringstream oss;
+      oss << name << " (value: \"" << val << "\")";
+      name = oss.str();
+      jnienv->ReleaseStringUTFChars(str, val);
+    }
+  }
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen));
+  jnienv->DeleteLocalRef(klass);
+  return name;
+}
+
+static std::string GetValOf(jvmtiEnv* env, JNIEnv* jnienv, std::string type, jvalue val) {
+  std::ostringstream oss;
+  switch (type[0]) {
+    case '[':
+    case 'L':
+      return val.l != nullptr ? GetName(env, jnienv, val.l) : "null";
+    case 'Z':
+      return val.z == JNI_TRUE ? "true" : "false";
+    case 'B':
+      oss << val.b;
+      return oss.str();
+    case 'C':
+      oss << val.c;
+      return oss.str();
+    case 'S':
+      oss << val.s;
+      return oss.str();
+    case 'I':
+      oss << val.i;
+      return oss.str();
+    case 'J':
+      oss << val.j;
+      return oss.str();
+    case 'F':
+      oss << val.f;
+      return oss.str();
+    case 'D':
+      oss << val.d;
+      return oss.str();
+    case 'V':
+      return "<void>";
+    default:
+      return "<ERROR Found type " + type + ">";
+  }
+}
+
+void JNICALL MethodExitHook(jvmtiEnv* jvmtienv,
+                            JNIEnv* env,
+                            jthread thread,
+                            jmethodID m,
+                            jboolean was_popped_by_exception,
+                            jvalue val) {
+  jvmtiThreadInfo info;
+  if (thread == nullptr) {
+    info.name = const_cast<char*>("<NULLPTR>");
+  } else if (jvmtienv->GetThreadInfo(thread, &info) != JVMTI_ERROR_NONE) {
+    // LOG(WARNING) << "Unable to get thread info!";
+    info.name = const_cast<char*>("<UNKNOWN THREAD>");
+  }
+  char *fname, *fsig, *fgen;
+  char *cname, *cgen;
+  jclass klass = nullptr;
+  if (jvmtienv->GetMethodDeclaringClass(m, &klass) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Unable to get method declaring class!";
+    return;
+  }
+  if (jvmtienv->GetMethodName(m, &fname, &fsig, &fgen) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Unable to get method name!";
+    env->DeleteLocalRef(klass);
+    return;
+  }
+  if (jvmtienv->GetClassSignature(klass, &cname, &cgen) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Unable to get class name!";
+    env->DeleteLocalRef(klass);
+    return;
+  }
+  std::string type(fsig);
+  type = type.substr(type.find(")") + 1);
+  std::string out_val(was_popped_by_exception ? "" : GetValOf(jvmtienv, env, type, val));
+  LOG(INFO) << "Leaving method \"" << cname << "->" << fname << fsig << "\". Thread is \""
+            << info.name << "\"." << std::endl
+            << "    Cause: " << (was_popped_by_exception ? "exception" : "return ")
+            << out_val << ".";
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen));
+  env->DeleteLocalRef(klass);
+}
+
+void JNICALL MethodEntryHook(jvmtiEnv* jvmtienv,
+                             JNIEnv* env,
+                             jthread thread,
+                             jmethodID m) {
+  jvmtiThreadInfo info;
+  if (thread == nullptr) {
+    info.name = const_cast<char*>("<NULLPTR>");
+  } else if (jvmtienv->GetThreadInfo(thread, &info) != JVMTI_ERROR_NONE) {
+    info.name = const_cast<char*>("<UNKNOWN THREAD>");
+  }
+  char *fname, *fsig, *fgen;
+  char *cname, *cgen;
+  jclass klass = nullptr;
+  if (jvmtienv->GetMethodDeclaringClass(m, &klass) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Unable to get method declaring class!";
+    return;
+  }
+  if (jvmtienv->GetMethodName(m, &fname, &fsig, &fgen) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Unable to get method name!";
+    env->DeleteLocalRef(klass);
+    return;
+  }
+  if (jvmtienv->GetClassSignature(klass, &cname, &cgen) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Unable to get class name!";
+    env->DeleteLocalRef(klass);
+    return;
+  }
+  LOG(INFO) << "Entering method \"" << cname << "->" << fname << fsig << "\". Thread is \""
+            << info.name << "\"";
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig));
+  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen));
+  env->DeleteLocalRef(klass);
+}
+
 // The hook we are using.
 void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti,
                                          JNIEnv* jni_env ATTRIBUTE_UNUSED,
@@ -163,27 +310,57 @@
   }
 }
 
-// Options are ${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2}
-static void ReadOptions(StressData* data, char* options) {
-  std::string ops(options);
-  data->dexter_cmd = ops.substr(0, ops.find(','));
-  ops = ops.substr(ops.find(',') + 1);
-  data->in_temp_dex = ops.substr(0, ops.find(','));
-  ops = ops.substr(ops.find(',') + 1);
-  data->out_temp_dex = ops;
+static std::string AdvanceOption(const std::string& ops) {
+  return ops.substr(ops.find(',') + 1);
 }
 
-// We need to make sure that VMClassLoader is initialized before we start redefining anything since
-// it can give (non-fatal) error messages if it's initialized after we've redefined BCP classes.
-// These error messages are expected and no problem but they will mess up our testing
-// infrastructure.
-static void JNICALL EnsureVMClassloaderInitializedCB(jvmtiEnv *jvmti_env,
-                                                     JNIEnv* jni_env,
-                                                     jthread thread ATTRIBUTE_UNUSED) {
+static bool HasNextOption(const std::string& ops) {
+  return ops.find(',') != std::string::npos;
+}
+
+static std::string GetOption(const std::string& in) {
+  return in.substr(0, in.find(','));
+}
+
+// Options are
+// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace]
+static void ReadOptions(StressData* data, char* options) {
+  std::string ops(options);
+  CHECK_EQ(GetOption(ops), "jvmti-stress") << "Options should start with jvmti-stress";
+  do {
+    ops = AdvanceOption(ops);
+    std::string cur = GetOption(ops);
+    if (cur == "trace") {
+      data->trace_stress = true;
+    } else if (cur == "redefine") {
+      data->redefine_stress = true;
+      ops = AdvanceOption(ops);
+      data->dexter_cmd = GetOption(ops);
+      ops = AdvanceOption(ops);
+      data->in_temp_dex = GetOption(ops);
+      ops = AdvanceOption(ops);
+      data->out_temp_dex = GetOption(ops);
+    } else {
+      LOG(FATAL) << "Unknown option: " << GetOption(ops);
+    }
+  } while (HasNextOption(ops));
+}
+
+// Do final setup during the VMInit callback. By this time most things are all setup.
+static void JNICALL PerformFinalSetupVMInit(jvmtiEnv *jvmti_env,
+                                            JNIEnv* jni_env,
+                                            jthread thread ATTRIBUTE_UNUSED) {
   // Load the VMClassLoader class. We will get a ClassNotFound exception because we don't have
   // visibility but the class will be loaded behind the scenes.
   LOG(INFO) << "manual load & initialization of class java/lang/VMClassLoader!";
   jclass klass = jni_env->FindClass("java/lang/VMClassLoader");
+  StressData* data = nullptr;
+  CHECK_EQ(jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
+           JVMTI_ERROR_NONE);
+  // We need to make sure that VMClassLoader is initialized before we start redefining anything
+  // since it can give (non-fatal) error messages if it's initialized after we've redefined BCP
+  // classes. These error messages are expected and no problem but they will mess up our testing
+  // infrastructure.
   if (klass == nullptr) {
     // Probably on RI. Clear the exception so we can continue but don't mark vmclassloader as
     // initialized.
@@ -193,11 +370,20 @@
     // GetMethodID is spec'd to cause the class to be initialized.
     jni_env->GetMethodID(klass, "hashCode", "()I");
     jni_env->DeleteLocalRef(klass);
-    StressData* data = nullptr;
-    CHECK_EQ(jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
-             JVMTI_ERROR_NONE);
     data->vm_class_loader_initialized = true;
   }
+  if (data->trace_stress) {
+    if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_METHOD_ENTRY,
+                                            nullptr) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!";
+    }
+    if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                        JVMTI_EVENT_METHOD_EXIT,
+                                        nullptr) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!";
+    }
+  }
 }
 
 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm,
@@ -233,7 +419,9 @@
   memset(&cb, 0, sizeof(cb));
   cb.ClassFileLoadHook = ClassFileLoadHookSecretNoOp;
   cb.NativeMethodBind = doJvmtiMethodBind;
-  cb.VMInit = EnsureVMClassloaderInitializedCB;
+  cb.VMInit = PerformFinalSetupVMInit;
+  cb.MethodEntry = MethodEntryHook;
+  cb.MethodExit = MethodExitHook;
   if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
     LOG(ERROR) << "Unable to set class file load hook cb!";
     return 1;
@@ -250,11 +438,13 @@
     LOG(ERROR) << "Unable to enable JVMTI_EVENT_VM_INIT event!";
     return 1;
   }
-  if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
-                                      JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
-                                      nullptr) != JVMTI_ERROR_NONE) {
-    LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!";
-    return 1;
+  if (data->redefine_stress) {
+    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+                                        JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+                                        nullptr) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!";
+      return 1;
+    }
   }
   return 0;
 }