Add method tracing JVMTI callbacks
Add MethodEntryHook and MethodExitHook callbacks and associated
capabilities.
Split --jvmti-stress option in run-test into --jvmti-trace-stress and
--jvmti-redefine-stress to test each different component.
NB 3 differences from RI found:
1) RI will call methodExitHook again if the method exit hook throws
an exception. This can easily cause an infinite loop and the test
is specifically tweaked to prevent this from happening on the RI.
2) RI always includes the method being exited in the stack trace of
errors thrown in the hooks. In ART we will not include the method
if it is native. This is due to the way we call native methods
and would be extremely difficult to change.
3) The RI will allow exceptions thrown in the MethodEnterHook to be
caught by the entered method in some situations. This occurs with
the tryCatchExit test in 989. In ART this does not happen.
Bug: 34414073
Test: ./test.py --host -j40
Test: ART_TEST_FULL=true DEXTER_BINARY="/path/to/dexter" \
./test/testrunner/testrunner.py --host -j40 -t 988
Test: ART_TEST_FULL=true DEXTER_BINARY="/path/to/dexter" \
./test/testrunner/testrunner.py --host -j40 -t 989
Test: lunch aosp_angler-userdebug; \
m -j40 droid build-art && \
fastboot -w flashall && \
./test.py --target -j4
Change-Id: Iab229353fae23c2ea27c2b698c831627a9f861b1
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;
}