ART: Move openjdkjvmti to art/
Move libopenjdkjvmti out of the runtime directory. Let's not
pollute the runtime library.
Test: m test-art-host
Change-Id: Idb6b9cebcd61777bd3200437a2ae584a63a4a341
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
new file mode 100644
index 0000000..b6b1b56
--- /dev/null
+++ b/openjdkjvmti/Android.bp
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2016 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.
+
+cc_library_headers {
+ name: "libopenjdkjvmti_headers",
+ host_supported: true,
+ export_include_dirs: ["include"],
+}
+
+cc_defaults {
+ name: "libopenjdkjvmti_defaults",
+ defaults: ["art_defaults"],
+ host_supported: true,
+ srcs: ["events.cc",
+ "fixed_up_dex_file.cc",
+ "object_tagging.cc",
+ "OpenjdkJvmTi.cc",
+ "ti_allocator.cc",
+ "ti_breakpoint.cc",
+ "ti_class.cc",
+ "ti_class_definition.cc",
+ "ti_class_loader.cc",
+ "ti_dump.cc",
+ "ti_field.cc",
+ "ti_heap.cc",
+ "ti_jni.cc",
+ "ti_method.cc",
+ "ti_monitor.cc",
+ "ti_object.cc",
+ "ti_phase.cc",
+ "ti_properties.cc",
+ "ti_search.cc",
+ "ti_stack.cc",
+ "ti_redefine.cc",
+ "ti_thread.cc",
+ "ti_threadgroup.cc",
+ "ti_timers.cc",
+ "transform.cc"],
+ header_libs: ["libopenjdkjvmti_headers"],
+ shared_libs: [
+ "libbase",
+ "libnativehelper",
+ ],
+}
+
+art_cc_library {
+ name: "libopenjdkjvmti",
+ defaults: ["libopenjdkjvmti_defaults"],
+ shared_libs: [
+ "libart",
+ "libart-compiler",
+ ],
+}
+
+art_cc_library {
+ name: "libopenjdkjvmtid",
+ defaults: [
+ "art_debug_defaults",
+ "libopenjdkjvmti_defaults",
+ ],
+ shared_libs: [
+ "libartd",
+ "libartd-compiler",
+ ],
+}
diff --git a/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
diff --git a/openjdkjvmti/NOTICE b/openjdkjvmti/NOTICE
new file mode 100644
index 0000000..6ec62cd
--- /dev/null
+++ b/openjdkjvmti/NOTICE
@@ -0,0 +1,29 @@
+Copyright (C) 2016 The Android Open Source Project
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+This file implements interfaces from the file jvmti.h. This implementation
+is licensed under the same terms as the file jvmti.h. The
+copyright and license information for the file jvmti.h follows.
+
+Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+This code is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 2 only, as
+published by the Free Software Foundation. Oracle designates this
+particular file as subject to the "Classpath" exception as provided
+by Oracle in the LICENSE file that accompanied this code.
+
+This code is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+version 2 for more details (a copy is included in the LICENSE file that
+accompanied this code).
+
+You should have received a copy of the GNU General Public License version
+2 along with this work; if not, write to the Free Software Foundation,
+Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+or visit www.oracle.com if you need additional information or have any
+questions.
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
new file mode 100644
index 0000000..af77072
--- /dev/null
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -0,0 +1,1903 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <jni.h>
+
+#include "jvmti.h"
+
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "base/mutex.h"
+#include "events-inl.h"
+#include "jni_env_ext-inl.h"
+#include "obj_ptr-inl.h"
+#include "object_tagging.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_allocator.h"
+#include "ti_breakpoint.h"
+#include "ti_class.h"
+#include "ti_dump.h"
+#include "ti_field.h"
+#include "ti_heap.h"
+#include "ti_jni.h"
+#include "ti_method.h"
+#include "ti_monitor.h"
+#include "ti_object.h"
+#include "ti_phase.h"
+#include "ti_properties.h"
+#include "ti_redefine.h"
+#include "ti_search.h"
+#include "ti_stack.h"
+#include "ti_thread.h"
+#include "ti_threadgroup.h"
+#include "ti_timers.h"
+#include "transform.h"
+
+namespace openjdkjvmti {
+
+EventHandler gEventHandler;
+
+#define ENSURE_NON_NULL(n) \
+ do { \
+ if ((n) == nullptr) { \
+ return ERR(NULL_POINTER); \
+ } \
+ } while (false)
+
+class JvmtiFunctions {
+ private:
+ static jvmtiError getEnvironmentError(jvmtiEnv* env) {
+ if (env == nullptr) {
+ return ERR(INVALID_ENVIRONMENT);
+ } else if (art::Thread::Current() == nullptr) {
+ return ERR(UNATTACHED_THREAD);
+ } else {
+ return OK;
+ }
+ }
+
+#define ENSURE_VALID_ENV(env) \
+ do { \
+ jvmtiError ensure_valid_env_ ## __LINE__ = getEnvironmentError(env); \
+ if (ensure_valid_env_ ## __LINE__ != OK) { \
+ return ensure_valid_env_ ## __LINE__ ; \
+ } \
+ } while (false)
+
+#define ENSURE_HAS_CAP(env, cap) \
+ do { \
+ if (ArtJvmTiEnv::AsArtJvmTiEnv(env)->capabilities.cap != 1) { \
+ return ERR(MUST_POSSESS_CAPABILITY); \
+ } \
+ } while (false)
+
+ public:
+ static jvmtiError Allocate(jvmtiEnv* env, jlong size, unsigned char** mem_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_NON_NULL(mem_ptr);
+ return AllocUtil::Allocate(env, size, mem_ptr);
+ }
+
+ static jvmtiError Deallocate(jvmtiEnv* env, unsigned char* mem) {
+ ENSURE_VALID_ENV(env);
+ return AllocUtil::Deallocate(env, mem);
+ }
+
+ static jvmtiError GetThreadState(jvmtiEnv* env, jthread thread, jint* thread_state_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ThreadUtil::GetThreadState(env, thread, thread_state_ptr);
+ }
+
+ static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ThreadUtil::GetCurrentThread(env, thread_ptr);
+ }
+
+ static jvmtiError GetAllThreads(jvmtiEnv* env, jint* threads_count_ptr, jthread** threads_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ThreadUtil::GetAllThreads(env, threads_count_ptr, threads_ptr);
+ }
+
+ static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_suspend);
+ return ThreadUtil::SuspendThread(env, thread);
+ }
+
+ static jvmtiError SuspendThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* request_list,
+ jvmtiError* results) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_suspend);
+ return ThreadUtil::SuspendThreadList(env, request_count, request_list, results);
+ }
+
+ static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_suspend);
+ return ThreadUtil::ResumeThread(env, thread);
+ }
+
+ static jvmtiError ResumeThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* request_list,
+ jvmtiError* results) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_suspend);
+ return ThreadUtil::ResumeThreadList(env, request_count, request_list, results);
+ }
+
+ static jvmtiError StopThread(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jobject exception ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_signal_thread);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError InterruptThread(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_signal_thread);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ThreadUtil::GetThreadInfo(env, thread, info_ptr);
+ }
+
+ static jvmtiError GetOwnedMonitorInfo(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jint* owned_monitor_count_ptr ATTRIBUTE_UNUSED,
+ jobject** owned_monitors_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_owned_monitor_info);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetOwnedMonitorStackDepthInfo(
+ jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jint* monitor_info_count_ptr ATTRIBUTE_UNUSED,
+ jvmtiMonitorStackDepthInfo** monitor_info_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_owned_monitor_stack_depth_info);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jobject* monitor_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_current_contended_monitor);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError RunAgentThread(jvmtiEnv* env,
+ jthread thread,
+ jvmtiStartFunction proc,
+ const void* arg,
+ jint priority) {
+ ENSURE_VALID_ENV(env);
+ return ThreadUtil::RunAgentThread(env, thread, proc, arg, priority);
+ }
+
+ static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data) {
+ ENSURE_VALID_ENV(env);
+ return ThreadUtil::SetThreadLocalStorage(env, thread, data);
+ }
+
+ static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ThreadUtil::GetThreadLocalStorage(env, thread, data_ptr);
+ }
+
+ static jvmtiError GetTopThreadGroups(jvmtiEnv* env,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ThreadGroupUtil::GetTopThreadGroups(env, group_count_ptr, groups_ptr);
+ }
+
+ static jvmtiError GetThreadGroupInfo(jvmtiEnv* env,
+ jthreadGroup group,
+ jvmtiThreadGroupInfo* info_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ThreadGroupUtil::GetThreadGroupInfo(env, group, info_ptr);
+ }
+
+ static jvmtiError GetThreadGroupChildren(jvmtiEnv* env,
+ jthreadGroup group,
+ jint* thread_count_ptr,
+ jthread** threads_ptr,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ThreadGroupUtil::GetThreadGroupChildren(env,
+ group,
+ thread_count_ptr,
+ threads_ptr,
+ group_count_ptr,
+ groups_ptr);
+ }
+
+ static jvmtiError GetStackTrace(jvmtiEnv* env,
+ jthread thread,
+ jint start_depth,
+ jint max_frame_count,
+ jvmtiFrameInfo* frame_buffer,
+ jint* count_ptr) {
+ ENSURE_VALID_ENV(env);
+ return StackUtil::GetStackTrace(env,
+ thread,
+ start_depth,
+ max_frame_count,
+ frame_buffer,
+ count_ptr);
+ }
+
+ static jvmtiError GetAllStackTraces(jvmtiEnv* env,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr,
+ jint* thread_count_ptr) {
+ ENSURE_VALID_ENV(env);
+ return StackUtil::GetAllStackTraces(env, max_frame_count, stack_info_ptr, thread_count_ptr);
+ }
+
+ static jvmtiError GetThreadListStackTraces(jvmtiEnv* env,
+ jint thread_count,
+ const jthread* thread_list,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr) {
+ ENSURE_VALID_ENV(env);
+ return StackUtil::GetThreadListStackTraces(env,
+ thread_count,
+ thread_list,
+ max_frame_count,
+ stack_info_ptr);
+ }
+
+ static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr) {
+ ENSURE_VALID_ENV(env);
+ return StackUtil::GetFrameCount(env, thread, count_ptr);
+ }
+
+ static jvmtiError PopFrame(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_pop_frame);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetFrameLocation(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jmethodID* method_ptr,
+ jlocation* location_ptr) {
+ ENSURE_VALID_ENV(env);
+ return StackUtil::GetFrameLocation(env, thread, depth, method_ptr, location_ptr);
+ }
+
+ static jvmtiError NotifyFramePop(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jint depth ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_generate_frame_pop_events);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jobject value ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_force_early_return);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError ForceEarlyReturnInt(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jint value ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_force_early_return);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError ForceEarlyReturnLong(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jlong value ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_force_early_return);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError ForceEarlyReturnFloat(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jfloat value ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_force_early_return);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError ForceEarlyReturnDouble(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jdouble value ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_force_early_return);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError ForceEarlyReturnVoid(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_force_early_return);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError FollowReferences(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ jobject initial_object,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+ HeapUtil heap_util(ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get());
+ return heap_util.FollowReferences(env,
+ heap_filter,
+ klass,
+ initial_object,
+ callbacks,
+ user_data);
+ }
+
+ static jvmtiError IterateThroughHeap(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+ HeapUtil heap_util(ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get());
+ return heap_util.IterateThroughHeap(env, heap_filter, klass, callbacks, user_data);
+ }
+
+ static jvmtiError GetTag(jvmtiEnv* env, jobject object, jlong* tag_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+
+ JNIEnv* jni_env = GetJniEnv(env);
+ if (jni_env == nullptr) {
+ return ERR(INTERNAL);
+ }
+
+ art::ScopedObjectAccess soa(jni_env);
+ art::ObjPtr<art::mirror::Object> obj = soa.Decode<art::mirror::Object>(object);
+ if (!ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table->GetTag(obj.Ptr(), tag_ptr)) {
+ *tag_ptr = 0;
+ }
+
+ return ERR(NONE);
+ }
+
+ static jvmtiError SetTag(jvmtiEnv* env, jobject object, jlong tag) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+
+ if (object == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ JNIEnv* jni_env = GetJniEnv(env);
+ if (jni_env == nullptr) {
+ return ERR(INTERNAL);
+ }
+
+ art::ScopedObjectAccess soa(jni_env);
+ art::ObjPtr<art::mirror::Object> obj = soa.Decode<art::mirror::Object>(object);
+ ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table->Set(obj.Ptr(), tag);
+
+ return ERR(NONE);
+ }
+
+ static jvmtiError GetObjectsWithTags(jvmtiEnv* env,
+ jint tag_count,
+ const jlong* tags,
+ jint* count_ptr,
+ jobject** object_result_ptr,
+ jlong** tag_result_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+
+ JNIEnv* jni_env = GetJniEnv(env);
+ if (jni_env == nullptr) {
+ return ERR(INTERNAL);
+ }
+
+ art::ScopedObjectAccess soa(jni_env);
+ return ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table->GetTaggedObjects(env,
+ tag_count,
+ tags,
+ count_ptr,
+ object_result_ptr,
+ tag_result_ptr);
+ }
+
+ static jvmtiError ForceGarbageCollection(jvmtiEnv* env) {
+ ENSURE_VALID_ENV(env);
+ return HeapUtil::ForceGarbageCollection(env);
+ }
+
+ static jvmtiError IterateOverObjectsReachableFromObject(
+ jvmtiEnv* env,
+ jobject object ATTRIBUTE_UNUSED,
+ jvmtiObjectReferenceCallback object_reference_callback ATTRIBUTE_UNUSED,
+ const void* user_data ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError IterateOverReachableObjects(
+ jvmtiEnv* env,
+ jvmtiHeapRootCallback heap_root_callback ATTRIBUTE_UNUSED,
+ jvmtiStackReferenceCallback stack_ref_callback ATTRIBUTE_UNUSED,
+ jvmtiObjectReferenceCallback object_ref_callback ATTRIBUTE_UNUSED,
+ const void* user_data ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError IterateOverHeap(jvmtiEnv* env,
+ jvmtiHeapObjectFilter object_filter ATTRIBUTE_UNUSED,
+ jvmtiHeapObjectCallback heap_object_callback ATTRIBUTE_UNUSED,
+ const void* user_data ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError IterateOverInstancesOfClass(
+ jvmtiEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jvmtiHeapObjectFilter object_filter ATTRIBUTE_UNUSED,
+ jvmtiHeapObjectCallback heap_object_callback ATTRIBUTE_UNUSED,
+ const void* user_data ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_tag_objects);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetLocalObject(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jobject* value_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+ }
+
+ static jvmtiError GetLocalInstance(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jobject* value_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::GetLocalInstance(env, thread, depth, value_ptr);
+ }
+
+ static jvmtiError GetLocalInt(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jint* value_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+ }
+
+ static jvmtiError GetLocalLong(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jlong* value_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+ }
+
+ static jvmtiError GetLocalFloat(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jfloat* value_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+ }
+
+ static jvmtiError GetLocalDouble(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jdouble* value_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+ }
+
+ static jvmtiError SetLocalObject(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jobject value) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+ }
+
+ static jvmtiError SetLocalInt(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jint value) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+ }
+
+ static jvmtiError SetLocalLong(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jlong value) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+ }
+
+ static jvmtiError SetLocalFloat(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jfloat value) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+ }
+
+ static jvmtiError SetLocalDouble(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jdouble value) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+ }
+
+
+ static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_generate_breakpoint_events);
+ return BreakpointUtil::SetBreakpoint(env, method, location);
+ }
+
+ static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_generate_breakpoint_events);
+ return BreakpointUtil::ClearBreakpoint(env, method, location);
+ }
+
+ static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_generate_field_access_events);
+ return FieldUtil::SetFieldAccessWatch(env, klass, field);
+ }
+
+ static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_generate_field_access_events);
+ return FieldUtil::ClearFieldAccessWatch(env, klass, field);
+ }
+
+ static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_generate_field_modification_events);
+ return FieldUtil::SetFieldModificationWatch(env, klass, field);
+ }
+
+ static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_generate_field_modification_events);
+ return FieldUtil::ClearFieldModificationWatch(env, klass, field);
+ }
+
+ static jvmtiError GetLoadedClasses(jvmtiEnv* env, jint* class_count_ptr, jclass** classes_ptr) {
+ ENSURE_VALID_ENV(env);
+ HeapUtil heap_util(ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get());
+ return heap_util.GetLoadedClasses(env, class_count_ptr, classes_ptr);
+ }
+
+ static jvmtiError GetClassLoaderClasses(jvmtiEnv* env,
+ jobject initiating_loader,
+ jint* class_count_ptr,
+ jclass** classes_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetClassLoaderClasses(env, initiating_loader, class_count_ptr, classes_ptr);
+ }
+
+ static jvmtiError GetClassSignature(jvmtiEnv* env,
+ jclass klass,
+ char** signature_ptr,
+ char** generic_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetClassSignature(env, klass, signature_ptr, generic_ptr);
+ }
+
+ static jvmtiError GetClassStatus(jvmtiEnv* env, jclass klass, jint* status_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetClassStatus(env, klass, status_ptr);
+ }
+
+ static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_source_file_name);
+ return ClassUtil::GetSourceFileName(env, klass, source_name_ptr);
+ }
+
+ static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetClassModifiers(env, klass, modifiers_ptr);
+ }
+
+ static jvmtiError GetClassMethods(jvmtiEnv* env,
+ jclass klass,
+ jint* method_count_ptr,
+ jmethodID** methods_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetClassMethods(env, klass, method_count_ptr, methods_ptr);
+ }
+
+ static jvmtiError GetClassFields(jvmtiEnv* env,
+ jclass klass,
+ jint* field_count_ptr,
+ jfieldID** fields_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetClassFields(env, klass, field_count_ptr, fields_ptr);
+ }
+
+ static jvmtiError GetImplementedInterfaces(jvmtiEnv* env,
+ jclass klass,
+ jint* interface_count_ptr,
+ jclass** interfaces_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetImplementedInterfaces(env, klass, interface_count_ptr, interfaces_ptr);
+ }
+
+ static jvmtiError GetClassVersionNumbers(jvmtiEnv* env,
+ jclass klass,
+ jint* minor_version_ptr,
+ jint* major_version_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetClassVersionNumbers(env, klass, minor_version_ptr, major_version_ptr);
+ }
+
+ static jvmtiError GetConstantPool(jvmtiEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jint* constant_pool_count_ptr ATTRIBUTE_UNUSED,
+ jint* constant_pool_byte_count_ptr ATTRIBUTE_UNUSED,
+ unsigned char** constant_pool_bytes_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_constant_pool);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::IsInterface(env, klass, is_interface_ptr);
+ }
+
+ static jvmtiError IsArrayClass(jvmtiEnv* env,
+ jclass klass,
+ jboolean* is_array_class_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::IsArrayClass(env, klass, is_array_class_ptr);
+ }
+
+ static jvmtiError IsModifiableClass(jvmtiEnv* env,
+ jclass klass,
+ jboolean* is_modifiable_class_ptr) {
+ ENSURE_VALID_ENV(env);
+ return Redefiner::IsModifiableClass(env, klass, is_modifiable_class_ptr);
+ }
+
+ static jvmtiError GetClassLoader(jvmtiEnv* env, jclass klass, jobject* classloader_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ClassUtil::GetClassLoader(env, klass, classloader_ptr);
+ }
+
+ static jvmtiError GetSourceDebugExtension(jvmtiEnv* env,
+ jclass klass,
+ char** source_debug_extension_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_source_debug_extension);
+ return ClassUtil::GetSourceDebugExtension(env, klass, source_debug_extension_ptr);
+ }
+
+ static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_retransform_classes);
+ std::string error_msg;
+ jvmtiError res = Transformer::RetransformClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+ &gEventHandler,
+ art::Runtime::Current(),
+ art::Thread::Current(),
+ class_count,
+ classes,
+ &error_msg);
+ if (res != OK) {
+ LOG(WARNING) << "FAILURE TO RETRANFORM " << error_msg;
+ }
+ return res;
+ }
+
+ static jvmtiError RedefineClasses(jvmtiEnv* env,
+ jint class_count,
+ const jvmtiClassDefinition* class_definitions) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_redefine_classes);
+ std::string error_msg;
+ jvmtiError res = Redefiner::RedefineClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+ &gEventHandler,
+ art::Runtime::Current(),
+ art::Thread::Current(),
+ class_count,
+ class_definitions,
+ &error_msg);
+ if (res != OK) {
+ LOG(WARNING) << "FAILURE TO REDEFINE " << error_msg;
+ }
+ return res;
+ }
+
+ static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ObjectUtil::GetObjectSize(env, object, size_ptr);
+ }
+
+ static jvmtiError GetObjectHashCode(jvmtiEnv* env, jobject object, jint* hash_code_ptr) {
+ ENSURE_VALID_ENV(env);
+ return ObjectUtil::GetObjectHashCode(env, object, hash_code_ptr);
+ }
+
+ static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env,
+ jobject object ATTRIBUTE_UNUSED,
+ jvmtiMonitorUsage* info_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_monitor_info);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetFieldName(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr) {
+ ENSURE_VALID_ENV(env);
+ return FieldUtil::GetFieldName(env, klass, field, name_ptr, signature_ptr, generic_ptr);
+ }
+
+ static jvmtiError GetFieldDeclaringClass(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jclass* declaring_class_ptr) {
+ ENSURE_VALID_ENV(env);
+ return FieldUtil::GetFieldDeclaringClass(env, klass, field, declaring_class_ptr);
+ }
+
+ static jvmtiError GetFieldModifiers(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jint* modifiers_ptr) {
+ ENSURE_VALID_ENV(env);
+ return FieldUtil::GetFieldModifiers(env, klass, field, modifiers_ptr);
+ }
+
+ static jvmtiError IsFieldSynthetic(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jboolean* is_synthetic_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_synthetic_attribute);
+ return FieldUtil::IsFieldSynthetic(env, klass, field, is_synthetic_ptr);
+ }
+
+ static jvmtiError GetMethodName(jvmtiEnv* env,
+ jmethodID method,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MethodUtil::GetMethodName(env, method, name_ptr, signature_ptr, generic_ptr);
+ }
+
+ static jvmtiError GetMethodDeclaringClass(jvmtiEnv* env,
+ jmethodID method,
+ jclass* declaring_class_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MethodUtil::GetMethodDeclaringClass(env, method, declaring_class_ptr);
+ }
+
+ static jvmtiError GetMethodModifiers(jvmtiEnv* env,
+ jmethodID method,
+ jint* modifiers_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MethodUtil::GetMethodModifiers(env, method, modifiers_ptr);
+ }
+
+ static jvmtiError GetMaxLocals(jvmtiEnv* env,
+ jmethodID method,
+ jint* max_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MethodUtil::GetMaxLocals(env, method, max_ptr);
+ }
+
+ static jvmtiError GetArgumentsSize(jvmtiEnv* env,
+ jmethodID method,
+ jint* size_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MethodUtil::GetArgumentsSize(env, method, size_ptr);
+ }
+
+ static jvmtiError GetLineNumberTable(jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLineNumberEntry** table_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_line_numbers);
+ return MethodUtil::GetLineNumberTable(env, method, entry_count_ptr, table_ptr);
+ }
+
+ static jvmtiError GetMethodLocation(jvmtiEnv* env,
+ jmethodID method,
+ jlocation* start_location_ptr,
+ jlocation* end_location_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MethodUtil::GetMethodLocation(env, method, start_location_ptr, end_location_ptr);
+ }
+
+ static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLocalVariableEntry** table_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_access_local_variables);
+ return MethodUtil::GetLocalVariableTable(env, method, entry_count_ptr, table_ptr);
+ }
+
+ static jvmtiError GetBytecodes(jvmtiEnv* env,
+ jmethodID method,
+ jint* bytecode_count_ptr,
+ unsigned char** bytecodes_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_bytecodes);
+ return MethodUtil::GetBytecodes(env, method, bytecode_count_ptr, bytecodes_ptr);
+ }
+
+ static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MethodUtil::IsMethodNative(env, method, is_native_ptr);
+ }
+
+ static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_synthetic_attribute);
+ return MethodUtil::IsMethodSynthetic(env, method, is_synthetic_ptr);
+ }
+
+ static jvmtiError IsMethodObsolete(jvmtiEnv* env, jmethodID method, jboolean* is_obsolete_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MethodUtil::IsMethodObsolete(env, method, is_obsolete_ptr);
+ }
+
+ static jvmtiError SetNativeMethodPrefix(jvmtiEnv* env, const char* prefix ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_set_native_method_prefix);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError SetNativeMethodPrefixes(jvmtiEnv* env,
+ jint prefix_count ATTRIBUTE_UNUSED,
+ char** prefixes ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_set_native_method_prefix);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError CreateRawMonitor(jvmtiEnv* env, const char* name, jrawMonitorID* monitor_ptr) {
+ ENSURE_VALID_ENV(env);
+ return MonitorUtil::CreateRawMonitor(env, name, monitor_ptr);
+ }
+
+ static jvmtiError DestroyRawMonitor(jvmtiEnv* env, jrawMonitorID monitor) {
+ ENSURE_VALID_ENV(env);
+ return MonitorUtil::DestroyRawMonitor(env, monitor);
+ }
+
+ static jvmtiError RawMonitorEnter(jvmtiEnv* env, jrawMonitorID monitor) {
+ ENSURE_VALID_ENV(env);
+ return MonitorUtil::RawMonitorEnter(env, monitor);
+ }
+
+ static jvmtiError RawMonitorExit(jvmtiEnv* env, jrawMonitorID monitor) {
+ ENSURE_VALID_ENV(env);
+ return MonitorUtil::RawMonitorExit(env, monitor);
+ }
+
+ static jvmtiError RawMonitorWait(jvmtiEnv* env, jrawMonitorID monitor, jlong millis) {
+ ENSURE_VALID_ENV(env);
+ return MonitorUtil::RawMonitorWait(env, monitor, millis);
+ }
+
+ static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor) {
+ ENSURE_VALID_ENV(env);
+ return MonitorUtil::RawMonitorNotify(env, monitor);
+ }
+
+ static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor) {
+ ENSURE_VALID_ENV(env);
+ return MonitorUtil::RawMonitorNotifyAll(env, monitor);
+ }
+
+ static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table) {
+ ENSURE_VALID_ENV(env);
+ return JNIUtil::SetJNIFunctionTable(env, function_table);
+ }
+
+ static jvmtiError GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table) {
+ ENSURE_VALID_ENV(env);
+ return JNIUtil::GetJNIFunctionTable(env, function_table);
+ }
+
+ // TODO: This will require locking, so that an agent can't remove callbacks when we're dispatching
+ // an event.
+ static jvmtiError SetEventCallbacks(jvmtiEnv* env,
+ const jvmtiEventCallbacks* callbacks,
+ jint size_of_callbacks) {
+ ENSURE_VALID_ENV(env);
+ if (size_of_callbacks < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+
+ if (callbacks == nullptr) {
+ ArtJvmTiEnv::AsArtJvmTiEnv(env)->event_callbacks.reset();
+ return ERR(NONE);
+ }
+
+ std::unique_ptr<jvmtiEventCallbacks> tmp(new jvmtiEventCallbacks());
+ memset(tmp.get(), 0, sizeof(jvmtiEventCallbacks));
+ size_t copy_size = std::min(sizeof(jvmtiEventCallbacks),
+ static_cast<size_t>(size_of_callbacks));
+ copy_size = art::RoundDown(copy_size, sizeof(void*));
+ memcpy(tmp.get(), callbacks, copy_size);
+
+ ArtJvmTiEnv::AsArtJvmTiEnv(env)->event_callbacks = std::move(tmp);
+
+ return ERR(NONE);
+ }
+
+ static jvmtiError SetEventNotificationMode(jvmtiEnv* env,
+ jvmtiEventMode mode,
+ jvmtiEvent event_type,
+ jthread event_thread,
+ ...) {
+ ENSURE_VALID_ENV(env);
+ art::Thread* art_thread = nullptr;
+ if (event_thread != nullptr) {
+ // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+ art_thread = art::Thread::FromManagedThread(soa, event_thread);
+
+ if (art_thread == nullptr || // The thread hasn't been started or is already dead.
+ art_thread->IsStillStarting()) {
+ // TODO: We may want to let the EventHandler know, so it could clean up masks, potentially.
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ }
+
+ ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+ return gEventHandler.SetEvent(art_env, art_thread, GetArtJvmtiEvent(art_env, event_type), mode);
+ }
+
+ static jvmtiError GenerateEvents(jvmtiEnv* env,
+ jvmtiEvent event_type ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ return OK;
+ }
+
+ static jvmtiError GetExtensionFunctions(jvmtiEnv* env,
+ jint* extension_count_ptr,
+ jvmtiExtensionFunctionInfo** extensions) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_NON_NULL(extension_count_ptr);
+ ENSURE_NON_NULL(extensions);
+
+ std::vector<jvmtiExtensionFunctionInfo> ext_vector;
+
+ // Holders for allocated values.
+ std::vector<JvmtiUniquePtr<char[]>> char_buffers;
+ std::vector<JvmtiUniquePtr<jvmtiParamInfo[]>> param_buffers;
+ std::vector<JvmtiUniquePtr<jvmtiError[]>> error_buffers;
+
+ // Add a helper struct that takes an arbitrary const char*. add_extension will use Allocate
+ // appropriately.
+ struct CParamInfo {
+ const char* name;
+ jvmtiParamKind kind;
+ jvmtiParamTypes base_type;
+ jboolean null_ok;
+ };
+
+ auto add_extension = [&](jvmtiExtensionFunction func,
+ const char* id,
+ const char* short_description,
+ jint param_count,
+ const std::vector<CParamInfo>& params,
+ jint error_count,
+ const std::vector<jvmtiError>& errors) {
+ jvmtiExtensionFunctionInfo func_info;
+ jvmtiError error;
+
+ func_info.func = func;
+
+ JvmtiUniquePtr<char[]> id_ptr = CopyString(env, id, &error);
+ if (id_ptr == nullptr) {
+ return error;
+ }
+ func_info.id = id_ptr.get();
+ char_buffers.push_back(std::move(id_ptr));
+
+ JvmtiUniquePtr<char[]> descr = CopyString(env, short_description, &error);
+ if (descr == nullptr) {
+ return error;
+ }
+ func_info.short_description = descr.get();
+ char_buffers.push_back(std::move(descr));
+
+ func_info.param_count = param_count;
+ if (param_count > 0) {
+ JvmtiUniquePtr<jvmtiParamInfo[]> params_ptr =
+ AllocJvmtiUniquePtr<jvmtiParamInfo[]>(env, param_count, &error);
+ if (params_ptr == nullptr) {
+ return error;
+ }
+ func_info.params = params_ptr.get();
+ param_buffers.push_back(std::move(params_ptr));
+
+ for (jint i = 0; i != param_count; ++i) {
+ JvmtiUniquePtr<char[]> param_name = CopyString(env, params[i].name, &error);
+ if (param_name == nullptr) {
+ return error;
+ }
+ func_info.params[i].name = param_name.get();
+ char_buffers.push_back(std::move(param_name));
+
+ func_info.params[i].kind = params[i].kind;
+ func_info.params[i].base_type = params[i].base_type;
+ func_info.params[i].null_ok = params[i].null_ok;
+ }
+ } else {
+ func_info.params = nullptr;
+ }
+
+ func_info.error_count = error_count;
+ if (error_count > 0) {
+ JvmtiUniquePtr<jvmtiError[]> errors_ptr =
+ AllocJvmtiUniquePtr<jvmtiError[]>(env, error_count, &error);
+ if (errors_ptr == nullptr) {
+ return error;
+ }
+ func_info.errors = errors_ptr.get();
+ error_buffers.push_back(std::move(errors_ptr));
+
+ for (jint i = 0; i != error_count; ++i) {
+ func_info.errors[i] = errors[i];
+ }
+ } else {
+ func_info.errors = nullptr;
+ }
+
+ ext_vector.push_back(func_info);
+
+ return ERR(NONE);
+ };
+
+ jvmtiError error;
+
+ // Heap extensions.
+ error = add_extension(
+ reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::GetObjectHeapId),
+ "com.android.art.heap.get_object_heap_id",
+ "Retrieve the heap id of the the object tagged with the given argument. An "
+ "arbitrary object is chosen if multiple objects exist with the same tag.",
+ 2,
+ { // NOLINT [whitespace/braces] [4]
+ { "tag", JVMTI_KIND_IN, JVMTI_TYPE_JLONG, false},
+ { "heap_id", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false}
+ },
+ 1,
+ { JVMTI_ERROR_NOT_FOUND });
+ if (error != ERR(NONE)) {
+ return error;
+ }
+
+ error = add_extension(
+ reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::GetHeapName),
+ "com.android.art.heap.get_heap_name",
+ "Retrieve the name of the heap with the given id.",
+ 2,
+ { // NOLINT [whitespace/braces] [4]
+ { "heap_id", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false},
+ { "heap_name", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, false}
+ },
+ 1,
+ { JVMTI_ERROR_ILLEGAL_ARGUMENT });
+ if (error != ERR(NONE)) {
+ return error;
+ }
+
+ error = add_extension(
+ reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::IterateThroughHeapExt),
+ "com.android.art.heap.iterate_through_heap_ext",
+ "Iterate through a heap. This is equivalent to the standard IterateThroughHeap function,"
+ " except for additionally passing the heap id of the current object. The jvmtiHeapCallbacks"
+ " structure is reused, with the callbacks field overloaded to a signature of "
+ "jint (*)(jlong, jlong, jlong*, jint length, void*, jint).",
+ 4,
+ { // NOLINT [whitespace/braces] [4]
+ { "heap_filter", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false},
+ { "klass", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, true},
+ { "callbacks", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CVOID, false},
+ { "user_data", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CVOID, true}
+ },
+ 3,
+ { // NOLINT [whitespace/braces] [4]
+ JVMTI_ERROR_MUST_POSSESS_CAPABILITY,
+ JVMTI_ERROR_INVALID_CLASS,
+ JVMTI_ERROR_NULL_POINTER
+ });
+ if (error != ERR(NONE)) {
+ return error;
+ }
+
+ error = add_extension(
+ reinterpret_cast<jvmtiExtensionFunction>(AllocUtil::GetGlobalJvmtiAllocationState),
+ "com.android.art.alloc.get_global_jvmti_allocation_state",
+ "Returns the total amount of memory currently allocated by all jvmtiEnvs through the"
+ " 'Allocate' jvmti function. This does not include any memory that has been deallocated"
+ " through the 'Deallocate' function. This number is approximate and might not correspond"
+ " exactly to the sum of the sizes of all not freed allocations.",
+ 1,
+ { // NOLINT [whitespace/braces] [4]
+ { "currently_allocated", JVMTI_KIND_OUT, JVMTI_TYPE_JLONG, false},
+ },
+ 1,
+ { ERR(NULL_POINTER) });
+ if (error != ERR(NONE)) {
+ return error;
+ }
+
+ // Copy into output buffer.
+
+ *extension_count_ptr = ext_vector.size();
+ JvmtiUniquePtr<jvmtiExtensionFunctionInfo[]> out_data =
+ AllocJvmtiUniquePtr<jvmtiExtensionFunctionInfo[]>(env, ext_vector.size(), &error);
+ if (out_data == nullptr) {
+ return error;
+ }
+ memcpy(out_data.get(),
+ ext_vector.data(),
+ ext_vector.size() * sizeof(jvmtiExtensionFunctionInfo));
+ *extensions = out_data.release();
+
+ // Release all the buffer holders, we're OK now.
+ for (auto& holder : char_buffers) {
+ holder.release();
+ }
+ for (auto& holder : param_buffers) {
+ holder.release();
+ }
+ for (auto& holder : error_buffers) {
+ holder.release();
+ }
+
+ return ERR(NONE);
+ }
+
+ static jvmtiError GetExtensionEvents(jvmtiEnv* env,
+ jint* extension_count_ptr,
+ jvmtiExtensionEventInfo** extensions) {
+ ENSURE_VALID_ENV(env);
+ // We do not have any extension events.
+ *extension_count_ptr = 0;
+ *extensions = nullptr;
+
+ return ERR(NONE);
+ }
+
+ static jvmtiError SetExtensionEventCallback(jvmtiEnv* env,
+ jint extension_event_index ATTRIBUTE_UNUSED,
+ jvmtiExtensionEvent callback ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ // We do not have any extension events, so any call is illegal.
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+
+ static jvmtiError GetPotentialCapabilities(jvmtiEnv* env, jvmtiCapabilities* capabilities_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_NON_NULL(capabilities_ptr);
+ *capabilities_ptr = kPotentialCapabilities;
+ return OK;
+ }
+
+ static jvmtiError AddCapabilities(jvmtiEnv* env, const jvmtiCapabilities* capabilities_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_NON_NULL(capabilities_ptr);
+ ArtJvmTiEnv* art_env = static_cast<ArtJvmTiEnv*>(env);
+ jvmtiError ret = OK;
+ jvmtiCapabilities changed = {};
+ jvmtiCapabilities potential_capabilities = {};
+ ret = env->GetPotentialCapabilities(&potential_capabilities);
+ if (ret != OK) {
+ return ret;
+ }
+#define ADD_CAPABILITY(e) \
+ do { \
+ if (capabilities_ptr->e == 1) { \
+ if (potential_capabilities.e == 1) { \
+ if (art_env->capabilities.e != 1) { \
+ art_env->capabilities.e = 1; \
+ changed.e = 1; \
+ }\
+ } else { \
+ ret = ERR(NOT_AVAILABLE); \
+ } \
+ } \
+ } while (false)
+
+ ADD_CAPABILITY(can_tag_objects);
+ ADD_CAPABILITY(can_generate_field_modification_events);
+ ADD_CAPABILITY(can_generate_field_access_events);
+ ADD_CAPABILITY(can_get_bytecodes);
+ ADD_CAPABILITY(can_get_synthetic_attribute);
+ ADD_CAPABILITY(can_get_owned_monitor_info);
+ ADD_CAPABILITY(can_get_current_contended_monitor);
+ ADD_CAPABILITY(can_get_monitor_info);
+ ADD_CAPABILITY(can_pop_frame);
+ ADD_CAPABILITY(can_redefine_classes);
+ ADD_CAPABILITY(can_signal_thread);
+ ADD_CAPABILITY(can_get_source_file_name);
+ ADD_CAPABILITY(can_get_line_numbers);
+ ADD_CAPABILITY(can_get_source_debug_extension);
+ ADD_CAPABILITY(can_access_local_variables);
+ ADD_CAPABILITY(can_maintain_original_method_order);
+ ADD_CAPABILITY(can_generate_single_step_events);
+ ADD_CAPABILITY(can_generate_exception_events);
+ ADD_CAPABILITY(can_generate_frame_pop_events);
+ ADD_CAPABILITY(can_generate_breakpoint_events);
+ ADD_CAPABILITY(can_suspend);
+ ADD_CAPABILITY(can_redefine_any_class);
+ ADD_CAPABILITY(can_get_current_thread_cpu_time);
+ ADD_CAPABILITY(can_get_thread_cpu_time);
+ ADD_CAPABILITY(can_generate_method_entry_events);
+ ADD_CAPABILITY(can_generate_method_exit_events);
+ ADD_CAPABILITY(can_generate_all_class_hook_events);
+ ADD_CAPABILITY(can_generate_compiled_method_load_events);
+ ADD_CAPABILITY(can_generate_monitor_events);
+ ADD_CAPABILITY(can_generate_vm_object_alloc_events);
+ ADD_CAPABILITY(can_generate_native_method_bind_events);
+ ADD_CAPABILITY(can_generate_garbage_collection_events);
+ ADD_CAPABILITY(can_generate_object_free_events);
+ ADD_CAPABILITY(can_force_early_return);
+ ADD_CAPABILITY(can_get_owned_monitor_stack_depth_info);
+ ADD_CAPABILITY(can_get_constant_pool);
+ ADD_CAPABILITY(can_set_native_method_prefix);
+ ADD_CAPABILITY(can_retransform_classes);
+ ADD_CAPABILITY(can_retransform_any_class);
+ ADD_CAPABILITY(can_generate_resource_exhaustion_heap_events);
+ ADD_CAPABILITY(can_generate_resource_exhaustion_threads_events);
+#undef ADD_CAPABILITY
+ gEventHandler.HandleChangedCapabilities(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+ changed,
+ /*added*/true);
+ return ret;
+ }
+
+ static jvmtiError RelinquishCapabilities(jvmtiEnv* env,
+ const jvmtiCapabilities* capabilities_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_NON_NULL(capabilities_ptr);
+ ArtJvmTiEnv* art_env = reinterpret_cast<ArtJvmTiEnv*>(env);
+ jvmtiCapabilities changed = {};
+#define DEL_CAPABILITY(e) \
+ do { \
+ if (capabilities_ptr->e == 1) { \
+ if (art_env->capabilities.e == 1) { \
+ art_env->capabilities.e = 0;\
+ changed.e = 1; \
+ } \
+ } \
+ } while (false)
+
+ DEL_CAPABILITY(can_tag_objects);
+ DEL_CAPABILITY(can_generate_field_modification_events);
+ DEL_CAPABILITY(can_generate_field_access_events);
+ DEL_CAPABILITY(can_get_bytecodes);
+ DEL_CAPABILITY(can_get_synthetic_attribute);
+ DEL_CAPABILITY(can_get_owned_monitor_info);
+ DEL_CAPABILITY(can_get_current_contended_monitor);
+ DEL_CAPABILITY(can_get_monitor_info);
+ DEL_CAPABILITY(can_pop_frame);
+ DEL_CAPABILITY(can_redefine_classes);
+ DEL_CAPABILITY(can_signal_thread);
+ DEL_CAPABILITY(can_get_source_file_name);
+ DEL_CAPABILITY(can_get_line_numbers);
+ DEL_CAPABILITY(can_get_source_debug_extension);
+ DEL_CAPABILITY(can_access_local_variables);
+ DEL_CAPABILITY(can_maintain_original_method_order);
+ DEL_CAPABILITY(can_generate_single_step_events);
+ DEL_CAPABILITY(can_generate_exception_events);
+ DEL_CAPABILITY(can_generate_frame_pop_events);
+ DEL_CAPABILITY(can_generate_breakpoint_events);
+ DEL_CAPABILITY(can_suspend);
+ DEL_CAPABILITY(can_redefine_any_class);
+ DEL_CAPABILITY(can_get_current_thread_cpu_time);
+ DEL_CAPABILITY(can_get_thread_cpu_time);
+ DEL_CAPABILITY(can_generate_method_entry_events);
+ DEL_CAPABILITY(can_generate_method_exit_events);
+ DEL_CAPABILITY(can_generate_all_class_hook_events);
+ DEL_CAPABILITY(can_generate_compiled_method_load_events);
+ DEL_CAPABILITY(can_generate_monitor_events);
+ DEL_CAPABILITY(can_generate_vm_object_alloc_events);
+ DEL_CAPABILITY(can_generate_native_method_bind_events);
+ DEL_CAPABILITY(can_generate_garbage_collection_events);
+ DEL_CAPABILITY(can_generate_object_free_events);
+ DEL_CAPABILITY(can_force_early_return);
+ DEL_CAPABILITY(can_get_owned_monitor_stack_depth_info);
+ DEL_CAPABILITY(can_get_constant_pool);
+ DEL_CAPABILITY(can_set_native_method_prefix);
+ DEL_CAPABILITY(can_retransform_classes);
+ DEL_CAPABILITY(can_retransform_any_class);
+ DEL_CAPABILITY(can_generate_resource_exhaustion_heap_events);
+ DEL_CAPABILITY(can_generate_resource_exhaustion_threads_events);
+#undef DEL_CAPABILITY
+ gEventHandler.HandleChangedCapabilities(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+ changed,
+ /*added*/false);
+ return OK;
+ }
+
+ static jvmtiError GetCapabilities(jvmtiEnv* env, jvmtiCapabilities* capabilities_ptr) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_NON_NULL(capabilities_ptr);
+ ArtJvmTiEnv* artenv = reinterpret_cast<ArtJvmTiEnv*>(env);
+ *capabilities_ptr = artenv->capabilities;
+ return OK;
+ }
+
+ static jvmtiError GetCurrentThreadCpuTimerInfo(jvmtiEnv* env,
+ jvmtiTimerInfo* info_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetCurrentThreadCpuTime(jvmtiEnv* env, jlong* nanos_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetThreadCpuTimerInfo(jvmtiEnv* env,
+ jvmtiTimerInfo* info_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_thread_cpu_time);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetThreadCpuTime(jvmtiEnv* env,
+ jthread thread ATTRIBUTE_UNUSED,
+ jlong* nanos_ptr ATTRIBUTE_UNUSED) {
+ ENSURE_VALID_ENV(env);
+ ENSURE_HAS_CAP(env, can_get_thread_cpu_time);
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ static jvmtiError GetTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr) {
+ ENSURE_VALID_ENV(env);
+ return TimerUtil::GetTimerInfo(env, info_ptr);
+ }
+
+ static jvmtiError GetTime(jvmtiEnv* env, jlong* nanos_ptr) {
+ ENSURE_VALID_ENV(env);
+ return TimerUtil::GetTime(env, nanos_ptr);
+ }
+
+ static jvmtiError GetAvailableProcessors(jvmtiEnv* env, jint* processor_count_ptr) {
+ ENSURE_VALID_ENV(env);
+ return TimerUtil::GetAvailableProcessors(env, processor_count_ptr);
+ }
+
+ static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment) {
+ ENSURE_VALID_ENV(env);
+ return SearchUtil::AddToBootstrapClassLoaderSearch(env, segment);
+ }
+
+ static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment) {
+ ENSURE_VALID_ENV(env);
+ return SearchUtil::AddToSystemClassLoaderSearch(env, segment);
+ }
+
+ static jvmtiError GetSystemProperties(jvmtiEnv* env, jint* count_ptr, char*** property_ptr) {
+ ENSURE_VALID_ENV(env);
+ return PropertiesUtil::GetSystemProperties(env, count_ptr, property_ptr);
+ }
+
+ static jvmtiError GetSystemProperty(jvmtiEnv* env, const char* property, char** value_ptr) {
+ ENSURE_VALID_ENV(env);
+ return PropertiesUtil::GetSystemProperty(env, property, value_ptr);
+ }
+
+ static jvmtiError SetSystemProperty(jvmtiEnv* env, const char* property, const char* value) {
+ ENSURE_VALID_ENV(env);
+ return PropertiesUtil::SetSystemProperty(env, property, value);
+ }
+
+ static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr) {
+ ENSURE_VALID_ENV(env);
+ return PhaseUtil::GetPhase(env, phase_ptr);
+ }
+
+ static jvmtiError DisposeEnvironment(jvmtiEnv* env) {
+ ENSURE_VALID_ENV(env);
+ ArtJvmTiEnv* tienv = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+ gEventHandler.RemoveArtJvmTiEnv(tienv);
+ art::Runtime::Current()->RemoveSystemWeakHolder(tienv->object_tag_table.get());
+ ThreadUtil::RemoveEnvironment(tienv);
+ delete tienv;
+ return OK;
+ }
+
+ static jvmtiError SetEnvironmentLocalStorage(jvmtiEnv* env, const void* data) {
+ ENSURE_VALID_ENV(env);
+ reinterpret_cast<ArtJvmTiEnv*>(env)->local_data = const_cast<void*>(data);
+ return OK;
+ }
+
+ static jvmtiError GetEnvironmentLocalStorage(jvmtiEnv* env, void** data_ptr) {
+ ENSURE_VALID_ENV(env);
+ *data_ptr = reinterpret_cast<ArtJvmTiEnv*>(env)->local_data;
+ return OK;
+ }
+
+ static jvmtiError GetVersionNumber(jvmtiEnv* env, jint* version_ptr) {
+ ENSURE_VALID_ENV(env);
+ *version_ptr = JVMTI_VERSION;
+ return OK;
+ }
+
+ static jvmtiError GetErrorName(jvmtiEnv* env, jvmtiError error, char** name_ptr) {
+ ENSURE_NON_NULL(name_ptr);
+ auto copy_fn = [&](const char* name_cstr) {
+ jvmtiError res;
+ JvmtiUniquePtr<char[]> copy = CopyString(env, name_cstr, &res);
+ if (copy == nullptr) {
+ *name_ptr = nullptr;
+ return res;
+ } else {
+ *name_ptr = copy.release();
+ return OK;
+ }
+ };
+ switch (error) {
+#define ERROR_CASE(e) case (JVMTI_ERROR_ ## e) : \
+ return copy_fn("JVMTI_ERROR_"#e);
+ ERROR_CASE(NONE);
+ ERROR_CASE(INVALID_THREAD);
+ ERROR_CASE(INVALID_THREAD_GROUP);
+ ERROR_CASE(INVALID_PRIORITY);
+ ERROR_CASE(THREAD_NOT_SUSPENDED);
+ ERROR_CASE(THREAD_SUSPENDED);
+ ERROR_CASE(THREAD_NOT_ALIVE);
+ ERROR_CASE(INVALID_OBJECT);
+ ERROR_CASE(INVALID_CLASS);
+ ERROR_CASE(CLASS_NOT_PREPARED);
+ ERROR_CASE(INVALID_METHODID);
+ ERROR_CASE(INVALID_LOCATION);
+ ERROR_CASE(INVALID_FIELDID);
+ ERROR_CASE(NO_MORE_FRAMES);
+ ERROR_CASE(OPAQUE_FRAME);
+ ERROR_CASE(TYPE_MISMATCH);
+ ERROR_CASE(INVALID_SLOT);
+ ERROR_CASE(DUPLICATE);
+ ERROR_CASE(NOT_FOUND);
+ ERROR_CASE(INVALID_MONITOR);
+ ERROR_CASE(NOT_MONITOR_OWNER);
+ ERROR_CASE(INTERRUPT);
+ ERROR_CASE(INVALID_CLASS_FORMAT);
+ ERROR_CASE(CIRCULAR_CLASS_DEFINITION);
+ ERROR_CASE(FAILS_VERIFICATION);
+ ERROR_CASE(UNSUPPORTED_REDEFINITION_METHOD_ADDED);
+ ERROR_CASE(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED);
+ ERROR_CASE(INVALID_TYPESTATE);
+ ERROR_CASE(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED);
+ ERROR_CASE(UNSUPPORTED_REDEFINITION_METHOD_DELETED);
+ ERROR_CASE(UNSUPPORTED_VERSION);
+ ERROR_CASE(NAMES_DONT_MATCH);
+ ERROR_CASE(UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED);
+ ERROR_CASE(UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED);
+ ERROR_CASE(UNMODIFIABLE_CLASS);
+ ERROR_CASE(NOT_AVAILABLE);
+ ERROR_CASE(MUST_POSSESS_CAPABILITY);
+ ERROR_CASE(NULL_POINTER);
+ ERROR_CASE(ABSENT_INFORMATION);
+ ERROR_CASE(INVALID_EVENT_TYPE);
+ ERROR_CASE(ILLEGAL_ARGUMENT);
+ ERROR_CASE(NATIVE_METHOD);
+ ERROR_CASE(CLASS_LOADER_UNSUPPORTED);
+ ERROR_CASE(OUT_OF_MEMORY);
+ ERROR_CASE(ACCESS_DENIED);
+ ERROR_CASE(WRONG_PHASE);
+ ERROR_CASE(INTERNAL);
+ ERROR_CASE(UNATTACHED_THREAD);
+ ERROR_CASE(INVALID_ENVIRONMENT);
+#undef ERROR_CASE
+ }
+
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+
+ static jvmtiError SetVerboseFlag(jvmtiEnv* env,
+ jvmtiVerboseFlag flag,
+ jboolean value) {
+ ENSURE_VALID_ENV(env);
+ if (flag == jvmtiVerboseFlag::JVMTI_VERBOSE_OTHER) {
+ // OTHER is special, as it's 0, so can't do a bit check.
+ bool val = (value == JNI_TRUE) ? true : false;
+
+ art::gLogVerbosity.collector = val;
+ art::gLogVerbosity.compiler = val;
+ art::gLogVerbosity.deopt = val;
+ art::gLogVerbosity.heap = val;
+ art::gLogVerbosity.jdwp = val;
+ art::gLogVerbosity.jit = val;
+ art::gLogVerbosity.monitor = val;
+ art::gLogVerbosity.oat = val;
+ art::gLogVerbosity.profiler = val;
+ art::gLogVerbosity.signals = val;
+ art::gLogVerbosity.simulator = val;
+ art::gLogVerbosity.startup = val;
+ art::gLogVerbosity.third_party_jni = val;
+ art::gLogVerbosity.threads = val;
+ art::gLogVerbosity.verifier = val;
+ art::gLogVerbosity.image = val;
+
+ // Note: can't switch systrace_lock_logging. That requires changing entrypoints.
+
+ art::gLogVerbosity.agents = val;
+ } else {
+ // Spec isn't clear whether "flag" is a mask or supposed to be single. We implement the mask
+ // semantics.
+ constexpr std::underlying_type<jvmtiVerboseFlag>::type kMask =
+ jvmtiVerboseFlag::JVMTI_VERBOSE_GC |
+ jvmtiVerboseFlag::JVMTI_VERBOSE_CLASS |
+ jvmtiVerboseFlag::JVMTI_VERBOSE_JNI;
+ if ((flag & ~kMask) != 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+
+ bool val = (value == JNI_TRUE) ? true : false;
+
+ if ((flag & jvmtiVerboseFlag::JVMTI_VERBOSE_GC) != 0) {
+ art::gLogVerbosity.gc = val;
+ }
+
+ if ((flag & jvmtiVerboseFlag::JVMTI_VERBOSE_CLASS) != 0) {
+ art::gLogVerbosity.class_linker = val;
+ }
+
+ if ((flag & jvmtiVerboseFlag::JVMTI_VERBOSE_JNI) != 0) {
+ art::gLogVerbosity.jni = val;
+ }
+ }
+
+ return ERR(NONE);
+ }
+
+ static jvmtiError GetJLocationFormat(jvmtiEnv* env, jvmtiJlocationFormat* format_ptr) {
+ ENSURE_VALID_ENV(env);
+ // Report BCI as jlocation format. We report dex bytecode indices.
+ if (format_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ *format_ptr = jvmtiJlocationFormat::JVMTI_JLOCATION_JVMBCI;
+ return ERR(NONE);
+ }
+};
+
+static bool IsJvmtiVersion(jint version) {
+ return version == JVMTI_VERSION_1 ||
+ version == JVMTI_VERSION_1_0 ||
+ version == JVMTI_VERSION_1_1 ||
+ version == JVMTI_VERSION_1_2 ||
+ version == JVMTI_VERSION;
+}
+
+extern const jvmtiInterface_1 gJvmtiInterface;
+
+ArtJvmTiEnv::ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler)
+ : art_vm(runtime),
+ local_data(nullptr),
+ capabilities() {
+ object_tag_table = std::unique_ptr<ObjectTagTable>(new ObjectTagTable(event_handler, this));
+ functions = &gJvmtiInterface;
+}
+
+// Creates a jvmtiEnv and returns it with the art::ti::Env that is associated with it. new_art_ti
+// is a pointer to the uninitialized memory for an art::ti::Env.
+static void CreateArtJvmTiEnv(art::JavaVMExt* vm, /*out*/void** new_jvmtiEnv) {
+ struct ArtJvmTiEnv* env = new ArtJvmTiEnv(vm, &gEventHandler);
+ *new_jvmtiEnv = env;
+
+ gEventHandler.RegisterArtJvmTiEnv(env);
+
+ art::Runtime::Current()->AddSystemWeakHolder(
+ ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get());
+}
+
+// A hook that the runtime uses to allow plugins to handle GetEnv calls. It returns true and
+// places the return value in 'env' if this library can handle the GetEnv request. Otherwise
+// returns false and does not modify the 'env' pointer.
+static jint GetEnvHandler(art::JavaVMExt* vm, /*out*/void** env, jint version) {
+ if (IsJvmtiVersion(version)) {
+ CreateArtJvmTiEnv(vm, env);
+ return JNI_OK;
+ } else {
+ printf("version 0x%x is not valid!", version);
+ return JNI_EVERSION;
+ }
+}
+
+// The plugin initialization function. This adds the jvmti environment.
+extern "C" bool ArtPlugin_Initialize() {
+ art::Runtime* runtime = art::Runtime::Current();
+
+ if (runtime->IsStarted()) {
+ PhaseUtil::SetToLive();
+ } else {
+ PhaseUtil::SetToOnLoad();
+ }
+ PhaseUtil::Register(&gEventHandler);
+ ThreadUtil::Register(&gEventHandler);
+ ClassUtil::Register(&gEventHandler);
+ DumpUtil::Register(&gEventHandler);
+ MethodUtil::Register(&gEventHandler);
+ SearchUtil::Register();
+ HeapUtil::Register();
+
+ runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
+
+ return true;
+}
+
+extern "C" bool ArtPlugin_Deinitialize() {
+ gEventHandler.Shutdown();
+ PhaseUtil::Unregister();
+ ThreadUtil::Unregister();
+ ClassUtil::Unregister();
+ DumpUtil::Unregister();
+ MethodUtil::Unregister();
+ SearchUtil::Unregister();
+ HeapUtil::Unregister();
+
+ return true;
+}
+
+// The actual struct holding all of the entrypoints into the jvmti interface.
+const jvmtiInterface_1 gJvmtiInterface = {
+ nullptr, // reserved1
+ JvmtiFunctions::SetEventNotificationMode,
+ nullptr, // reserved3
+ JvmtiFunctions::GetAllThreads,
+ JvmtiFunctions::SuspendThread,
+ JvmtiFunctions::ResumeThread,
+ JvmtiFunctions::StopThread,
+ JvmtiFunctions::InterruptThread,
+ JvmtiFunctions::GetThreadInfo,
+ JvmtiFunctions::GetOwnedMonitorInfo, // 10
+ JvmtiFunctions::GetCurrentContendedMonitor,
+ JvmtiFunctions::RunAgentThread,
+ JvmtiFunctions::GetTopThreadGroups,
+ JvmtiFunctions::GetThreadGroupInfo,
+ JvmtiFunctions::GetThreadGroupChildren,
+ JvmtiFunctions::GetFrameCount,
+ JvmtiFunctions::GetThreadState,
+ JvmtiFunctions::GetCurrentThread,
+ JvmtiFunctions::GetFrameLocation,
+ JvmtiFunctions::NotifyFramePop, // 20
+ JvmtiFunctions::GetLocalObject,
+ JvmtiFunctions::GetLocalInt,
+ JvmtiFunctions::GetLocalLong,
+ JvmtiFunctions::GetLocalFloat,
+ JvmtiFunctions::GetLocalDouble,
+ JvmtiFunctions::SetLocalObject,
+ JvmtiFunctions::SetLocalInt,
+ JvmtiFunctions::SetLocalLong,
+ JvmtiFunctions::SetLocalFloat,
+ JvmtiFunctions::SetLocalDouble, // 30
+ JvmtiFunctions::CreateRawMonitor,
+ JvmtiFunctions::DestroyRawMonitor,
+ JvmtiFunctions::RawMonitorEnter,
+ JvmtiFunctions::RawMonitorExit,
+ JvmtiFunctions::RawMonitorWait,
+ JvmtiFunctions::RawMonitorNotify,
+ JvmtiFunctions::RawMonitorNotifyAll,
+ JvmtiFunctions::SetBreakpoint,
+ JvmtiFunctions::ClearBreakpoint,
+ nullptr, // reserved40
+ JvmtiFunctions::SetFieldAccessWatch,
+ JvmtiFunctions::ClearFieldAccessWatch,
+ JvmtiFunctions::SetFieldModificationWatch,
+ JvmtiFunctions::ClearFieldModificationWatch,
+ JvmtiFunctions::IsModifiableClass,
+ JvmtiFunctions::Allocate,
+ JvmtiFunctions::Deallocate,
+ JvmtiFunctions::GetClassSignature,
+ JvmtiFunctions::GetClassStatus,
+ JvmtiFunctions::GetSourceFileName, // 50
+ JvmtiFunctions::GetClassModifiers,
+ JvmtiFunctions::GetClassMethods,
+ JvmtiFunctions::GetClassFields,
+ JvmtiFunctions::GetImplementedInterfaces,
+ JvmtiFunctions::IsInterface,
+ JvmtiFunctions::IsArrayClass,
+ JvmtiFunctions::GetClassLoader,
+ JvmtiFunctions::GetObjectHashCode,
+ JvmtiFunctions::GetObjectMonitorUsage,
+ JvmtiFunctions::GetFieldName, // 60
+ JvmtiFunctions::GetFieldDeclaringClass,
+ JvmtiFunctions::GetFieldModifiers,
+ JvmtiFunctions::IsFieldSynthetic,
+ JvmtiFunctions::GetMethodName,
+ JvmtiFunctions::GetMethodDeclaringClass,
+ JvmtiFunctions::GetMethodModifiers,
+ nullptr, // reserved67
+ JvmtiFunctions::GetMaxLocals,
+ JvmtiFunctions::GetArgumentsSize,
+ JvmtiFunctions::GetLineNumberTable, // 70
+ JvmtiFunctions::GetMethodLocation,
+ JvmtiFunctions::GetLocalVariableTable,
+ JvmtiFunctions::SetNativeMethodPrefix,
+ JvmtiFunctions::SetNativeMethodPrefixes,
+ JvmtiFunctions::GetBytecodes,
+ JvmtiFunctions::IsMethodNative,
+ JvmtiFunctions::IsMethodSynthetic,
+ JvmtiFunctions::GetLoadedClasses,
+ JvmtiFunctions::GetClassLoaderClasses,
+ JvmtiFunctions::PopFrame, // 80
+ JvmtiFunctions::ForceEarlyReturnObject,
+ JvmtiFunctions::ForceEarlyReturnInt,
+ JvmtiFunctions::ForceEarlyReturnLong,
+ JvmtiFunctions::ForceEarlyReturnFloat,
+ JvmtiFunctions::ForceEarlyReturnDouble,
+ JvmtiFunctions::ForceEarlyReturnVoid,
+ JvmtiFunctions::RedefineClasses,
+ JvmtiFunctions::GetVersionNumber,
+ JvmtiFunctions::GetCapabilities,
+ JvmtiFunctions::GetSourceDebugExtension, // 90
+ JvmtiFunctions::IsMethodObsolete,
+ JvmtiFunctions::SuspendThreadList,
+ JvmtiFunctions::ResumeThreadList,
+ nullptr, // reserved94
+ nullptr, // reserved95
+ nullptr, // reserved96
+ nullptr, // reserved97
+ nullptr, // reserved98
+ nullptr, // reserved99
+ JvmtiFunctions::GetAllStackTraces, // 100
+ JvmtiFunctions::GetThreadListStackTraces,
+ JvmtiFunctions::GetThreadLocalStorage,
+ JvmtiFunctions::SetThreadLocalStorage,
+ JvmtiFunctions::GetStackTrace,
+ nullptr, // reserved105
+ JvmtiFunctions::GetTag,
+ JvmtiFunctions::SetTag,
+ JvmtiFunctions::ForceGarbageCollection,
+ JvmtiFunctions::IterateOverObjectsReachableFromObject,
+ JvmtiFunctions::IterateOverReachableObjects, // 110
+ JvmtiFunctions::IterateOverHeap,
+ JvmtiFunctions::IterateOverInstancesOfClass,
+ nullptr, // reserved113
+ JvmtiFunctions::GetObjectsWithTags,
+ JvmtiFunctions::FollowReferences,
+ JvmtiFunctions::IterateThroughHeap,
+ nullptr, // reserved117
+ nullptr, // reserved118
+ nullptr, // reserved119
+ JvmtiFunctions::SetJNIFunctionTable, // 120
+ JvmtiFunctions::GetJNIFunctionTable,
+ JvmtiFunctions::SetEventCallbacks,
+ JvmtiFunctions::GenerateEvents,
+ JvmtiFunctions::GetExtensionFunctions,
+ JvmtiFunctions::GetExtensionEvents,
+ JvmtiFunctions::SetExtensionEventCallback,
+ JvmtiFunctions::DisposeEnvironment,
+ JvmtiFunctions::GetErrorName,
+ JvmtiFunctions::GetJLocationFormat,
+ JvmtiFunctions::GetSystemProperties, // 130
+ JvmtiFunctions::GetSystemProperty,
+ JvmtiFunctions::SetSystemProperty,
+ JvmtiFunctions::GetPhase,
+ JvmtiFunctions::GetCurrentThreadCpuTimerInfo,
+ JvmtiFunctions::GetCurrentThreadCpuTime,
+ JvmtiFunctions::GetThreadCpuTimerInfo,
+ JvmtiFunctions::GetThreadCpuTime,
+ JvmtiFunctions::GetTimerInfo,
+ JvmtiFunctions::GetTime,
+ JvmtiFunctions::GetPotentialCapabilities, // 140
+ nullptr, // reserved141
+ JvmtiFunctions::AddCapabilities,
+ JvmtiFunctions::RelinquishCapabilities,
+ JvmtiFunctions::GetAvailableProcessors,
+ JvmtiFunctions::GetClassVersionNumbers,
+ JvmtiFunctions::GetConstantPool,
+ JvmtiFunctions::GetEnvironmentLocalStorage,
+ JvmtiFunctions::SetEnvironmentLocalStorage,
+ JvmtiFunctions::AddToBootstrapClassLoaderSearch,
+ JvmtiFunctions::SetVerboseFlag, // 150
+ JvmtiFunctions::AddToSystemClassLoaderSearch,
+ JvmtiFunctions::RetransformClasses,
+ JvmtiFunctions::GetOwnedMonitorStackDepthInfo,
+ JvmtiFunctions::GetObjectSize,
+ JvmtiFunctions::GetLocalInstance,
+};
+
+}; // namespace openjdkjvmti
diff --git a/openjdkjvmti/README.md b/openjdkjvmti/README.md
new file mode 100644
index 0000000..b8bab57
--- /dev/null
+++ b/openjdkjvmti/README.md
@@ -0,0 +1,7 @@
+openjdkjvmti plugin
+====
+
+This is a partial implementation of the JVMTI v1.2 interface for the android
+runtime as a plugin. This allows the use of agents that can modify the running
+state of the program by modifying dex files in memory and performing other
+operations on the global runtime state.
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
new file mode 100644
index 0000000..12f4cab
--- /dev/null
+++ b/openjdkjvmti/art_jvmti.h
@@ -0,0 +1,265 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_ART_JVMTI_H_
+#define ART_OPENJDKJVMTI_ART_JVMTI_H_
+
+#include <memory>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <jni.h>
+
+#include "base/casts.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strlcpy.h"
+#include "events.h"
+#include "java_vm_ext.h"
+#include "jni_env_ext.h"
+#include "jvmti.h"
+#include "ti_breakpoint.h"
+
+namespace art {
+class ArtField;
+class ArtMethod;
+} // namespace art
+
+namespace openjdkjvmti {
+
+class ObjectTagTable;
+
+// A structure that is a jvmtiEnv with additional information for the runtime.
+struct ArtJvmTiEnv : public jvmtiEnv {
+ art::JavaVMExt* art_vm;
+ void* local_data;
+ jvmtiCapabilities capabilities;
+
+ EventMasks event_masks;
+ std::unique_ptr<jvmtiEventCallbacks> event_callbacks;
+
+ // Tagging is specific to the jvmtiEnv.
+ std::unique_ptr<ObjectTagTable> object_tag_table;
+
+ // Set of watched fields is unique to each jvmtiEnv.
+ // TODO It might be good to follow the RI and only let one jvmtiEnv ever have the watch caps so
+ // we can record this on the field directly. We could do this either using free access-flag bits
+ // or by putting a list in the ClassExt of a field's DeclaringClass.
+ // TODO Maybe just have an extension to let one put a watch on every field, that would probably be
+ // good enough maybe since you probably want either a few or all/almost all of them.
+ std::unordered_set<art::ArtField*> access_watched_fields;
+ std::unordered_set<art::ArtField*> modify_watched_fields;
+
+ // Set of breakpoints is unique to each jvmtiEnv.
+ std::unordered_set<Breakpoint> breakpoints;
+
+ ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler);
+
+ static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) {
+ return art::down_cast<ArtJvmTiEnv*>(env);
+ }
+};
+
+// Macro and constexpr to make error values less annoying to write.
+#define ERR(e) JVMTI_ERROR_ ## e
+static constexpr jvmtiError OK = JVMTI_ERROR_NONE;
+
+// Special error code for unimplemented functions in JVMTI
+static constexpr jvmtiError ERR(NOT_IMPLEMENTED) = JVMTI_ERROR_NOT_AVAILABLE;
+
+static inline JNIEnv* GetJniEnv(jvmtiEnv* env) {
+ JNIEnv* ret_value = nullptr;
+ jint res = reinterpret_cast<ArtJvmTiEnv*>(env)->art_vm->GetEnv(
+ reinterpret_cast<void**>(&ret_value), JNI_VERSION_1_1);
+ if (res != JNI_OK) {
+ return nullptr;
+ }
+ return ret_value;
+}
+
+template <typename T>
+class JvmtiDeleter {
+ public:
+ JvmtiDeleter() : env_(nullptr) {}
+ explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
+
+ JvmtiDeleter(JvmtiDeleter&) = default;
+ JvmtiDeleter(JvmtiDeleter&&) = default;
+ JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
+
+ void operator()(T* ptr) const {
+ CHECK(env_ != nullptr);
+ jvmtiError ret = env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+ CHECK(ret == ERR(NONE));
+ }
+
+ private:
+ mutable jvmtiEnv* env_;
+};
+
+template <typename T>
+class JvmtiDeleter<T[]> {
+ public:
+ JvmtiDeleter() : env_(nullptr) {}
+ explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
+
+ JvmtiDeleter(JvmtiDeleter&) = default;
+ JvmtiDeleter(JvmtiDeleter&&) = default;
+ JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
+
+ template <typename U>
+ void operator()(U* ptr) const {
+ CHECK(env_ != nullptr);
+ jvmtiError ret = env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+ CHECK(ret == ERR(NONE));
+ }
+
+ private:
+ mutable jvmtiEnv* env_;
+};
+
+template <typename T>
+using JvmtiUniquePtr = std::unique_ptr<T, JvmtiDeleter<T>>;
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) {
+ return JvmtiUniquePtr<T>(mem, JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> MakeJvmtiUniquePtr(jvmtiEnv* env, unsigned char* mem) {
+ return JvmtiUniquePtr<T>(reinterpret_cast<T*>(mem), JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> AllocJvmtiUniquePtr(jvmtiEnv* env, jvmtiError* error) {
+ unsigned char* tmp;
+ *error = env->Allocate(sizeof(T), &tmp);
+ if (*error != ERR(NONE)) {
+ return JvmtiUniquePtr<T>();
+ }
+ return JvmtiUniquePtr<T>(tmp, JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> AllocJvmtiUniquePtr(jvmtiEnv* env,
+ size_t count,
+ jvmtiError* error) {
+ unsigned char* tmp;
+ *error = env->Allocate(sizeof(typename std::remove_extent<T>::type) * count, &tmp);
+ if (*error != ERR(NONE)) {
+ return JvmtiUniquePtr<T>();
+ }
+ return JvmtiUniquePtr<T>(reinterpret_cast<typename std::remove_extent<T>::type*>(tmp),
+ JvmtiDeleter<T>(env));
+}
+
+ALWAYS_INLINE
+static inline jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env,
+ const unsigned char* source,
+ jint len,
+ /*out*/unsigned char** dest) {
+ jvmtiError res = env->Allocate(len, dest);
+ if (res != OK) {
+ return res;
+ }
+ memcpy(reinterpret_cast<void*>(*dest),
+ reinterpret_cast<const void*>(source),
+ len);
+ return OK;
+}
+
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src, jvmtiError* error) {
+ if (src == nullptr) {
+ JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, 0, error);
+ return ret;
+ }
+ size_t len = strlen(src) + 1;
+ JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, len, error);
+ if (ret != nullptr) {
+ strlcpy(ret.get(), src, len);
+ }
+ return ret;
+}
+
+const jvmtiCapabilities kPotentialCapabilities = {
+ .can_tag_objects = 1,
+ .can_generate_field_modification_events = 1,
+ .can_generate_field_access_events = 1,
+ .can_get_bytecodes = 1,
+ .can_get_synthetic_attribute = 1,
+ .can_get_owned_monitor_info = 0,
+ .can_get_current_contended_monitor = 0,
+ .can_get_monitor_info = 0,
+ .can_pop_frame = 0,
+ .can_redefine_classes = 1,
+ .can_signal_thread = 0,
+ .can_get_source_file_name = 1,
+ .can_get_line_numbers = 1,
+ .can_get_source_debug_extension = 1,
+ .can_access_local_variables = 1,
+ .can_maintain_original_method_order = 1,
+ .can_generate_single_step_events = 1,
+ .can_generate_exception_events = 0,
+ .can_generate_frame_pop_events = 0,
+ .can_generate_breakpoint_events = 1,
+ .can_suspend = 1,
+ .can_redefine_any_class = 0,
+ .can_get_current_thread_cpu_time = 0,
+ .can_get_thread_cpu_time = 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,
+ .can_generate_vm_object_alloc_events = 1,
+ .can_generate_native_method_bind_events = 1,
+ .can_generate_garbage_collection_events = 1,
+ .can_generate_object_free_events = 1,
+ .can_force_early_return = 0,
+ .can_get_owned_monitor_stack_depth_info = 0,
+ .can_get_constant_pool = 0,
+ .can_set_native_method_prefix = 0,
+ .can_retransform_classes = 1,
+ .can_retransform_any_class = 0,
+ .can_generate_resource_exhaustion_heap_events = 0,
+ .can_generate_resource_exhaustion_threads_events = 0,
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_ART_JVMTI_H_
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
new file mode 100644
index 0000000..32dba3e
--- /dev/null
+++ b/openjdkjvmti/events-inl.h
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ART_OPENJDKJVMTI_EVENTS_INL_H_
+#define ART_OPENJDKJVMTI_EVENTS_INL_H_
+
+#include <array>
+
+#include "events.h"
+#include "jni_internal.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "ti_breakpoint.h"
+
+#include "art_jvmti.h"
+
+namespace openjdkjvmti {
+
+static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e) {
+ if (UNLIKELY(e == JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
+ if (env->capabilities.can_retransform_classes) {
+ return ArtJvmtiEvent::kClassFileLoadHookRetransformable;
+ } else {
+ return ArtJvmtiEvent::kClassFileLoadHookNonRetransformable;
+ }
+ } else {
+ return static_cast<ArtJvmtiEvent>(e);
+ }
+}
+
+namespace impl {
+
+// Infrastructure to achieve type safety for event dispatch.
+
+#define FORALL_EVENT_TYPES(fn) \
+ fn(VMInit, ArtJvmtiEvent::kVmInit) \
+ fn(VMDeath, ArtJvmtiEvent::kVmDeath) \
+ fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \
+ fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \
+ fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \
+ fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \
+ fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \
+ fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \
+ fn(VMStart, ArtJvmtiEvent::kVmStart) \
+ fn(Exception, ArtJvmtiEvent::kException) \
+ fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \
+ fn(SingleStep, ArtJvmtiEvent::kSingleStep) \
+ fn(FramePop, ArtJvmtiEvent::kFramePop) \
+ fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \
+ fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \
+ fn(FieldModification, ArtJvmtiEvent::kFieldModification) \
+ fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \
+ fn(MethodExit, ArtJvmtiEvent::kMethodExit) \
+ fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \
+ fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \
+ fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \
+ fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \
+ fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \
+ fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \
+ fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \
+ fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \
+ fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \
+ fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \
+ fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \
+ fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \
+ fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \
+ fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc)
+
+template <ArtJvmtiEvent kEvent>
+struct EventFnType {
+};
+
+#define EVENT_FN_TYPE(name, enum_name) \
+template <> \
+struct EventFnType<enum_name> { \
+ using type = decltype(jvmtiEventCallbacks().name); \
+};
+
+FORALL_EVENT_TYPES(EVENT_FN_TYPE)
+
+#undef EVENT_FN_TYPE
+
+template <ArtJvmtiEvent kEvent>
+ALWAYS_INLINE inline typename EventFnType<kEvent>::type GetCallback(ArtJvmTiEnv* env);
+
+#define GET_CALLBACK(name, enum_name) \
+template <> \
+ALWAYS_INLINE inline EventFnType<enum_name>::type GetCallback<enum_name>( \
+ ArtJvmTiEnv* env) { \
+ if (env->event_callbacks == nullptr) { \
+ return nullptr; \
+ } \
+ return env->event_callbacks->name; \
+}
+
+FORALL_EVENT_TYPES(GET_CALLBACK)
+
+#undef GET_CALLBACK
+
+#undef FORALL_EVENT_TYPES
+
+} // namespace impl
+
+// C++ does not allow partial template function specialization. The dispatch for our separated
+// ClassFileLoadHook event types is the same, so use this helper for code deduplication.
+// TODO Locking of some type!
+template <ArtJvmtiEvent kEvent>
+inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread,
+ JNIEnv* jnienv,
+ jclass class_being_redefined,
+ jobject loader,
+ const char* name,
+ jobject protection_domain,
+ jint class_data_len,
+ const unsigned char* class_data,
+ jint* new_class_data_len,
+ unsigned char** new_class_data) const {
+ static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
+ kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event");
+ DCHECK(*new_class_data == nullptr);
+ jint current_len = class_data_len;
+ unsigned char* current_class_data = const_cast<unsigned char*>(class_data);
+ ArtJvmTiEnv* last_env = nullptr;
+ for (ArtJvmTiEnv* env : envs) {
+ if (env == nullptr) {
+ 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);
+ callback(env,
+ jnienv,
+ class_being_redefined,
+ loader,
+ name,
+ protection_domain,
+ current_len,
+ 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.
+ // NB Currently this doesn't matter since all allocations just go to malloc but in the
+ // future we might have jvmtiEnv's keep track of their allocations for leak-checking.
+ if (last_env != nullptr) {
+ last_env->Deallocate(current_class_data);
+ }
+ last_env = env;
+ current_class_data = new_data;
+ current_len = new_len;
+ }
+ }
+ }
+ if (last_env != nullptr) {
+ *new_class_data_len = current_len;
+ *new_class_data = current_class_data;
+ }
+}
+
+// Our goal for DispatchEvent: Do not allow implicit type conversion. Types of ...args must match
+// exactly the argument types of the corresponding Jvmti kEvent function pointer.
+
+template <ArtJvmtiEvent kEvent, typename ...Args>
+inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const {
+ for (ArtJvmTiEnv* env : envs) {
+ if (env != nullptr) {
+ DispatchEvent<kEvent, Args...>(env, thread, args...);
+ }
+ }
+}
+
+// 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...);
+ if (ShouldDispatch<kEvent>(env, thread)) {
+ FnType* callback = impl::GetCallback<kEvent>(env);
+ if (callback != nullptr) {
+ (*callback)(env, args...);
+ }
+ }
+}
+
+// Need to give custom specializations for Breakpoint since it needs to filter out which particular
+// methods/dex_pcs agents get notified on.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* thread,
+ JNIEnv* jnienv,
+ jthread jni_thread,
+ jmethodID jmethod,
+ jlocation location) const {
+ art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod);
+ for (ArtJvmTiEnv* env : envs) {
+ // Search for a breakpoint on this particular method and location.
+ if (env != nullptr &&
+ ShouldDispatch<ArtJvmtiEvent::kBreakpoint>(env, thread) &&
+ env->breakpoints.find({method, location}) != env->breakpoints.end()) {
+ // We temporarily clear any pending exceptions so the event can call back into java code.
+ ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+ jnienv->ExceptionClear();
+ auto callback = impl::GetCallback<ArtJvmtiEvent::kBreakpoint>(env);
+ (*callback)(env, jnienv, jni_thread, jmethod, location);
+ if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+ jnienv->Throw(thr.get());
+ }
+ }
+ }
+}
+
+// Need to give custom specializations for FieldAccess and FieldModification since they need to
+// filter out which particular fields agents want to get notified on.
+// TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This
+// could make the system more performant.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldModification>(art::Thread* thread,
+ JNIEnv* jnienv,
+ jthread jni_thread,
+ jmethodID method,
+ jlocation location,
+ jclass field_klass,
+ jobject object,
+ jfieldID field,
+ char type_char,
+ jvalue val) const {
+ for (ArtJvmTiEnv* env : envs) {
+ if (env != nullptr &&
+ ShouldDispatch<ArtJvmtiEvent::kFieldModification>(env, thread) &&
+ env->modify_watched_fields.find(
+ art::jni::DecodeArtField(field)) != env->modify_watched_fields.end()) {
+ ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+ jnienv->ExceptionClear();
+ auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldModification>(env);
+ (*callback)(env,
+ jnienv,
+ jni_thread,
+ method,
+ location,
+ field_klass,
+ object,
+ field,
+ type_char,
+ val);
+ if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+ jnienv->Throw(thr.get());
+ }
+ }
+ }
+}
+
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldAccess>(art::Thread* thread,
+ JNIEnv* jnienv,
+ jthread jni_thread,
+ jmethodID method,
+ jlocation location,
+ jclass field_klass,
+ jobject object,
+ jfieldID field) const {
+ for (ArtJvmTiEnv* env : envs) {
+ if (env != nullptr &&
+ ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(env, thread) &&
+ env->access_watched_fields.find(
+ art::jni::DecodeArtField(field)) != env->access_watched_fields.end()) {
+ ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+ jnienv->ExceptionClear();
+ auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldAccess>(env);
+ (*callback)(env, jnienv, jni_thread, method, location, field_klass, object, field);
+ if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+ jnienv->Throw(thr.get());
+ }
+ }
+ }
+}
+
+// Need to give a custom specialization for NativeMethodBind since it has to deal with an out
+// variable.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::Thread* thread,
+ JNIEnv* jnienv,
+ jthread jni_thread,
+ jmethodID method,
+ void* cur_method,
+ void** new_method) const {
+ *new_method = cur_method;
+ for (ArtJvmTiEnv* env : envs) {
+ if (env != nullptr && ShouldDispatch<ArtJvmtiEvent::kNativeMethodBind>(env, thread)) {
+ auto callback = impl::GetCallback<ArtJvmtiEvent::kNativeMethodBind>(env);
+ (*callback)(env, jnienv, jni_thread, method, cur_method, new_method);
+ if (*new_method != nullptr) {
+ cur_method = *new_method;
+ }
+ }
+ }
+}
+
+// C++ does not allow partial template function specialization. The dispatch for our separated
+// ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper.
+// The following two DispatchEvent specializations dispatch to it.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+ art::Thread* thread,
+ JNIEnv* jnienv,
+ jclass class_being_redefined,
+ jobject loader,
+ const char* name,
+ jobject protection_domain,
+ jint class_data_len,
+ const unsigned char* class_data,
+ jint* new_class_data_len,
+ unsigned char** new_class_data) const {
+ return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+ thread,
+ jnienv,
+ class_being_redefined,
+ loader,
+ name,
+ protection_domain,
+ class_data_len,
+ class_data,
+ new_class_data_len,
+ new_class_data);
+}
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
+ art::Thread* thread,
+ JNIEnv* jnienv,
+ jclass class_being_redefined,
+ jobject loader,
+ const char* name,
+ jobject protection_domain,
+ jint class_data_len,
+ const unsigned char* class_data,
+ jint* new_class_data_len,
+ unsigned char** new_class_data) const {
+ return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
+ thread,
+ jnienv,
+ class_being_redefined,
+ loader,
+ name,
+ protection_domain,
+ class_data_len,
+ class_data,
+ new_class_data_len,
+ new_class_data);
+}
+
+template <ArtJvmtiEvent kEvent>
+inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env,
+ art::Thread* thread) {
+ bool dispatch = env->event_masks.global_event_mask.Test(kEvent);
+
+ if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) {
+ EventMask* mask = env->event_masks.GetEventMaskOrNull(thread);
+ dispatch = mask != nullptr && mask->Test(kEvent);
+ }
+ return dispatch;
+}
+
+inline void EventHandler::RecalculateGlobalEventMask(ArtJvmtiEvent event) {
+ bool union_value = false;
+ for (const ArtJvmTiEnv* stored_env : envs) {
+ if (stored_env == nullptr) {
+ continue;
+ }
+ union_value |= stored_env->event_masks.global_event_mask.Test(event);
+ union_value |= stored_env->event_masks.unioned_thread_event_mask.Test(event);
+ if (union_value) {
+ break;
+ }
+ }
+ global_mask.Set(event, union_value);
+}
+
+inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env,
+ const jvmtiCapabilities& caps,
+ bool added) {
+ ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable
+ : ArtJvmtiEvent::kClassFileLoadHookRetransformable;
+ return (added && caps.can_access_local_variables == 1) ||
+ (caps.can_retransform_classes == 1 &&
+ IsEventEnabledAnywhere(event) &&
+ env->event_masks.IsEnabledAnywhere(event));
+}
+
+inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env,
+ const jvmtiCapabilities& caps,
+ bool added) {
+ if (UNLIKELY(NeedsEventUpdate(env, caps, added))) {
+ env->event_masks.HandleChangedCapabilities(caps, added);
+ if (caps.can_retransform_classes == 1) {
+ RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable);
+ RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable);
+ }
+ if (added && caps.can_access_local_variables == 1) {
+ HandleLocalAccessCapabilityAdded();
+ }
+ }
+}
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_EVENTS_INL_H_
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
new file mode 100644
index 0000000..2944a45
--- /dev/null
+++ b/openjdkjvmti/events.cc
@@ -0,0 +1,791 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "events-inl.h"
+
+#include "art_field-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 "nativehelper/ScopedLocalRef.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+bool EventMasks::IsEnabledAnywhere(ArtJvmtiEvent event) {
+ return global_event_mask.Test(event) || unioned_thread_event_mask.Test(event);
+}
+
+EventMask& EventMasks::GetEventMask(art::Thread* thread) {
+ if (thread == nullptr) {
+ return global_event_mask;
+ }
+
+ for (auto& pair : thread_event_masks) {
+ const UniqueThread& unique_thread = pair.first;
+ if (unique_thread.first == thread &&
+ unique_thread.second == static_cast<uint32_t>(thread->GetTid())) {
+ return pair.second;
+ }
+ }
+
+ // TODO: Remove old UniqueThread with the same pointer, if exists.
+
+ thread_event_masks.emplace_back(UniqueThread(thread, thread->GetTid()), EventMask());
+ return thread_event_masks.back().second;
+}
+
+EventMask* EventMasks::GetEventMaskOrNull(art::Thread* thread) {
+ if (thread == nullptr) {
+ return &global_event_mask;
+ }
+
+ for (auto& pair : thread_event_masks) {
+ const UniqueThread& unique_thread = pair.first;
+ if (unique_thread.first == thread &&
+ unique_thread.second == static_cast<uint32_t>(thread->GetTid())) {
+ return &pair.second;
+ }
+ }
+
+ return nullptr;
+}
+
+
+void EventMasks::EnableEvent(art::Thread* thread, ArtJvmtiEvent event) {
+ DCHECK(EventMask::EventIsInRange(event));
+ GetEventMask(thread).Set(event);
+ if (thread != nullptr) {
+ unioned_thread_event_mask.Set(event, true);
+ }
+}
+
+void EventMasks::DisableEvent(art::Thread* thread, ArtJvmtiEvent event) {
+ DCHECK(EventMask::EventIsInRange(event));
+ GetEventMask(thread).Set(event, false);
+ if (thread != nullptr) {
+ // Regenerate union for the event.
+ bool union_value = false;
+ for (auto& pair : thread_event_masks) {
+ union_value |= pair.second.Test(event);
+ if (union_value) {
+ break;
+ }
+ }
+ unioned_thread_event_mask.Set(event, union_value);
+ }
+}
+
+void EventMasks::HandleChangedCapabilities(const jvmtiCapabilities& caps, bool caps_added) {
+ if (UNLIKELY(caps.can_retransform_classes == 1)) {
+ // If we are giving this env the retransform classes cap we need to switch all events of
+ // NonTransformable to Transformable and vice versa.
+ ArtJvmtiEvent to_remove = caps_added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable
+ : ArtJvmtiEvent::kClassFileLoadHookRetransformable;
+ ArtJvmtiEvent to_add = caps_added ? ArtJvmtiEvent::kClassFileLoadHookRetransformable
+ : ArtJvmtiEvent::kClassFileLoadHookNonRetransformable;
+ if (global_event_mask.Test(to_remove)) {
+ CHECK(!global_event_mask.Test(to_add));
+ global_event_mask.Set(to_remove, false);
+ global_event_mask.Set(to_add, true);
+ }
+
+ if (unioned_thread_event_mask.Test(to_remove)) {
+ CHECK(!unioned_thread_event_mask.Test(to_add));
+ unioned_thread_event_mask.Set(to_remove, false);
+ unioned_thread_event_mask.Set(to_add, true);
+ }
+ for (auto thread_mask : thread_event_masks) {
+ if (thread_mask.second.Test(to_remove)) {
+ CHECK(!thread_mask.second.Test(to_add));
+ thread_mask.second.Set(to_remove, false);
+ thread_mask.second.Set(to_add, true);
+ }
+ }
+ }
+}
+
+void EventHandler::RegisterArtJvmTiEnv(ArtJvmTiEnv* env) {
+ // Since we never shrink this array we might as well try to fill gaps.
+ auto it = std::find(envs.begin(), envs.end(), nullptr);
+ if (it != envs.end()) {
+ *it = env;
+ } else {
+ envs.push_back(env);
+ }
+}
+
+void EventHandler::RemoveArtJvmTiEnv(ArtJvmTiEnv* env) {
+ // Since we might be currently iterating over the envs list we cannot actually erase elements.
+ // Instead we will simply replace them with 'nullptr' and skip them manually.
+ auto it = std::find(envs.begin(), envs.end(), env);
+ if (it != envs.end()) {
+ *it = nullptr;
+ for (size_t i = static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal);
+ i <= static_cast<size_t>(ArtJvmtiEvent::kMaxEventTypeVal);
+ ++i) {
+ RecalculateGlobalEventMask(static_cast<ArtJvmtiEvent>(i));
+ }
+ }
+}
+
+static bool IsThreadControllable(ArtJvmtiEvent event) {
+ switch (event) {
+ case ArtJvmtiEvent::kVmInit:
+ case ArtJvmtiEvent::kVmStart:
+ case ArtJvmtiEvent::kVmDeath:
+ case ArtJvmtiEvent::kThreadStart:
+ case ArtJvmtiEvent::kCompiledMethodLoad:
+ case ArtJvmtiEvent::kCompiledMethodUnload:
+ case ArtJvmtiEvent::kDynamicCodeGenerated:
+ case ArtJvmtiEvent::kDataDumpRequest:
+ return false;
+
+ default:
+ return true;
+ }
+}
+
+class JvmtiAllocationListener : public art::gc::AllocationListener {
+ public:
+ explicit JvmtiAllocationListener(EventHandler* handler) : handler_(handler) {}
+
+ void ObjectAllocated(art::Thread* self, art::ObjPtr<art::mirror::Object>* obj, size_t byte_count)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK_EQ(self, art::Thread::Current());
+
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kVmObjectAlloc)) {
+ art::StackHandleScope<1> hs(self);
+ auto h = hs.NewHandleWrapper(obj);
+ // jvmtiEventVMObjectAlloc parameters:
+ // jvmtiEnv *jvmti_env,
+ // JNIEnv* jni_env,
+ // jthread thread,
+ // jobject object,
+ // jclass object_klass,
+ // jlong size
+ art::JNIEnvExt* jni_env = self->GetJniEnv();
+
+ jthread thread_peer;
+ if (self->IsStillStarting()) {
+ thread_peer = nullptr;
+ } else {
+ thread_peer = jni_env->AddLocalReference<jthread>(self->GetPeer());
+ }
+
+ ScopedLocalRef<jthread> thread(jni_env, thread_peer);
+ ScopedLocalRef<jobject> object(
+ jni_env, jni_env->AddLocalReference<jobject>(*obj));
+ ScopedLocalRef<jclass> klass(
+ jni_env, jni_env->AddLocalReference<jclass>(obj->Ptr()->GetClass()));
+
+ handler_->DispatchEvent<ArtJvmtiEvent::kVmObjectAlloc>(self,
+ reinterpret_cast<JNIEnv*>(jni_env),
+ thread.get(),
+ object.get(),
+ klass.get(),
+ static_cast<jlong>(byte_count));
+ }
+ }
+
+ private:
+ EventHandler* handler_;
+};
+
+static void SetupObjectAllocationTracking(art::gc::AllocationListener* listener, bool enable) {
+ // We must not hold the mutator lock here, but if we're in FastJNI, for example, we might. For
+ // now, do a workaround: (possibly) acquire and release.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ScopedThreadSuspension sts(soa.Self(), art::ThreadState::kSuspended);
+ if (enable) {
+ art::Runtime::Current()->GetHeap()->SetAllocationListener(listener);
+ } else {
+ art::Runtime::Current()->GetHeap()->RemoveAllocationListener();
+ }
+}
+
+// Report GC pauses (see spec) as GARBAGE_COLLECTION_START and GARBAGE_COLLECTION_END.
+class JvmtiGcPauseListener : public art::gc::GcPauseListener {
+ public:
+ explicit JvmtiGcPauseListener(EventHandler* handler)
+ : handler_(handler),
+ start_enabled_(false),
+ finish_enabled_(false) {}
+
+ void StartPause() OVERRIDE {
+ handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionStart>(nullptr);
+ }
+
+ void EndPause() OVERRIDE {
+ handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionFinish>(nullptr);
+ }
+
+ bool IsEnabled() {
+ return start_enabled_ || finish_enabled_;
+ }
+
+ void SetStartEnabled(bool e) {
+ start_enabled_ = e;
+ }
+
+ void SetFinishEnabled(bool e) {
+ finish_enabled_ = e;
+ }
+
+ private:
+ EventHandler* handler_;
+ bool start_enabled_;
+ bool finish_enabled_;
+};
+
+static void SetupGcPauseTracking(JvmtiGcPauseListener* listener, ArtJvmtiEvent event, bool enable) {
+ bool old_state = listener->IsEnabled();
+
+ if (event == ArtJvmtiEvent::kGarbageCollectionStart) {
+ listener->SetStartEnabled(enable);
+ } else {
+ listener->SetFinishEnabled(enable);
+ }
+
+ bool new_state = listener->IsEnabled();
+
+ if (old_state != new_state) {
+ if (new_state) {
+ art::Runtime::Current()->GetHeap()->SetGcPauseListener(listener);
+ } else {
+ art::Runtime::Current()->GetHeap()->RemoveGcPauseListener();
+ }
+ }
+}
+
+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.
+ void DexPcMoved(art::Thread* self,
+ art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+ art::ArtMethod* method,
+ uint32_t new_dex_pc)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+ DCHECK(!method->IsRuntimeMethod());
+ // Default methods might be copied to multiple classes. We need to get the canonical version of
+ // this method so that we can check for breakpoints correctly.
+ // TODO We should maybe do this on other events to ensure that we are consistent WRT default
+ // methods. This could interact with obsolete methods if we ever let interface redefinition
+ // happen though.
+ method = method->GetCanonicalMethod();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ jmethodID jmethod = art::jni::EncodeArtMethod(method);
+ jlocation location = static_cast<jlocation>(new_dex_pc);
+ // Step event is reported first according to the spec.
+ if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) {
+ RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location);
+ }
+ // Next we do the Breakpoint events. The Dispatch code will filter the individual
+ if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) {
+ RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location);
+ }
+ }
+
+ // Call-back for when we read from a field.
+ void FieldRead(art::Thread* self,
+ art::Handle<art::mirror::Object> this_object,
+ art::ArtMethod* method,
+ uint32_t dex_pc,
+ art::ArtField* field)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+ if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldAccess)) {
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ // DCHECK(!self->IsExceptionPending());
+ ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+ ScopedLocalRef<jobject> fklass(jnienv,
+ AddLocalRef<jobject>(jnienv,
+ field->GetDeclaringClass().Ptr()));
+ RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self,
+ jnienv,
+ art::jni::EncodeArtMethod(method),
+ static_cast<jlocation>(dex_pc),
+ static_cast<jclass>(fklass.get()),
+ this_ref.get(),
+ art::jni::EncodeArtField(field));
+ }
+ }
+
+ void FieldWritten(art::Thread* self,
+ art::Handle<art::mirror::Object> this_object,
+ art::ArtMethod* method,
+ uint32_t dex_pc,
+ art::ArtField* field,
+ art::Handle<art::mirror::Object> new_val)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+ if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) {
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ // DCHECK(!self->IsExceptionPending());
+ ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+ ScopedLocalRef<jobject> fklass(jnienv,
+ AddLocalRef<jobject>(jnienv,
+ field->GetDeclaringClass().Ptr()));
+ ScopedLocalRef<jobject> fval(jnienv, AddLocalRef<jobject>(jnienv, new_val.Get()));
+ jvalue val;
+ val.l = fval.get();
+ RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+ self,
+ jnienv,
+ art::jni::EncodeArtMethod(method),
+ static_cast<jlocation>(dex_pc),
+ static_cast<jclass>(fklass.get()),
+ field->IsStatic() ? nullptr : this_ref.get(),
+ art::jni::EncodeArtField(field),
+ 'L', // type_char
+ val);
+ }
+ }
+
+ // Call-back for when we write into a field.
+ void FieldWritten(art::Thread* self,
+ art::Handle<art::mirror::Object> this_object,
+ art::ArtMethod* method,
+ uint32_t dex_pc,
+ art::ArtField* field,
+ const art::JValue& field_value)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+ if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) {
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ DCHECK(!self->IsExceptionPending());
+ ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+ ScopedLocalRef<jobject> fklass(jnienv,
+ AddLocalRef<jobject>(jnienv,
+ field->GetDeclaringClass().Ptr()));
+ char type_char = art::Primitive::Descriptor(field->GetTypeAsPrimitiveType())[0];
+ jvalue val;
+ // 64bit integer is the largest value in the union so we should be fine simply copying it into
+ // the union.
+ val.j = field_value.GetJ();
+ RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+ self,
+ jnienv,
+ art::jni::EncodeArtMethod(method),
+ static_cast<jlocation>(dex_pc),
+ static_cast<jclass>(fklass.get()),
+ field->IsStatic() ? nullptr : this_ref.get(), // nb static field modification get given
+ // the class as this_object for some
+ // reason.
+ art::jni::EncodeArtField(field),
+ type_char,
+ val);
+ }
+ }
+
+ // 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;
+ case ArtJvmtiEvent::kFieldModification:
+ return art::instrumentation::Instrumentation::kFieldWritten;
+ case ArtJvmtiEvent::kFieldAccess:
+ return art::instrumentation::Instrumentation::kFieldRead;
+ case ArtJvmtiEvent::kBreakpoint:
+ case ArtJvmtiEvent::kSingleStep:
+ return art::instrumentation::Instrumentation::kDexPcMoved;
+ default:
+ LOG(FATAL) << "Unknown event ";
+ return 0;
+ }
+}
+
+static void SetupTraceListener(JvmtiMethodTraceListener* listener,
+ ArtJvmtiEvent event,
+ bool enable) {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
+ 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) {
+ // TODO Depending on the features being used we should be able to avoid deoptimizing everything
+ // like we do here.
+ if (!instr->AreAllMethodsDeoptimized()) {
+ instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true);
+ }
+ instr->AddListener(listener, new_events);
+ } else {
+ instr->RemoveListener(listener, new_events);
+ }
+}
+
+void EventHandler::HandleLocalAccessCapabilityAdded() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
+ art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
+ art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(),
+ art::gc::kGcCauseInstrumentation,
+ art::gc::kCollectorTypeInstrumentation);
+ art::ScopedSuspendAll ssa("Deoptimize everything for local variable access", true);
+ // TODO This should be disabled when there are no environments using it.
+ if (!instr->CanDeoptimize()) {
+ instr->EnableDeoptimization();
+ }
+ // TODO We should be able to support can_access_local_variables without this.
+ instr->DeoptimizeEverything("jvmti-local-variable-access");
+}
+
+// Handle special work for the given event type, if necessary.
+void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
+ switch (event) {
+ case ArtJvmtiEvent::kVmObjectAlloc:
+ SetupObjectAllocationTracking(alloc_listener_.get(), enable);
+ return;
+
+ case ArtJvmtiEvent::kGarbageCollectionStart:
+ case ArtJvmtiEvent::kGarbageCollectionFinish:
+ SetupGcPauseTracking(gc_pause_listener_.get(), event, enable);
+ return;
+
+ case ArtJvmtiEvent::kBreakpoint:
+ case ArtJvmtiEvent::kSingleStep: {
+ ArtJvmtiEvent other = (event == ArtJvmtiEvent::kBreakpoint) ? ArtJvmtiEvent::kSingleStep
+ : ArtJvmtiEvent::kBreakpoint;
+ // We only need to do anything if there isn't already a listener installed/held-on by the
+ // other jvmti event that uses DexPcMoved.
+ if (!IsEventEnabledAnywhere(other)) {
+ SetupTraceListener(method_trace_listener_.get(), event, enable);
+ }
+ return;
+ }
+ case ArtJvmtiEvent::kMethodEntry:
+ case ArtJvmtiEvent::kMethodExit:
+ case ArtJvmtiEvent::kFieldAccess:
+ case ArtJvmtiEvent::kFieldModification:
+ SetupTraceListener(method_trace_listener_.get(), event, enable);
+ return;
+
+ default:
+ break;
+ }
+}
+
+// Checks to see if the env has the capabilities associated with the given event.
+static bool HasAssociatedCapability(ArtJvmTiEnv* env,
+ ArtJvmtiEvent event) {
+ jvmtiCapabilities caps = env->capabilities;
+ switch (event) {
+ case ArtJvmtiEvent::kBreakpoint:
+ return caps.can_generate_breakpoint_events == 1;
+
+ case ArtJvmtiEvent::kCompiledMethodLoad:
+ case ArtJvmtiEvent::kCompiledMethodUnload:
+ return caps.can_generate_compiled_method_load_events == 1;
+
+ case ArtJvmtiEvent::kException:
+ case ArtJvmtiEvent::kExceptionCatch:
+ return caps.can_generate_exception_events == 1;
+
+ case ArtJvmtiEvent::kFieldAccess:
+ return caps.can_generate_field_access_events == 1;
+
+ case ArtJvmtiEvent::kFieldModification:
+ return caps.can_generate_field_modification_events == 1;
+
+ case ArtJvmtiEvent::kFramePop:
+ return caps.can_generate_frame_pop_events == 1;
+
+ case ArtJvmtiEvent::kGarbageCollectionStart:
+ case ArtJvmtiEvent::kGarbageCollectionFinish:
+ return caps.can_generate_garbage_collection_events == 1;
+
+ case ArtJvmtiEvent::kMethodEntry:
+ return caps.can_generate_method_entry_events == 1;
+
+ case ArtJvmtiEvent::kMethodExit:
+ return caps.can_generate_method_exit_events == 1;
+
+ case ArtJvmtiEvent::kMonitorContendedEnter:
+ case ArtJvmtiEvent::kMonitorContendedEntered:
+ case ArtJvmtiEvent::kMonitorWait:
+ case ArtJvmtiEvent::kMonitorWaited:
+ return caps.can_generate_monitor_events == 1;
+
+ case ArtJvmtiEvent::kNativeMethodBind:
+ return caps.can_generate_native_method_bind_events == 1;
+
+ case ArtJvmtiEvent::kObjectFree:
+ return caps.can_generate_object_free_events == 1;
+
+ case ArtJvmtiEvent::kSingleStep:
+ return caps.can_generate_single_step_events == 1;
+
+ case ArtJvmtiEvent::kVmObjectAlloc:
+ return caps.can_generate_vm_object_alloc_events == 1;
+
+ default:
+ return true;
+ }
+}
+
+jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env,
+ art::Thread* thread,
+ ArtJvmtiEvent event,
+ jvmtiEventMode mode) {
+ if (thread != nullptr) {
+ art::ThreadState state = thread->GetState();
+ if (state == art::ThreadState::kStarting ||
+ state == art::ThreadState::kTerminated ||
+ thread->IsStillStarting()) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ if (!IsThreadControllable(event)) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ }
+
+ if (mode != JVMTI_ENABLE && mode != JVMTI_DISABLE) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+
+ if (!EventMask::EventIsInRange(event)) {
+ return ERR(INVALID_EVENT_TYPE);
+ }
+
+ if (!HasAssociatedCapability(env, event)) {
+ return ERR(MUST_POSSESS_CAPABILITY);
+ }
+
+ bool old_state = global_mask.Test(event);
+
+ if (mode == JVMTI_ENABLE) {
+ env->event_masks.EnableEvent(thread, event);
+ global_mask.Set(event);
+ } else {
+ DCHECK_EQ(mode, JVMTI_DISABLE);
+
+ env->event_masks.DisableEvent(thread, event);
+ RecalculateGlobalEventMask(event);
+ }
+
+ bool new_state = global_mask.Test(event);
+
+ // Handle any special work required for the event type.
+ if (new_state != old_state) {
+ HandleEventType(event, mode == JVMTI_ENABLE);
+ }
+
+ 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() {
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
new file mode 100644
index 0000000..3d05fa1
--- /dev/null
+++ b/openjdkjvmti/events.h
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ART_OPENJDKJVMTI_EVENTS_H_
+#define ART_OPENJDKJVMTI_EVENTS_H_
+
+#include <bitset>
+#include <vector>
+
+#include "base/logging.h"
+#include "jvmti.h"
+#include "thread.h"
+
+namespace openjdkjvmti {
+
+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
+enum class ArtJvmtiEvent {
+ kMinEventTypeVal = JVMTI_MIN_EVENT_TYPE_VAL,
+ kVmInit = JVMTI_EVENT_VM_INIT,
+ kVmDeath = JVMTI_EVENT_VM_DEATH,
+ kThreadStart = JVMTI_EVENT_THREAD_START,
+ kThreadEnd = JVMTI_EVENT_THREAD_END,
+ kClassFileLoadHookNonRetransformable = JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+ kClassLoad = JVMTI_EVENT_CLASS_LOAD,
+ kClassPrepare = JVMTI_EVENT_CLASS_PREPARE,
+ kVmStart = JVMTI_EVENT_VM_START,
+ kException = JVMTI_EVENT_EXCEPTION,
+ kExceptionCatch = JVMTI_EVENT_EXCEPTION_CATCH,
+ kSingleStep = JVMTI_EVENT_SINGLE_STEP,
+ kFramePop = JVMTI_EVENT_FRAME_POP,
+ kBreakpoint = JVMTI_EVENT_BREAKPOINT,
+ kFieldAccess = JVMTI_EVENT_FIELD_ACCESS,
+ kFieldModification = JVMTI_EVENT_FIELD_MODIFICATION,
+ kMethodEntry = JVMTI_EVENT_METHOD_ENTRY,
+ kMethodExit = JVMTI_EVENT_METHOD_EXIT,
+ kNativeMethodBind = JVMTI_EVENT_NATIVE_METHOD_BIND,
+ kCompiledMethodLoad = JVMTI_EVENT_COMPILED_METHOD_LOAD,
+ kCompiledMethodUnload = JVMTI_EVENT_COMPILED_METHOD_UNLOAD,
+ kDynamicCodeGenerated = JVMTI_EVENT_DYNAMIC_CODE_GENERATED,
+ kDataDumpRequest = JVMTI_EVENT_DATA_DUMP_REQUEST,
+ kMonitorWait = JVMTI_EVENT_MONITOR_WAIT,
+ kMonitorWaited = JVMTI_EVENT_MONITOR_WAITED,
+ kMonitorContendedEnter = JVMTI_EVENT_MONITOR_CONTENDED_ENTER,
+ kMonitorContendedEntered = JVMTI_EVENT_MONITOR_CONTENDED_ENTERED,
+ kResourceExhausted = JVMTI_EVENT_RESOURCE_EXHAUSTED,
+ kGarbageCollectionStart = JVMTI_EVENT_GARBAGE_COLLECTION_START,
+ kGarbageCollectionFinish = JVMTI_EVENT_GARBAGE_COLLECTION_FINISH,
+ kObjectFree = JVMTI_EVENT_OBJECT_FREE,
+ kVmObjectAlloc = JVMTI_EVENT_VM_OBJECT_ALLOC,
+ kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1,
+ kMaxEventTypeVal = kClassFileLoadHookRetransformable,
+};
+
+// Convert a jvmtiEvent into a ArtJvmtiEvent
+ALWAYS_INLINE static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e);
+
+static inline jvmtiEvent GetJvmtiEvent(ArtJvmtiEvent e) {
+ if (UNLIKELY(e == ArtJvmtiEvent::kClassFileLoadHookRetransformable)) {
+ return JVMTI_EVENT_CLASS_FILE_LOAD_HOOK;
+ } else {
+ return static_cast<jvmtiEvent>(e);
+ }
+}
+
+struct EventMask {
+ static constexpr size_t kEventsSize =
+ static_cast<size_t>(ArtJvmtiEvent::kMaxEventTypeVal) -
+ static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal) + 1;
+ std::bitset<kEventsSize> bit_set;
+
+ static bool EventIsInRange(ArtJvmtiEvent event) {
+ return event >= ArtJvmtiEvent::kMinEventTypeVal && event <= ArtJvmtiEvent::kMaxEventTypeVal;
+ }
+
+ void Set(ArtJvmtiEvent event, bool value = true) {
+ DCHECK(EventIsInRange(event));
+ bit_set.set(static_cast<size_t>(event) - static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal),
+ value);
+ }
+
+ bool Test(ArtJvmtiEvent event) const {
+ DCHECK(EventIsInRange(event));
+ return bit_set.test(
+ static_cast<size_t>(event) - static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal));
+ }
+};
+
+struct EventMasks {
+ // The globally enabled events.
+ EventMask global_event_mask;
+
+ // The per-thread enabled events.
+
+ // It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the
+ // thread id.
+ // Note: We could just use the tid like tracing does.
+ using UniqueThread = std::pair<art::Thread*, uint32_t>;
+ // TODO: Native thread objects are immovable, so we can use them as keys in an (unordered) map,
+ // if necessary.
+ std::vector<std::pair<UniqueThread, EventMask>> thread_event_masks;
+
+ // A union of the per-thread events, for fast-pathing.
+ EventMask unioned_thread_event_mask;
+
+ EventMask& GetEventMask(art::Thread* thread);
+ EventMask* GetEventMaskOrNull(art::Thread* thread);
+ void EnableEvent(art::Thread* thread, ArtJvmtiEvent event);
+ void DisableEvent(art::Thread* thread, ArtJvmtiEvent event);
+ bool IsEnabledAnywhere(ArtJvmtiEvent event);
+ // Make any changes to event masks needed for the given capability changes. If caps_added is true
+ // then caps is all the newly set capabilities of the jvmtiEnv. If it is false then caps is the
+ // set of all capabilities that were removed from the jvmtiEnv.
+ void HandleChangedCapabilities(const jvmtiCapabilities& caps, bool caps_added);
+};
+
+// Helper class for event handling.
+class EventHandler {
+ public:
+ 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);
+
+ // Remove an env.
+ void RemoveArtJvmTiEnv(ArtJvmTiEnv* env);
+
+ bool IsEventEnabledAnywhere(ArtJvmtiEvent event) const {
+ if (!EventMask::EventIsInRange(event)) {
+ return false;
+ }
+ return global_mask.Test(event);
+ }
+
+ jvmtiError SetEvent(ArtJvmTiEnv* env,
+ art::Thread* thread,
+ ArtJvmtiEvent event,
+ jvmtiEventMode mode);
+
+ // Dispatch event to all registered environments.
+ 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
+ inline void DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const;
+
+ // Tell the event handler capabilities were added/lost so it can adjust the sent events.If
+ // caps_added is true then caps is all the newly set capabilities of the jvmtiEnv. If it is false
+ // then caps is the set of all capabilities that were removed from the jvmtiEnv.
+ ALWAYS_INLINE
+ inline void HandleChangedCapabilities(ArtJvmTiEnv* env,
+ const jvmtiCapabilities& caps,
+ bool added);
+
+ private:
+ template <ArtJvmtiEvent kEvent>
+ ALWAYS_INLINE
+ static inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread);
+
+ ALWAYS_INLINE
+ inline bool NeedsEventUpdate(ArtJvmTiEnv* env,
+ const jvmtiCapabilities& caps,
+ bool added);
+
+ // Recalculates the event mask for the given event.
+ ALWAYS_INLINE
+ inline void RecalculateGlobalEventMask(ArtJvmtiEvent event);
+
+ template <ArtJvmtiEvent kEvent>
+ ALWAYS_INLINE inline void DispatchClassFileLoadHookEvent(art::Thread* thread,
+ JNIEnv* jnienv,
+ jclass class_being_redefined,
+ jobject loader,
+ const char* name,
+ jobject protection_domain,
+ jint class_data_len,
+ const unsigned char* class_data,
+ jint* new_class_data_len,
+ unsigned char** new_class_data) const;
+
+ void HandleEventType(ArtJvmtiEvent event, bool enable);
+ void HandleLocalAccessCapabilityAdded();
+
+ // List of all JvmTiEnv objects that have been created, in their creation order.
+ // NB Some elements might be null representing envs that have been deleted. They should be skipped
+ // anytime this list is used.
+ std::vector<ArtJvmTiEnv*> envs;
+
+ // A union of all enabled events, anywhere.
+ EventMask global_mask;
+
+ std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
+ std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
+ std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_EVENTS_H_
diff --git a/openjdkjvmti/fixed_up_dex_file.cc b/openjdkjvmti/fixed_up_dex_file.cc
new file mode 100644
index 0000000..5bfa5ca
--- /dev/null
+++ b/openjdkjvmti/fixed_up_dex_file.cc
@@ -0,0 +1,91 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "fixed_up_dex_file.h"
+#include "dex_file-inl.h"
+
+// Runtime includes.
+#include "dex_to_dex_decompiler.h"
+#include "oat_file.h"
+#include "vdex_file.h"
+
+namespace openjdkjvmti {
+
+static void RecomputeDexChecksum(art::DexFile* dex_file)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ reinterpret_cast<art::DexFile::Header*>(const_cast<uint8_t*>(dex_file->Begin()))->checksum_ =
+ dex_file->CalculateChecksum();
+}
+
+static void DoDexUnquicken(const art::DexFile& new_dex_file, const art::DexFile& original_dex_file)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ const art::OatDexFile* oat_dex = original_dex_file.GetOatDexFile();
+ if (oat_dex == nullptr) {
+ return;
+ }
+ const art::OatFile* oat_file = oat_dex->GetOatFile();
+ if (oat_file == nullptr) {
+ return;
+ }
+ const art::VdexFile* vdex = oat_file->GetVdexFile();
+ if (vdex == nullptr) {
+ return;
+ }
+ vdex->FullyUnquickenDexFile(new_dex_file, original_dex_file);
+}
+
+std::unique_ptr<FixedUpDexFile> FixedUpDexFile::Create(const art::DexFile& original) {
+ // Copy the data into mutable memory.
+ std::vector<unsigned char> data;
+ data.resize(original.Size());
+ memcpy(data.data(), original.Begin(), original.Size());
+ std::string error;
+ std::unique_ptr<const art::DexFile> new_dex_file(art::DexFile::Open(
+ data.data(),
+ data.size(),
+ /*location*/"Unquickening_dexfile.dex",
+ /*location_checksum*/0,
+ /*oat_dex_file*/nullptr,
+ /*verify*/false,
+ /*verify_checksum*/false,
+ &error));
+ if (new_dex_file.get() == nullptr) {
+ LOG(ERROR) << "Unable to open dex file from memory for unquickening! error: " << error;
+ return nullptr;
+ }
+
+ DoDexUnquicken(*new_dex_file, original);
+ RecomputeDexChecksum(const_cast<art::DexFile*>(new_dex_file.get()));
+ std::unique_ptr<FixedUpDexFile> ret(new FixedUpDexFile(std::move(new_dex_file), std::move(data)));
+ return ret;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/fixed_up_dex_file.h b/openjdkjvmti/fixed_up_dex_file.h
new file mode 100644
index 0000000..4cb39cf
--- /dev/null
+++ b/openjdkjvmti/fixed_up_dex_file.h
@@ -0,0 +1,83 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+#define ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+
+#include <memory>
+#include <vector>
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/mutex.h"
+#include "dex_file.h"
+
+namespace openjdkjvmti {
+
+// A holder for a DexFile that has been 'fixed up' to ensure it is fully compliant with the
+// published standard (no internal/quick opcodes, all fields are the defined values, etc). This is
+// used to ensure that agents get a consistent dex file regardless of what version of android they
+// are running on.
+class FixedUpDexFile {
+ public:
+ static std::unique_ptr<FixedUpDexFile> Create(const art::DexFile& original)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ const art::DexFile& GetDexFile() {
+ return *dex_file_;
+ }
+
+ const unsigned char* Begin() {
+ return data_.data();
+ }
+
+ size_t Size() {
+ return data_.size();
+ }
+
+ private:
+ explicit FixedUpDexFile(std::unique_ptr<const art::DexFile> fixed_up_dex_file,
+ std::vector<unsigned char> data)
+ : dex_file_(std::move(fixed_up_dex_file)),
+ data_(std::move(data)) {}
+
+ // the fixed up DexFile
+ std::unique_ptr<const art::DexFile> dex_file_;
+ // The backing data for dex_file_.
+ const std::vector<unsigned char> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixedUpDexFile);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
diff --git a/openjdkjvmti/include/jvmti.h b/openjdkjvmti/include/jvmti.h
new file mode 100644
index 0000000..de07c16
--- /dev/null
+++ b/openjdkjvmti/include/jvmti.h
@@ -0,0 +1,2534 @@
+/*
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ /* AUTOMATICALLY GENERATED FILE - DO NOT EDIT */
+
+
+ /* Include file for the Java(tm) Virtual Machine Tool Interface */
+
+#ifndef _JAVA_JVMTI_H_
+#define _JAVA_JVMTI_H_
+
+#include "jni.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum {
+ JVMTI_VERSION_1 = 0x30010000,
+ JVMTI_VERSION_1_0 = 0x30010000,
+ JVMTI_VERSION_1_1 = 0x30010100,
+ JVMTI_VERSION_1_2 = 0x30010200,
+
+ JVMTI_VERSION = 0x30000000 + (1 * 0x10000) + (2 * 0x100) + 1 /* version: 1.2.1 */
+};
+
+JNIEXPORT jint JNICALL
+Agent_OnLoad(JavaVM *vm, char *options, void *reserved);
+
+JNIEXPORT jint JNICALL
+Agent_OnAttach(JavaVM* vm, char* options, void* reserved);
+
+JNIEXPORT void JNICALL
+Agent_OnUnload(JavaVM *vm);
+
+ /* Forward declaration of the environment */
+
+struct _jvmtiEnv;
+
+struct jvmtiInterface_1_;
+
+#ifdef __cplusplus
+typedef _jvmtiEnv jvmtiEnv;
+#else
+typedef const struct jvmtiInterface_1_ *jvmtiEnv;
+#endif /* __cplusplus */
+
+/* Derived Base Types */
+
+typedef jobject jthread;
+typedef jobject jthreadGroup;
+typedef jlong jlocation;
+struct _jrawMonitorID;
+typedef struct _jrawMonitorID *jrawMonitorID;
+typedef struct JNINativeInterface jniNativeInterface;
+
+ /* Constants */
+
+
+ /* Thread State Flags */
+
+enum {
+ JVMTI_THREAD_STATE_ALIVE = 0x0001,
+ JVMTI_THREAD_STATE_TERMINATED = 0x0002,
+ JVMTI_THREAD_STATE_RUNNABLE = 0x0004,
+ JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400,
+ JVMTI_THREAD_STATE_WAITING = 0x0080,
+ JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010,
+ JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020,
+ JVMTI_THREAD_STATE_SLEEPING = 0x0040,
+ JVMTI_THREAD_STATE_IN_OBJECT_WAIT = 0x0100,
+ JVMTI_THREAD_STATE_PARKED = 0x0200,
+ JVMTI_THREAD_STATE_SUSPENDED = 0x100000,
+ JVMTI_THREAD_STATE_INTERRUPTED = 0x200000,
+ JVMTI_THREAD_STATE_IN_NATIVE = 0x400000,
+ JVMTI_THREAD_STATE_VENDOR_1 = 0x10000000,
+ JVMTI_THREAD_STATE_VENDOR_2 = 0x20000000,
+ JVMTI_THREAD_STATE_VENDOR_3 = 0x40000000
+};
+
+ /* java.lang.Thread.State Conversion Masks */
+
+enum {
+ JVMTI_JAVA_LANG_THREAD_STATE_MASK = JVMTI_THREAD_STATE_TERMINATED | JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT,
+ JVMTI_JAVA_LANG_THREAD_STATE_NEW = 0,
+ JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED = JVMTI_THREAD_STATE_TERMINATED,
+ JVMTI_JAVA_LANG_THREAD_STATE_RUNNABLE = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE,
+ JVMTI_JAVA_LANG_THREAD_STATE_BLOCKED = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER,
+ JVMTI_JAVA_LANG_THREAD_STATE_WAITING = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY,
+ JVMTI_JAVA_LANG_THREAD_STATE_TIMED_WAITING = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT
+};
+
+ /* Thread Priority Constants */
+
+enum {
+ JVMTI_THREAD_MIN_PRIORITY = 1,
+ JVMTI_THREAD_NORM_PRIORITY = 5,
+ JVMTI_THREAD_MAX_PRIORITY = 10
+};
+
+ /* Heap Filter Flags */
+
+enum {
+ JVMTI_HEAP_FILTER_TAGGED = 0x4,
+ JVMTI_HEAP_FILTER_UNTAGGED = 0x8,
+ JVMTI_HEAP_FILTER_CLASS_TAGGED = 0x10,
+ JVMTI_HEAP_FILTER_CLASS_UNTAGGED = 0x20
+};
+
+ /* Heap Visit Control Flags */
+
+enum {
+ JVMTI_VISIT_OBJECTS = 0x100,
+ JVMTI_VISIT_ABORT = 0x8000
+};
+
+ /* Heap Reference Enumeration */
+
+typedef enum {
+ JVMTI_HEAP_REFERENCE_CLASS = 1,
+ JVMTI_HEAP_REFERENCE_FIELD = 2,
+ JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT = 3,
+ JVMTI_HEAP_REFERENCE_CLASS_LOADER = 4,
+ JVMTI_HEAP_REFERENCE_SIGNERS = 5,
+ JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN = 6,
+ JVMTI_HEAP_REFERENCE_INTERFACE = 7,
+ JVMTI_HEAP_REFERENCE_STATIC_FIELD = 8,
+ JVMTI_HEAP_REFERENCE_CONSTANT_POOL = 9,
+ JVMTI_HEAP_REFERENCE_SUPERCLASS = 10,
+ JVMTI_HEAP_REFERENCE_JNI_GLOBAL = 21,
+ JVMTI_HEAP_REFERENCE_SYSTEM_CLASS = 22,
+ JVMTI_HEAP_REFERENCE_MONITOR = 23,
+ JVMTI_HEAP_REFERENCE_STACK_LOCAL = 24,
+ JVMTI_HEAP_REFERENCE_JNI_LOCAL = 25,
+ JVMTI_HEAP_REFERENCE_THREAD = 26,
+ JVMTI_HEAP_REFERENCE_OTHER = 27
+} jvmtiHeapReferenceKind;
+
+ /* Primitive Type Enumeration */
+
+typedef enum {
+ JVMTI_PRIMITIVE_TYPE_BOOLEAN = 90,
+ JVMTI_PRIMITIVE_TYPE_BYTE = 66,
+ JVMTI_PRIMITIVE_TYPE_CHAR = 67,
+ JVMTI_PRIMITIVE_TYPE_SHORT = 83,
+ JVMTI_PRIMITIVE_TYPE_INT = 73,
+ JVMTI_PRIMITIVE_TYPE_LONG = 74,
+ JVMTI_PRIMITIVE_TYPE_FLOAT = 70,
+ JVMTI_PRIMITIVE_TYPE_DOUBLE = 68
+} jvmtiPrimitiveType;
+
+ /* Heap Object Filter Enumeration */
+
+typedef enum {
+ JVMTI_HEAP_OBJECT_TAGGED = 1,
+ JVMTI_HEAP_OBJECT_UNTAGGED = 2,
+ JVMTI_HEAP_OBJECT_EITHER = 3
+} jvmtiHeapObjectFilter;
+
+ /* Heap Root Kind Enumeration */
+
+typedef enum {
+ JVMTI_HEAP_ROOT_JNI_GLOBAL = 1,
+ JVMTI_HEAP_ROOT_SYSTEM_CLASS = 2,
+ JVMTI_HEAP_ROOT_MONITOR = 3,
+ JVMTI_HEAP_ROOT_STACK_LOCAL = 4,
+ JVMTI_HEAP_ROOT_JNI_LOCAL = 5,
+ JVMTI_HEAP_ROOT_THREAD = 6,
+ JVMTI_HEAP_ROOT_OTHER = 7
+} jvmtiHeapRootKind;
+
+ /* Object Reference Enumeration */
+
+typedef enum {
+ JVMTI_REFERENCE_CLASS = 1,
+ JVMTI_REFERENCE_FIELD = 2,
+ JVMTI_REFERENCE_ARRAY_ELEMENT = 3,
+ JVMTI_REFERENCE_CLASS_LOADER = 4,
+ JVMTI_REFERENCE_SIGNERS = 5,
+ JVMTI_REFERENCE_PROTECTION_DOMAIN = 6,
+ JVMTI_REFERENCE_INTERFACE = 7,
+ JVMTI_REFERENCE_STATIC_FIELD = 8,
+ JVMTI_REFERENCE_CONSTANT_POOL = 9
+} jvmtiObjectReferenceKind;
+
+ /* Iteration Control Enumeration */
+
+typedef enum {
+ JVMTI_ITERATION_CONTINUE = 1,
+ JVMTI_ITERATION_IGNORE = 2,
+ JVMTI_ITERATION_ABORT = 0
+} jvmtiIterationControl;
+
+ /* Class Status Flags */
+
+enum {
+ JVMTI_CLASS_STATUS_VERIFIED = 1,
+ JVMTI_CLASS_STATUS_PREPARED = 2,
+ JVMTI_CLASS_STATUS_INITIALIZED = 4,
+ JVMTI_CLASS_STATUS_ERROR = 8,
+ JVMTI_CLASS_STATUS_ARRAY = 16,
+ JVMTI_CLASS_STATUS_PRIMITIVE = 32
+};
+
+ /* Event Enable/Disable */
+
+typedef enum {
+ JVMTI_ENABLE = 1,
+ JVMTI_DISABLE = 0
+} jvmtiEventMode;
+
+ /* Extension Function/Event Parameter Types */
+
+typedef enum {
+ JVMTI_TYPE_JBYTE = 101,
+ JVMTI_TYPE_JCHAR = 102,
+ JVMTI_TYPE_JSHORT = 103,
+ JVMTI_TYPE_JINT = 104,
+ JVMTI_TYPE_JLONG = 105,
+ JVMTI_TYPE_JFLOAT = 106,
+ JVMTI_TYPE_JDOUBLE = 107,
+ JVMTI_TYPE_JBOOLEAN = 108,
+ JVMTI_TYPE_JOBJECT = 109,
+ JVMTI_TYPE_JTHREAD = 110,
+ JVMTI_TYPE_JCLASS = 111,
+ JVMTI_TYPE_JVALUE = 112,
+ JVMTI_TYPE_JFIELDID = 113,
+ JVMTI_TYPE_JMETHODID = 114,
+ JVMTI_TYPE_CCHAR = 115,
+ JVMTI_TYPE_CVOID = 116,
+ JVMTI_TYPE_JNIENV = 117
+} jvmtiParamTypes;
+
+ /* Extension Function/Event Parameter Kinds */
+
+typedef enum {
+ JVMTI_KIND_IN = 91,
+ JVMTI_KIND_IN_PTR = 92,
+ JVMTI_KIND_IN_BUF = 93,
+ JVMTI_KIND_ALLOC_BUF = 94,
+ JVMTI_KIND_ALLOC_ALLOC_BUF = 95,
+ JVMTI_KIND_OUT = 96,
+ JVMTI_KIND_OUT_BUF = 97
+} jvmtiParamKind;
+
+ /* Timer Kinds */
+
+typedef enum {
+ JVMTI_TIMER_USER_CPU = 30,
+ JVMTI_TIMER_TOTAL_CPU = 31,
+ JVMTI_TIMER_ELAPSED = 32
+} jvmtiTimerKind;
+
+ /* Phases of execution */
+
+typedef enum {
+ JVMTI_PHASE_ONLOAD = 1,
+ JVMTI_PHASE_PRIMORDIAL = 2,
+ JVMTI_PHASE_START = 6,
+ JVMTI_PHASE_LIVE = 4,
+ JVMTI_PHASE_DEAD = 8
+} jvmtiPhase;
+
+ /* Version Interface Types */
+
+enum {
+ JVMTI_VERSION_INTERFACE_JNI = 0x00000000,
+ JVMTI_VERSION_INTERFACE_JVMTI = 0x30000000
+};
+
+ /* Version Masks */
+
+enum {
+ JVMTI_VERSION_MASK_INTERFACE_TYPE = 0x70000000,
+ JVMTI_VERSION_MASK_MAJOR = 0x0FFF0000,
+ JVMTI_VERSION_MASK_MINOR = 0x0000FF00,
+ JVMTI_VERSION_MASK_MICRO = 0x000000FF
+};
+
+ /* Version Shifts */
+
+enum {
+ JVMTI_VERSION_SHIFT_MAJOR = 16,
+ JVMTI_VERSION_SHIFT_MINOR = 8,
+ JVMTI_VERSION_SHIFT_MICRO = 0
+};
+
+ /* Verbose Flag Enumeration */
+
+typedef enum {
+ JVMTI_VERBOSE_OTHER = 0,
+ JVMTI_VERBOSE_GC = 1,
+ JVMTI_VERBOSE_CLASS = 2,
+ JVMTI_VERBOSE_JNI = 4
+} jvmtiVerboseFlag;
+
+ /* JLocation Format Enumeration */
+
+typedef enum {
+ JVMTI_JLOCATION_JVMBCI = 1,
+ JVMTI_JLOCATION_MACHINEPC = 2,
+ JVMTI_JLOCATION_OTHER = 0
+} jvmtiJlocationFormat;
+
+ /* Resource Exhaustion Flags */
+
+enum {
+ JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR = 0x0001,
+ JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP = 0x0002,
+ JVMTI_RESOURCE_EXHAUSTED_THREADS = 0x0004
+};
+
+ /* Errors */
+
+typedef enum {
+ JVMTI_ERROR_NONE = 0,
+ JVMTI_ERROR_INVALID_THREAD = 10,
+ JVMTI_ERROR_INVALID_THREAD_GROUP = 11,
+ JVMTI_ERROR_INVALID_PRIORITY = 12,
+ JVMTI_ERROR_THREAD_NOT_SUSPENDED = 13,
+ JVMTI_ERROR_THREAD_SUSPENDED = 14,
+ JVMTI_ERROR_THREAD_NOT_ALIVE = 15,
+ JVMTI_ERROR_INVALID_OBJECT = 20,
+ JVMTI_ERROR_INVALID_CLASS = 21,
+ JVMTI_ERROR_CLASS_NOT_PREPARED = 22,
+ JVMTI_ERROR_INVALID_METHODID = 23,
+ JVMTI_ERROR_INVALID_LOCATION = 24,
+ JVMTI_ERROR_INVALID_FIELDID = 25,
+ JVMTI_ERROR_NO_MORE_FRAMES = 31,
+ JVMTI_ERROR_OPAQUE_FRAME = 32,
+ JVMTI_ERROR_TYPE_MISMATCH = 34,
+ JVMTI_ERROR_INVALID_SLOT = 35,
+ JVMTI_ERROR_DUPLICATE = 40,
+ JVMTI_ERROR_NOT_FOUND = 41,
+ JVMTI_ERROR_INVALID_MONITOR = 50,
+ JVMTI_ERROR_NOT_MONITOR_OWNER = 51,
+ JVMTI_ERROR_INTERRUPT = 52,
+ JVMTI_ERROR_INVALID_CLASS_FORMAT = 60,
+ JVMTI_ERROR_CIRCULAR_CLASS_DEFINITION = 61,
+ JVMTI_ERROR_FAILS_VERIFICATION = 62,
+ JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED = 63,
+ JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED = 64,
+ JVMTI_ERROR_INVALID_TYPESTATE = 65,
+ JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED = 66,
+ JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED = 67,
+ JVMTI_ERROR_UNSUPPORTED_VERSION = 68,
+ JVMTI_ERROR_NAMES_DONT_MATCH = 69,
+ JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED = 70,
+ JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED = 71,
+ JVMTI_ERROR_UNMODIFIABLE_CLASS = 79,
+ JVMTI_ERROR_NOT_AVAILABLE = 98,
+ JVMTI_ERROR_MUST_POSSESS_CAPABILITY = 99,
+ JVMTI_ERROR_NULL_POINTER = 100,
+ JVMTI_ERROR_ABSENT_INFORMATION = 101,
+ JVMTI_ERROR_INVALID_EVENT_TYPE = 102,
+ JVMTI_ERROR_ILLEGAL_ARGUMENT = 103,
+ JVMTI_ERROR_NATIVE_METHOD = 104,
+ JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED = 106,
+ JVMTI_ERROR_OUT_OF_MEMORY = 110,
+ JVMTI_ERROR_ACCESS_DENIED = 111,
+ JVMTI_ERROR_WRONG_PHASE = 112,
+ JVMTI_ERROR_INTERNAL = 113,
+ JVMTI_ERROR_UNATTACHED_THREAD = 115,
+ JVMTI_ERROR_INVALID_ENVIRONMENT = 116,
+ JVMTI_ERROR_MAX = 116
+} jvmtiError;
+
+ /* Event IDs */
+
+typedef enum {
+ JVMTI_MIN_EVENT_TYPE_VAL = 50,
+ JVMTI_EVENT_VM_INIT = 50,
+ JVMTI_EVENT_VM_DEATH = 51,
+ JVMTI_EVENT_THREAD_START = 52,
+ JVMTI_EVENT_THREAD_END = 53,
+ JVMTI_EVENT_CLASS_FILE_LOAD_HOOK = 54,
+ JVMTI_EVENT_CLASS_LOAD = 55,
+ JVMTI_EVENT_CLASS_PREPARE = 56,
+ JVMTI_EVENT_VM_START = 57,
+ JVMTI_EVENT_EXCEPTION = 58,
+ JVMTI_EVENT_EXCEPTION_CATCH = 59,
+ JVMTI_EVENT_SINGLE_STEP = 60,
+ JVMTI_EVENT_FRAME_POP = 61,
+ JVMTI_EVENT_BREAKPOINT = 62,
+ JVMTI_EVENT_FIELD_ACCESS = 63,
+ JVMTI_EVENT_FIELD_MODIFICATION = 64,
+ JVMTI_EVENT_METHOD_ENTRY = 65,
+ JVMTI_EVENT_METHOD_EXIT = 66,
+ JVMTI_EVENT_NATIVE_METHOD_BIND = 67,
+ JVMTI_EVENT_COMPILED_METHOD_LOAD = 68,
+ JVMTI_EVENT_COMPILED_METHOD_UNLOAD = 69,
+ JVMTI_EVENT_DYNAMIC_CODE_GENERATED = 70,
+ JVMTI_EVENT_DATA_DUMP_REQUEST = 71,
+ JVMTI_EVENT_MONITOR_WAIT = 73,
+ JVMTI_EVENT_MONITOR_WAITED = 74,
+ JVMTI_EVENT_MONITOR_CONTENDED_ENTER = 75,
+ JVMTI_EVENT_MONITOR_CONTENDED_ENTERED = 76,
+ JVMTI_EVENT_RESOURCE_EXHAUSTED = 80,
+ JVMTI_EVENT_GARBAGE_COLLECTION_START = 81,
+ JVMTI_EVENT_GARBAGE_COLLECTION_FINISH = 82,
+ JVMTI_EVENT_OBJECT_FREE = 83,
+ JVMTI_EVENT_VM_OBJECT_ALLOC = 84,
+ JVMTI_MAX_EVENT_TYPE_VAL = 84
+} jvmtiEvent;
+
+
+ /* Pre-Declarations */
+struct _jvmtiThreadInfo;
+typedef struct _jvmtiThreadInfo jvmtiThreadInfo;
+struct _jvmtiMonitorStackDepthInfo;
+typedef struct _jvmtiMonitorStackDepthInfo jvmtiMonitorStackDepthInfo;
+struct _jvmtiThreadGroupInfo;
+typedef struct _jvmtiThreadGroupInfo jvmtiThreadGroupInfo;
+struct _jvmtiFrameInfo;
+typedef struct _jvmtiFrameInfo jvmtiFrameInfo;
+struct _jvmtiStackInfo;
+typedef struct _jvmtiStackInfo jvmtiStackInfo;
+struct _jvmtiHeapReferenceInfoField;
+typedef struct _jvmtiHeapReferenceInfoField jvmtiHeapReferenceInfoField;
+struct _jvmtiHeapReferenceInfoArray;
+typedef struct _jvmtiHeapReferenceInfoArray jvmtiHeapReferenceInfoArray;
+struct _jvmtiHeapReferenceInfoConstantPool;
+typedef struct _jvmtiHeapReferenceInfoConstantPool jvmtiHeapReferenceInfoConstantPool;
+struct _jvmtiHeapReferenceInfoStackLocal;
+typedef struct _jvmtiHeapReferenceInfoStackLocal jvmtiHeapReferenceInfoStackLocal;
+struct _jvmtiHeapReferenceInfoJniLocal;
+typedef struct _jvmtiHeapReferenceInfoJniLocal jvmtiHeapReferenceInfoJniLocal;
+struct _jvmtiHeapReferenceInfoReserved;
+typedef struct _jvmtiHeapReferenceInfoReserved jvmtiHeapReferenceInfoReserved;
+union _jvmtiHeapReferenceInfo;
+typedef union _jvmtiHeapReferenceInfo jvmtiHeapReferenceInfo;
+struct _jvmtiHeapCallbacks;
+typedef struct _jvmtiHeapCallbacks jvmtiHeapCallbacks;
+struct _jvmtiClassDefinition;
+typedef struct _jvmtiClassDefinition jvmtiClassDefinition;
+struct _jvmtiMonitorUsage;
+typedef struct _jvmtiMonitorUsage jvmtiMonitorUsage;
+struct _jvmtiLineNumberEntry;
+typedef struct _jvmtiLineNumberEntry jvmtiLineNumberEntry;
+struct _jvmtiLocalVariableEntry;
+typedef struct _jvmtiLocalVariableEntry jvmtiLocalVariableEntry;
+struct _jvmtiParamInfo;
+typedef struct _jvmtiParamInfo jvmtiParamInfo;
+struct _jvmtiExtensionFunctionInfo;
+typedef struct _jvmtiExtensionFunctionInfo jvmtiExtensionFunctionInfo;
+struct _jvmtiExtensionEventInfo;
+typedef struct _jvmtiExtensionEventInfo jvmtiExtensionEventInfo;
+struct _jvmtiTimerInfo;
+typedef struct _jvmtiTimerInfo jvmtiTimerInfo;
+struct _jvmtiAddrLocationMap;
+typedef struct _jvmtiAddrLocationMap jvmtiAddrLocationMap;
+
+ /* Function Types */
+
+typedef void (JNICALL *jvmtiStartFunction)
+ (jvmtiEnv* jvmti_env, JNIEnv* jni_env, void* arg);
+
+typedef jint (JNICALL *jvmtiHeapIterationCallback)
+ (jlong class_tag, jlong size, jlong* tag_ptr, jint length, void* user_data);
+
+typedef jint (JNICALL *jvmtiHeapReferenceCallback)
+ (jvmtiHeapReferenceKind reference_kind, const jvmtiHeapReferenceInfo* reference_info, jlong class_tag, jlong referrer_class_tag, jlong size, jlong* tag_ptr, jlong* referrer_tag_ptr, jint length, void* user_data);
+
+typedef jint (JNICALL *jvmtiPrimitiveFieldCallback)
+ (jvmtiHeapReferenceKind kind, const jvmtiHeapReferenceInfo* info, jlong object_class_tag, jlong* object_tag_ptr, jvalue value, jvmtiPrimitiveType value_type, void* user_data);
+
+typedef jint (JNICALL *jvmtiArrayPrimitiveValueCallback)
+ (jlong class_tag, jlong size, jlong* tag_ptr, jint element_count, jvmtiPrimitiveType element_type, const void* elements, void* user_data);
+
+typedef jint (JNICALL *jvmtiStringPrimitiveValueCallback)
+ (jlong class_tag, jlong size, jlong* tag_ptr, const jchar* value, jint value_length, void* user_data);
+
+typedef jint (JNICALL *jvmtiReservedCallback)
+ ();
+
+typedef jvmtiIterationControl (JNICALL *jvmtiHeapObjectCallback)
+ (jlong class_tag, jlong size, jlong* tag_ptr, void* user_data);
+
+typedef jvmtiIterationControl (JNICALL *jvmtiHeapRootCallback)
+ (jvmtiHeapRootKind root_kind, jlong class_tag, jlong size, jlong* tag_ptr, void* user_data);
+
+typedef jvmtiIterationControl (JNICALL *jvmtiStackReferenceCallback)
+ (jvmtiHeapRootKind root_kind, jlong class_tag, jlong size, jlong* tag_ptr, jlong thread_tag, jint depth, jmethodID method, jint slot, void* user_data);
+
+typedef jvmtiIterationControl (JNICALL *jvmtiObjectReferenceCallback)
+ (jvmtiObjectReferenceKind reference_kind, jlong class_tag, jlong size, jlong* tag_ptr, jlong referrer_tag, jint referrer_index, void* user_data);
+
+typedef jvmtiError (JNICALL *jvmtiExtensionFunction)
+ (jvmtiEnv* jvmti_env, ...);
+
+typedef void (JNICALL *jvmtiExtensionEvent)
+ (jvmtiEnv* jvmti_env, ...);
+
+
+ /* Structure Types */
+struct _jvmtiThreadInfo {
+ char* name;
+ jint priority;
+ jboolean is_daemon;
+ jthreadGroup thread_group;
+ jobject context_class_loader;
+};
+struct _jvmtiMonitorStackDepthInfo {
+ jobject monitor;
+ jint stack_depth;
+};
+struct _jvmtiThreadGroupInfo {
+ jthreadGroup parent;
+ char* name;
+ jint max_priority;
+ jboolean is_daemon;
+};
+struct _jvmtiFrameInfo {
+ jmethodID method;
+ jlocation location;
+};
+struct _jvmtiStackInfo {
+ jthread thread;
+ jint state;
+ jvmtiFrameInfo* frame_buffer;
+ jint frame_count;
+};
+struct _jvmtiHeapReferenceInfoField {
+ jint index;
+};
+struct _jvmtiHeapReferenceInfoArray {
+ jint index;
+};
+struct _jvmtiHeapReferenceInfoConstantPool {
+ jint index;
+};
+struct _jvmtiHeapReferenceInfoStackLocal {
+ jlong thread_tag;
+ jlong thread_id;
+ jint depth;
+ jmethodID method;
+ jlocation location;
+ jint slot;
+};
+struct _jvmtiHeapReferenceInfoJniLocal {
+ jlong thread_tag;
+ jlong thread_id;
+ jint depth;
+ jmethodID method;
+};
+struct _jvmtiHeapReferenceInfoReserved {
+ jlong reserved1;
+ jlong reserved2;
+ jlong reserved3;
+ jlong reserved4;
+ jlong reserved5;
+ jlong reserved6;
+ jlong reserved7;
+ jlong reserved8;
+};
+union _jvmtiHeapReferenceInfo {
+ jvmtiHeapReferenceInfoField field;
+ jvmtiHeapReferenceInfoArray array;
+ jvmtiHeapReferenceInfoConstantPool constant_pool;
+ jvmtiHeapReferenceInfoStackLocal stack_local;
+ jvmtiHeapReferenceInfoJniLocal jni_local;
+ jvmtiHeapReferenceInfoReserved other;
+};
+struct _jvmtiHeapCallbacks {
+ jvmtiHeapIterationCallback heap_iteration_callback;
+ jvmtiHeapReferenceCallback heap_reference_callback;
+ jvmtiPrimitiveFieldCallback primitive_field_callback;
+ jvmtiArrayPrimitiveValueCallback array_primitive_value_callback;
+ jvmtiStringPrimitiveValueCallback string_primitive_value_callback;
+ jvmtiReservedCallback reserved5;
+ jvmtiReservedCallback reserved6;
+ jvmtiReservedCallback reserved7;
+ jvmtiReservedCallback reserved8;
+ jvmtiReservedCallback reserved9;
+ jvmtiReservedCallback reserved10;
+ jvmtiReservedCallback reserved11;
+ jvmtiReservedCallback reserved12;
+ jvmtiReservedCallback reserved13;
+ jvmtiReservedCallback reserved14;
+ jvmtiReservedCallback reserved15;
+};
+struct _jvmtiClassDefinition {
+ jclass klass;
+ jint class_byte_count;
+ const unsigned char* class_bytes;
+};
+struct _jvmtiMonitorUsage {
+ jthread owner;
+ jint entry_count;
+ jint waiter_count;
+ jthread* waiters;
+ jint notify_waiter_count;
+ jthread* notify_waiters;
+};
+struct _jvmtiLineNumberEntry {
+ jlocation start_location;
+ jint line_number;
+};
+struct _jvmtiLocalVariableEntry {
+ jlocation start_location;
+ jint length;
+ char* name;
+ char* signature;
+ char* generic_signature;
+ jint slot;
+};
+struct _jvmtiParamInfo {
+ char* name;
+ jvmtiParamKind kind;
+ jvmtiParamTypes base_type;
+ jboolean null_ok;
+};
+struct _jvmtiExtensionFunctionInfo {
+ jvmtiExtensionFunction func;
+ char* id;
+ char* short_description;
+ jint param_count;
+ jvmtiParamInfo* params;
+ jint error_count;
+ jvmtiError* errors;
+};
+struct _jvmtiExtensionEventInfo {
+ jint extension_event_index;
+ char* id;
+ char* short_description;
+ jint param_count;
+ jvmtiParamInfo* params;
+};
+struct _jvmtiTimerInfo {
+ jlong max_value;
+ jboolean may_skip_forward;
+ jboolean may_skip_backward;
+ jvmtiTimerKind kind;
+ jlong reserved1;
+ jlong reserved2;
+};
+struct _jvmtiAddrLocationMap {
+ const void* start_address;
+ jlocation location;
+};
+
+typedef struct {
+ unsigned int can_tag_objects : 1;
+ unsigned int can_generate_field_modification_events : 1;
+ unsigned int can_generate_field_access_events : 1;
+ unsigned int can_get_bytecodes : 1;
+ unsigned int can_get_synthetic_attribute : 1;
+ unsigned int can_get_owned_monitor_info : 1;
+ unsigned int can_get_current_contended_monitor : 1;
+ unsigned int can_get_monitor_info : 1;
+ unsigned int can_pop_frame : 1;
+ unsigned int can_redefine_classes : 1;
+ unsigned int can_signal_thread : 1;
+ unsigned int can_get_source_file_name : 1;
+ unsigned int can_get_line_numbers : 1;
+ unsigned int can_get_source_debug_extension : 1;
+ unsigned int can_access_local_variables : 1;
+ unsigned int can_maintain_original_method_order : 1;
+ unsigned int can_generate_single_step_events : 1;
+ unsigned int can_generate_exception_events : 1;
+ unsigned int can_generate_frame_pop_events : 1;
+ unsigned int can_generate_breakpoint_events : 1;
+ unsigned int can_suspend : 1;
+ unsigned int can_redefine_any_class : 1;
+ unsigned int can_get_current_thread_cpu_time : 1;
+ unsigned int can_get_thread_cpu_time : 1;
+ unsigned int can_generate_method_entry_events : 1;
+ unsigned int can_generate_method_exit_events : 1;
+ unsigned int can_generate_all_class_hook_events : 1;
+ unsigned int can_generate_compiled_method_load_events : 1;
+ unsigned int can_generate_monitor_events : 1;
+ unsigned int can_generate_vm_object_alloc_events : 1;
+ unsigned int can_generate_native_method_bind_events : 1;
+ unsigned int can_generate_garbage_collection_events : 1;
+ unsigned int can_generate_object_free_events : 1;
+ unsigned int can_force_early_return : 1;
+ unsigned int can_get_owned_monitor_stack_depth_info : 1;
+ unsigned int can_get_constant_pool : 1;
+ unsigned int can_set_native_method_prefix : 1;
+ unsigned int can_retransform_classes : 1;
+ unsigned int can_retransform_any_class : 1;
+ unsigned int can_generate_resource_exhaustion_heap_events : 1;
+ unsigned int can_generate_resource_exhaustion_threads_events : 1;
+ unsigned int : 7;
+ unsigned int : 16;
+ unsigned int : 16;
+ unsigned int : 16;
+ unsigned int : 16;
+ unsigned int : 16;
+} jvmtiCapabilities;
+
+
+ /* Event Definitions */
+
+typedef void (JNICALL *jvmtiEventReserved)(void);
+
+
+typedef void (JNICALL *jvmtiEventBreakpoint)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jlocation location);
+
+typedef void (JNICALL *jvmtiEventClassFileLoadHook)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jclass class_being_redefined,
+ jobject loader,
+ const char* name,
+ jobject protection_domain,
+ jint class_data_len,
+ const unsigned char* class_data,
+ jint* new_class_data_len,
+ unsigned char** new_class_data);
+
+typedef void (JNICALL *jvmtiEventClassLoad)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jclass klass);
+
+typedef void (JNICALL *jvmtiEventClassPrepare)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jclass klass);
+
+typedef void (JNICALL *jvmtiEventCompiledMethodLoad)
+ (jvmtiEnv *jvmti_env,
+ jmethodID method,
+ jint code_size,
+ const void* code_addr,
+ jint map_length,
+ const jvmtiAddrLocationMap* map,
+ const void* compile_info);
+
+typedef void (JNICALL *jvmtiEventCompiledMethodUnload)
+ (jvmtiEnv *jvmti_env,
+ jmethodID method,
+ const void* code_addr);
+
+typedef void (JNICALL *jvmtiEventDataDumpRequest)
+ (jvmtiEnv *jvmti_env);
+
+typedef void (JNICALL *jvmtiEventDynamicCodeGenerated)
+ (jvmtiEnv *jvmti_env,
+ const char* name,
+ const void* address,
+ jint length);
+
+typedef void (JNICALL *jvmtiEventException)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jlocation location,
+ jobject exception,
+ jmethodID catch_method,
+ jlocation catch_location);
+
+typedef void (JNICALL *jvmtiEventExceptionCatch)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jlocation location,
+ jobject exception);
+
+typedef void (JNICALL *jvmtiEventFieldAccess)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jlocation location,
+ jclass field_klass,
+ jobject object,
+ jfieldID field);
+
+typedef void (JNICALL *jvmtiEventFieldModification)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jlocation location,
+ jclass field_klass,
+ jobject object,
+ jfieldID field,
+ char signature_type,
+ jvalue new_value);
+
+typedef void (JNICALL *jvmtiEventFramePop)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jboolean was_popped_by_exception);
+
+typedef void (JNICALL *jvmtiEventGarbageCollectionFinish)
+ (jvmtiEnv *jvmti_env);
+
+typedef void (JNICALL *jvmtiEventGarbageCollectionStart)
+ (jvmtiEnv *jvmti_env);
+
+typedef void (JNICALL *jvmtiEventMethodEntry)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method);
+
+typedef void (JNICALL *jvmtiEventMethodExit)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jboolean was_popped_by_exception,
+ jvalue return_value);
+
+typedef void (JNICALL *jvmtiEventMonitorContendedEnter)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jobject object);
+
+typedef void (JNICALL *jvmtiEventMonitorContendedEntered)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jobject object);
+
+typedef void (JNICALL *jvmtiEventMonitorWait)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jobject object,
+ jlong timeout);
+
+typedef void (JNICALL *jvmtiEventMonitorWaited)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jobject object,
+ jboolean timed_out);
+
+typedef void (JNICALL *jvmtiEventNativeMethodBind)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ void* address,
+ void** new_address_ptr);
+
+typedef void (JNICALL *jvmtiEventObjectFree)
+ (jvmtiEnv *jvmti_env,
+ jlong tag);
+
+typedef void (JNICALL *jvmtiEventResourceExhausted)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jint flags,
+ const void* reserved,
+ const char* description);
+
+typedef void (JNICALL *jvmtiEventSingleStep)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jlocation location);
+
+typedef void (JNICALL *jvmtiEventThreadEnd)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread);
+
+typedef void (JNICALL *jvmtiEventThreadStart)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread);
+
+typedef void (JNICALL *jvmtiEventVMDeath)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env);
+
+typedef void (JNICALL *jvmtiEventVMInit)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread);
+
+typedef void (JNICALL *jvmtiEventVMObjectAlloc)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jobject object,
+ jclass object_klass,
+ jlong size);
+
+typedef void (JNICALL *jvmtiEventVMStart)
+ (jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env);
+
+ /* Event Callback Structure */
+
+typedef struct {
+ /* 50 : VM Initialization Event */
+ jvmtiEventVMInit VMInit;
+ /* 51 : VM Death Event */
+ jvmtiEventVMDeath VMDeath;
+ /* 52 : Thread Start */
+ jvmtiEventThreadStart ThreadStart;
+ /* 53 : Thread End */
+ jvmtiEventThreadEnd ThreadEnd;
+ /* 54 : Class File Load Hook */
+ jvmtiEventClassFileLoadHook ClassFileLoadHook;
+ /* 55 : Class Load */
+ jvmtiEventClassLoad ClassLoad;
+ /* 56 : Class Prepare */
+ jvmtiEventClassPrepare ClassPrepare;
+ /* 57 : VM Start Event */
+ jvmtiEventVMStart VMStart;
+ /* 58 : Exception */
+ jvmtiEventException Exception;
+ /* 59 : Exception Catch */
+ jvmtiEventExceptionCatch ExceptionCatch;
+ /* 60 : Single Step */
+ jvmtiEventSingleStep SingleStep;
+ /* 61 : Frame Pop */
+ jvmtiEventFramePop FramePop;
+ /* 62 : Breakpoint */
+ jvmtiEventBreakpoint Breakpoint;
+ /* 63 : Field Access */
+ jvmtiEventFieldAccess FieldAccess;
+ /* 64 : Field Modification */
+ jvmtiEventFieldModification FieldModification;
+ /* 65 : Method Entry */
+ jvmtiEventMethodEntry MethodEntry;
+ /* 66 : Method Exit */
+ jvmtiEventMethodExit MethodExit;
+ /* 67 : Native Method Bind */
+ jvmtiEventNativeMethodBind NativeMethodBind;
+ /* 68 : Compiled Method Load */
+ jvmtiEventCompiledMethodLoad CompiledMethodLoad;
+ /* 69 : Compiled Method Unload */
+ jvmtiEventCompiledMethodUnload CompiledMethodUnload;
+ /* 70 : Dynamic Code Generated */
+ jvmtiEventDynamicCodeGenerated DynamicCodeGenerated;
+ /* 71 : Data Dump Request */
+ jvmtiEventDataDumpRequest DataDumpRequest;
+ /* 72 */
+ jvmtiEventReserved reserved72;
+ /* 73 : Monitor Wait */
+ jvmtiEventMonitorWait MonitorWait;
+ /* 74 : Monitor Waited */
+ jvmtiEventMonitorWaited MonitorWaited;
+ /* 75 : Monitor Contended Enter */
+ jvmtiEventMonitorContendedEnter MonitorContendedEnter;
+ /* 76 : Monitor Contended Entered */
+ jvmtiEventMonitorContendedEntered MonitorContendedEntered;
+ /* 77 */
+ jvmtiEventReserved reserved77;
+ /* 78 */
+ jvmtiEventReserved reserved78;
+ /* 79 */
+ jvmtiEventReserved reserved79;
+ /* 80 : Resource Exhausted */
+ jvmtiEventResourceExhausted ResourceExhausted;
+ /* 81 : Garbage Collection Start */
+ jvmtiEventGarbageCollectionStart GarbageCollectionStart;
+ /* 82 : Garbage Collection Finish */
+ jvmtiEventGarbageCollectionFinish GarbageCollectionFinish;
+ /* 83 : Object Free */
+ jvmtiEventObjectFree ObjectFree;
+ /* 84 : VM Object Allocation */
+ jvmtiEventVMObjectAlloc VMObjectAlloc;
+} jvmtiEventCallbacks;
+
+
+ /* Function Interface */
+
+typedef struct jvmtiInterface_1_ {
+
+ /* 1 : RESERVED */
+ void *reserved1;
+
+ /* 2 : Set Event Notification Mode */
+ jvmtiError (JNICALL *SetEventNotificationMode) (jvmtiEnv* env,
+ jvmtiEventMode mode,
+ jvmtiEvent event_type,
+ jthread event_thread,
+ ...);
+
+ /* 3 : RESERVED */
+ void *reserved3;
+
+ /* 4 : Get All Threads */
+ jvmtiError (JNICALL *GetAllThreads) (jvmtiEnv* env,
+ jint* threads_count_ptr,
+ jthread** threads_ptr);
+
+ /* 5 : Suspend Thread */
+ jvmtiError (JNICALL *SuspendThread) (jvmtiEnv* env,
+ jthread thread);
+
+ /* 6 : Resume Thread */
+ jvmtiError (JNICALL *ResumeThread) (jvmtiEnv* env,
+ jthread thread);
+
+ /* 7 : Stop Thread */
+ jvmtiError (JNICALL *StopThread) (jvmtiEnv* env,
+ jthread thread,
+ jobject exception);
+
+ /* 8 : Interrupt Thread */
+ jvmtiError (JNICALL *InterruptThread) (jvmtiEnv* env,
+ jthread thread);
+
+ /* 9 : Get Thread Info */
+ jvmtiError (JNICALL *GetThreadInfo) (jvmtiEnv* env,
+ jthread thread,
+ jvmtiThreadInfo* info_ptr);
+
+ /* 10 : Get Owned Monitor Info */
+ jvmtiError (JNICALL *GetOwnedMonitorInfo) (jvmtiEnv* env,
+ jthread thread,
+ jint* owned_monitor_count_ptr,
+ jobject** owned_monitors_ptr);
+
+ /* 11 : Get Current Contended Monitor */
+ jvmtiError (JNICALL *GetCurrentContendedMonitor) (jvmtiEnv* env,
+ jthread thread,
+ jobject* monitor_ptr);
+
+ /* 12 : Run Agent Thread */
+ jvmtiError (JNICALL *RunAgentThread) (jvmtiEnv* env,
+ jthread thread,
+ jvmtiStartFunction proc,
+ const void* arg,
+ jint priority);
+
+ /* 13 : Get Top Thread Groups */
+ jvmtiError (JNICALL *GetTopThreadGroups) (jvmtiEnv* env,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr);
+
+ /* 14 : Get Thread Group Info */
+ jvmtiError (JNICALL *GetThreadGroupInfo) (jvmtiEnv* env,
+ jthreadGroup group,
+ jvmtiThreadGroupInfo* info_ptr);
+
+ /* 15 : Get Thread Group Children */
+ jvmtiError (JNICALL *GetThreadGroupChildren) (jvmtiEnv* env,
+ jthreadGroup group,
+ jint* thread_count_ptr,
+ jthread** threads_ptr,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr);
+
+ /* 16 : Get Frame Count */
+ jvmtiError (JNICALL *GetFrameCount) (jvmtiEnv* env,
+ jthread thread,
+ jint* count_ptr);
+
+ /* 17 : Get Thread State */
+ jvmtiError (JNICALL *GetThreadState) (jvmtiEnv* env,
+ jthread thread,
+ jint* thread_state_ptr);
+
+ /* 18 : Get Current Thread */
+ jvmtiError (JNICALL *GetCurrentThread) (jvmtiEnv* env,
+ jthread* thread_ptr);
+
+ /* 19 : Get Frame Location */
+ jvmtiError (JNICALL *GetFrameLocation) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jmethodID* method_ptr,
+ jlocation* location_ptr);
+
+ /* 20 : Notify Frame Pop */
+ jvmtiError (JNICALL *NotifyFramePop) (jvmtiEnv* env,
+ jthread thread,
+ jint depth);
+
+ /* 21 : Get Local Variable - Object */
+ jvmtiError (JNICALL *GetLocalObject) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jobject* value_ptr);
+
+ /* 22 : Get Local Variable - Int */
+ jvmtiError (JNICALL *GetLocalInt) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jint* value_ptr);
+
+ /* 23 : Get Local Variable - Long */
+ jvmtiError (JNICALL *GetLocalLong) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jlong* value_ptr);
+
+ /* 24 : Get Local Variable - Float */
+ jvmtiError (JNICALL *GetLocalFloat) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jfloat* value_ptr);
+
+ /* 25 : Get Local Variable - Double */
+ jvmtiError (JNICALL *GetLocalDouble) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jdouble* value_ptr);
+
+ /* 26 : Set Local Variable - Object */
+ jvmtiError (JNICALL *SetLocalObject) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jobject value);
+
+ /* 27 : Set Local Variable - Int */
+ jvmtiError (JNICALL *SetLocalInt) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jint value);
+
+ /* 28 : Set Local Variable - Long */
+ jvmtiError (JNICALL *SetLocalLong) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jlong value);
+
+ /* 29 : Set Local Variable - Float */
+ jvmtiError (JNICALL *SetLocalFloat) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jfloat value);
+
+ /* 30 : Set Local Variable - Double */
+ jvmtiError (JNICALL *SetLocalDouble) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ jdouble value);
+
+ /* 31 : Create Raw Monitor */
+ jvmtiError (JNICALL *CreateRawMonitor) (jvmtiEnv* env,
+ const char* name,
+ jrawMonitorID* monitor_ptr);
+
+ /* 32 : Destroy Raw Monitor */
+ jvmtiError (JNICALL *DestroyRawMonitor) (jvmtiEnv* env,
+ jrawMonitorID monitor);
+
+ /* 33 : Raw Monitor Enter */
+ jvmtiError (JNICALL *RawMonitorEnter) (jvmtiEnv* env,
+ jrawMonitorID monitor);
+
+ /* 34 : Raw Monitor Exit */
+ jvmtiError (JNICALL *RawMonitorExit) (jvmtiEnv* env,
+ jrawMonitorID monitor);
+
+ /* 35 : Raw Monitor Wait */
+ jvmtiError (JNICALL *RawMonitorWait) (jvmtiEnv* env,
+ jrawMonitorID monitor,
+ jlong millis);
+
+ /* 36 : Raw Monitor Notify */
+ jvmtiError (JNICALL *RawMonitorNotify) (jvmtiEnv* env,
+ jrawMonitorID monitor);
+
+ /* 37 : Raw Monitor Notify All */
+ jvmtiError (JNICALL *RawMonitorNotifyAll) (jvmtiEnv* env,
+ jrawMonitorID monitor);
+
+ /* 38 : Set Breakpoint */
+ jvmtiError (JNICALL *SetBreakpoint) (jvmtiEnv* env,
+ jmethodID method,
+ jlocation location);
+
+ /* 39 : Clear Breakpoint */
+ jvmtiError (JNICALL *ClearBreakpoint) (jvmtiEnv* env,
+ jmethodID method,
+ jlocation location);
+
+ /* 40 : RESERVED */
+ void *reserved40;
+
+ /* 41 : Set Field Access Watch */
+ jvmtiError (JNICALL *SetFieldAccessWatch) (jvmtiEnv* env,
+ jclass klass,
+ jfieldID field);
+
+ /* 42 : Clear Field Access Watch */
+ jvmtiError (JNICALL *ClearFieldAccessWatch) (jvmtiEnv* env,
+ jclass klass,
+ jfieldID field);
+
+ /* 43 : Set Field Modification Watch */
+ jvmtiError (JNICALL *SetFieldModificationWatch) (jvmtiEnv* env,
+ jclass klass,
+ jfieldID field);
+
+ /* 44 : Clear Field Modification Watch */
+ jvmtiError (JNICALL *ClearFieldModificationWatch) (jvmtiEnv* env,
+ jclass klass,
+ jfieldID field);
+
+ /* 45 : Is Modifiable Class */
+ jvmtiError (JNICALL *IsModifiableClass) (jvmtiEnv* env,
+ jclass klass,
+ jboolean* is_modifiable_class_ptr);
+
+ /* 46 : Allocate */
+ jvmtiError (JNICALL *Allocate) (jvmtiEnv* env,
+ jlong size,
+ unsigned char** mem_ptr);
+
+ /* 47 : Deallocate */
+ jvmtiError (JNICALL *Deallocate) (jvmtiEnv* env,
+ unsigned char* mem);
+
+ /* 48 : Get Class Signature */
+ jvmtiError (JNICALL *GetClassSignature) (jvmtiEnv* env,
+ jclass klass,
+ char** signature_ptr,
+ char** generic_ptr);
+
+ /* 49 : Get Class Status */
+ jvmtiError (JNICALL *GetClassStatus) (jvmtiEnv* env,
+ jclass klass,
+ jint* status_ptr);
+
+ /* 50 : Get Source File Name */
+ jvmtiError (JNICALL *GetSourceFileName) (jvmtiEnv* env,
+ jclass klass,
+ char** source_name_ptr);
+
+ /* 51 : Get Class Modifiers */
+ jvmtiError (JNICALL *GetClassModifiers) (jvmtiEnv* env,
+ jclass klass,
+ jint* modifiers_ptr);
+
+ /* 52 : Get Class Methods */
+ jvmtiError (JNICALL *GetClassMethods) (jvmtiEnv* env,
+ jclass klass,
+ jint* method_count_ptr,
+ jmethodID** methods_ptr);
+
+ /* 53 : Get Class Fields */
+ jvmtiError (JNICALL *GetClassFields) (jvmtiEnv* env,
+ jclass klass,
+ jint* field_count_ptr,
+ jfieldID** fields_ptr);
+
+ /* 54 : Get Implemented Interfaces */
+ jvmtiError (JNICALL *GetImplementedInterfaces) (jvmtiEnv* env,
+ jclass klass,
+ jint* interface_count_ptr,
+ jclass** interfaces_ptr);
+
+ /* 55 : Is Interface */
+ jvmtiError (JNICALL *IsInterface) (jvmtiEnv* env,
+ jclass klass,
+ jboolean* is_interface_ptr);
+
+ /* 56 : Is Array Class */
+ jvmtiError (JNICALL *IsArrayClass) (jvmtiEnv* env,
+ jclass klass,
+ jboolean* is_array_class_ptr);
+
+ /* 57 : Get Class Loader */
+ jvmtiError (JNICALL *GetClassLoader) (jvmtiEnv* env,
+ jclass klass,
+ jobject* classloader_ptr);
+
+ /* 58 : Get Object Hash Code */
+ jvmtiError (JNICALL *GetObjectHashCode) (jvmtiEnv* env,
+ jobject object,
+ jint* hash_code_ptr);
+
+ /* 59 : Get Object Monitor Usage */
+ jvmtiError (JNICALL *GetObjectMonitorUsage) (jvmtiEnv* env,
+ jobject object,
+ jvmtiMonitorUsage* info_ptr);
+
+ /* 60 : Get Field Name (and Signature) */
+ jvmtiError (JNICALL *GetFieldName) (jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr);
+
+ /* 61 : Get Field Declaring Class */
+ jvmtiError (JNICALL *GetFieldDeclaringClass) (jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jclass* declaring_class_ptr);
+
+ /* 62 : Get Field Modifiers */
+ jvmtiError (JNICALL *GetFieldModifiers) (jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jint* modifiers_ptr);
+
+ /* 63 : Is Field Synthetic */
+ jvmtiError (JNICALL *IsFieldSynthetic) (jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jboolean* is_synthetic_ptr);
+
+ /* 64 : Get Method Name (and Signature) */
+ jvmtiError (JNICALL *GetMethodName) (jvmtiEnv* env,
+ jmethodID method,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr);
+
+ /* 65 : Get Method Declaring Class */
+ jvmtiError (JNICALL *GetMethodDeclaringClass) (jvmtiEnv* env,
+ jmethodID method,
+ jclass* declaring_class_ptr);
+
+ /* 66 : Get Method Modifiers */
+ jvmtiError (JNICALL *GetMethodModifiers) (jvmtiEnv* env,
+ jmethodID method,
+ jint* modifiers_ptr);
+
+ /* 67 : RESERVED */
+ void *reserved67;
+
+ /* 68 : Get Max Locals */
+ jvmtiError (JNICALL *GetMaxLocals) (jvmtiEnv* env,
+ jmethodID method,
+ jint* max_ptr);
+
+ /* 69 : Get Arguments Size */
+ jvmtiError (JNICALL *GetArgumentsSize) (jvmtiEnv* env,
+ jmethodID method,
+ jint* size_ptr);
+
+ /* 70 : Get Line Number Table */
+ jvmtiError (JNICALL *GetLineNumberTable) (jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLineNumberEntry** table_ptr);
+
+ /* 71 : Get Method Location */
+ jvmtiError (JNICALL *GetMethodLocation) (jvmtiEnv* env,
+ jmethodID method,
+ jlocation* start_location_ptr,
+ jlocation* end_location_ptr);
+
+ /* 72 : Get Local Variable Table */
+ jvmtiError (JNICALL *GetLocalVariableTable) (jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLocalVariableEntry** table_ptr);
+
+ /* 73 : Set Native Method Prefix */
+ jvmtiError (JNICALL *SetNativeMethodPrefix) (jvmtiEnv* env,
+ const char* prefix);
+
+ /* 74 : Set Native Method Prefixes */
+ jvmtiError (JNICALL *SetNativeMethodPrefixes) (jvmtiEnv* env,
+ jint prefix_count,
+ char** prefixes);
+
+ /* 75 : Get Bytecodes */
+ jvmtiError (JNICALL *GetBytecodes) (jvmtiEnv* env,
+ jmethodID method,
+ jint* bytecode_count_ptr,
+ unsigned char** bytecodes_ptr);
+
+ /* 76 : Is Method Native */
+ jvmtiError (JNICALL *IsMethodNative) (jvmtiEnv* env,
+ jmethodID method,
+ jboolean* is_native_ptr);
+
+ /* 77 : Is Method Synthetic */
+ jvmtiError (JNICALL *IsMethodSynthetic) (jvmtiEnv* env,
+ jmethodID method,
+ jboolean* is_synthetic_ptr);
+
+ /* 78 : Get Loaded Classes */
+ jvmtiError (JNICALL *GetLoadedClasses) (jvmtiEnv* env,
+ jint* class_count_ptr,
+ jclass** classes_ptr);
+
+ /* 79 : Get Classloader Classes */
+ jvmtiError (JNICALL *GetClassLoaderClasses) (jvmtiEnv* env,
+ jobject initiating_loader,
+ jint* class_count_ptr,
+ jclass** classes_ptr);
+
+ /* 80 : Pop Frame */
+ jvmtiError (JNICALL *PopFrame) (jvmtiEnv* env,
+ jthread thread);
+
+ /* 81 : Force Early Return - Object */
+ jvmtiError (JNICALL *ForceEarlyReturnObject) (jvmtiEnv* env,
+ jthread thread,
+ jobject value);
+
+ /* 82 : Force Early Return - Int */
+ jvmtiError (JNICALL *ForceEarlyReturnInt) (jvmtiEnv* env,
+ jthread thread,
+ jint value);
+
+ /* 83 : Force Early Return - Long */
+ jvmtiError (JNICALL *ForceEarlyReturnLong) (jvmtiEnv* env,
+ jthread thread,
+ jlong value);
+
+ /* 84 : Force Early Return - Float */
+ jvmtiError (JNICALL *ForceEarlyReturnFloat) (jvmtiEnv* env,
+ jthread thread,
+ jfloat value);
+
+ /* 85 : Force Early Return - Double */
+ jvmtiError (JNICALL *ForceEarlyReturnDouble) (jvmtiEnv* env,
+ jthread thread,
+ jdouble value);
+
+ /* 86 : Force Early Return - Void */
+ jvmtiError (JNICALL *ForceEarlyReturnVoid) (jvmtiEnv* env,
+ jthread thread);
+
+ /* 87 : Redefine Classes */
+ jvmtiError (JNICALL *RedefineClasses) (jvmtiEnv* env,
+ jint class_count,
+ const jvmtiClassDefinition* class_definitions);
+
+ /* 88 : Get Version Number */
+ jvmtiError (JNICALL *GetVersionNumber) (jvmtiEnv* env,
+ jint* version_ptr);
+
+ /* 89 : Get Capabilities */
+ jvmtiError (JNICALL *GetCapabilities) (jvmtiEnv* env,
+ jvmtiCapabilities* capabilities_ptr);
+
+ /* 90 : Get Source Debug Extension */
+ jvmtiError (JNICALL *GetSourceDebugExtension) (jvmtiEnv* env,
+ jclass klass,
+ char** source_debug_extension_ptr);
+
+ /* 91 : Is Method Obsolete */
+ jvmtiError (JNICALL *IsMethodObsolete) (jvmtiEnv* env,
+ jmethodID method,
+ jboolean* is_obsolete_ptr);
+
+ /* 92 : Suspend Thread List */
+ jvmtiError (JNICALL *SuspendThreadList) (jvmtiEnv* env,
+ jint request_count,
+ const jthread* request_list,
+ jvmtiError* results);
+
+ /* 93 : Resume Thread List */
+ jvmtiError (JNICALL *ResumeThreadList) (jvmtiEnv* env,
+ jint request_count,
+ const jthread* request_list,
+ jvmtiError* results);
+
+ /* 94 : RESERVED */
+ void *reserved94;
+
+ /* 95 : RESERVED */
+ void *reserved95;
+
+ /* 96 : RESERVED */
+ void *reserved96;
+
+ /* 97 : RESERVED */
+ void *reserved97;
+
+ /* 98 : RESERVED */
+ void *reserved98;
+
+ /* 99 : RESERVED */
+ void *reserved99;
+
+ /* 100 : Get All Stack Traces */
+ jvmtiError (JNICALL *GetAllStackTraces) (jvmtiEnv* env,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr,
+ jint* thread_count_ptr);
+
+ /* 101 : Get Thread List Stack Traces */
+ jvmtiError (JNICALL *GetThreadListStackTraces) (jvmtiEnv* env,
+ jint thread_count,
+ const jthread* thread_list,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr);
+
+ /* 102 : Get Thread Local Storage */
+ jvmtiError (JNICALL *GetThreadLocalStorage) (jvmtiEnv* env,
+ jthread thread,
+ void** data_ptr);
+
+ /* 103 : Set Thread Local Storage */
+ jvmtiError (JNICALL *SetThreadLocalStorage) (jvmtiEnv* env,
+ jthread thread,
+ const void* data);
+
+ /* 104 : Get Stack Trace */
+ jvmtiError (JNICALL *GetStackTrace) (jvmtiEnv* env,
+ jthread thread,
+ jint start_depth,
+ jint max_frame_count,
+ jvmtiFrameInfo* frame_buffer,
+ jint* count_ptr);
+
+ /* 105 : RESERVED */
+ void *reserved105;
+
+ /* 106 : Get Tag */
+ jvmtiError (JNICALL *GetTag) (jvmtiEnv* env,
+ jobject object,
+ jlong* tag_ptr);
+
+ /* 107 : Set Tag */
+ jvmtiError (JNICALL *SetTag) (jvmtiEnv* env,
+ jobject object,
+ jlong tag);
+
+ /* 108 : Force Garbage Collection */
+ jvmtiError (JNICALL *ForceGarbageCollection) (jvmtiEnv* env);
+
+ /* 109 : Iterate Over Objects Reachable From Object */
+ jvmtiError (JNICALL *IterateOverObjectsReachableFromObject) (jvmtiEnv* env,
+ jobject object,
+ jvmtiObjectReferenceCallback object_reference_callback,
+ const void* user_data);
+
+ /* 110 : Iterate Over Reachable Objects */
+ jvmtiError (JNICALL *IterateOverReachableObjects) (jvmtiEnv* env,
+ jvmtiHeapRootCallback heap_root_callback,
+ jvmtiStackReferenceCallback stack_ref_callback,
+ jvmtiObjectReferenceCallback object_ref_callback,
+ const void* user_data);
+
+ /* 111 : Iterate Over Heap */
+ jvmtiError (JNICALL *IterateOverHeap) (jvmtiEnv* env,
+ jvmtiHeapObjectFilter object_filter,
+ jvmtiHeapObjectCallback heap_object_callback,
+ const void* user_data);
+
+ /* 112 : Iterate Over Instances Of Class */
+ jvmtiError (JNICALL *IterateOverInstancesOfClass) (jvmtiEnv* env,
+ jclass klass,
+ jvmtiHeapObjectFilter object_filter,
+ jvmtiHeapObjectCallback heap_object_callback,
+ const void* user_data);
+
+ /* 113 : RESERVED */
+ void *reserved113;
+
+ /* 114 : Get Objects With Tags */
+ jvmtiError (JNICALL *GetObjectsWithTags) (jvmtiEnv* env,
+ jint tag_count,
+ const jlong* tags,
+ jint* count_ptr,
+ jobject** object_result_ptr,
+ jlong** tag_result_ptr);
+
+ /* 115 : Follow References */
+ jvmtiError (JNICALL *FollowReferences) (jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ jobject initial_object,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data);
+
+ /* 116 : Iterate Through Heap */
+ jvmtiError (JNICALL *IterateThroughHeap) (jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data);
+
+ /* 117 : RESERVED */
+ void *reserved117;
+
+ /* 118 : RESERVED */
+ void *reserved118;
+
+ /* 119 : RESERVED */
+ void *reserved119;
+
+ /* 120 : Set JNI Function Table */
+ jvmtiError (JNICALL *SetJNIFunctionTable) (jvmtiEnv* env,
+ const jniNativeInterface* function_table);
+
+ /* 121 : Get JNI Function Table */
+ jvmtiError (JNICALL *GetJNIFunctionTable) (jvmtiEnv* env,
+ jniNativeInterface** function_table);
+
+ /* 122 : Set Event Callbacks */
+ jvmtiError (JNICALL *SetEventCallbacks) (jvmtiEnv* env,
+ const jvmtiEventCallbacks* callbacks,
+ jint size_of_callbacks);
+
+ /* 123 : Generate Events */
+ jvmtiError (JNICALL *GenerateEvents) (jvmtiEnv* env,
+ jvmtiEvent event_type);
+
+ /* 124 : Get Extension Functions */
+ jvmtiError (JNICALL *GetExtensionFunctions) (jvmtiEnv* env,
+ jint* extension_count_ptr,
+ jvmtiExtensionFunctionInfo** extensions);
+
+ /* 125 : Get Extension Events */
+ jvmtiError (JNICALL *GetExtensionEvents) (jvmtiEnv* env,
+ jint* extension_count_ptr,
+ jvmtiExtensionEventInfo** extensions);
+
+ /* 126 : Set Extension Event Callback */
+ jvmtiError (JNICALL *SetExtensionEventCallback) (jvmtiEnv* env,
+ jint extension_event_index,
+ jvmtiExtensionEvent callback);
+
+ /* 127 : Dispose Environment */
+ jvmtiError (JNICALL *DisposeEnvironment) (jvmtiEnv* env);
+
+ /* 128 : Get Error Name */
+ jvmtiError (JNICALL *GetErrorName) (jvmtiEnv* env,
+ jvmtiError error,
+ char** name_ptr);
+
+ /* 129 : Get JLocation Format */
+ jvmtiError (JNICALL *GetJLocationFormat) (jvmtiEnv* env,
+ jvmtiJlocationFormat* format_ptr);
+
+ /* 130 : Get System Properties */
+ jvmtiError (JNICALL *GetSystemProperties) (jvmtiEnv* env,
+ jint* count_ptr,
+ char*** property_ptr);
+
+ /* 131 : Get System Property */
+ jvmtiError (JNICALL *GetSystemProperty) (jvmtiEnv* env,
+ const char* property,
+ char** value_ptr);
+
+ /* 132 : Set System Property */
+ jvmtiError (JNICALL *SetSystemProperty) (jvmtiEnv* env,
+ const char* property,
+ const char* value);
+
+ /* 133 : Get Phase */
+ jvmtiError (JNICALL *GetPhase) (jvmtiEnv* env,
+ jvmtiPhase* phase_ptr);
+
+ /* 134 : Get Current Thread CPU Timer Information */
+ jvmtiError (JNICALL *GetCurrentThreadCpuTimerInfo) (jvmtiEnv* env,
+ jvmtiTimerInfo* info_ptr);
+
+ /* 135 : Get Current Thread CPU Time */
+ jvmtiError (JNICALL *GetCurrentThreadCpuTime) (jvmtiEnv* env,
+ jlong* nanos_ptr);
+
+ /* 136 : Get Thread CPU Timer Information */
+ jvmtiError (JNICALL *GetThreadCpuTimerInfo) (jvmtiEnv* env,
+ jvmtiTimerInfo* info_ptr);
+
+ /* 137 : Get Thread CPU Time */
+ jvmtiError (JNICALL *GetThreadCpuTime) (jvmtiEnv* env,
+ jthread thread,
+ jlong* nanos_ptr);
+
+ /* 138 : Get Timer Information */
+ jvmtiError (JNICALL *GetTimerInfo) (jvmtiEnv* env,
+ jvmtiTimerInfo* info_ptr);
+
+ /* 139 : Get Time */
+ jvmtiError (JNICALL *GetTime) (jvmtiEnv* env,
+ jlong* nanos_ptr);
+
+ /* 140 : Get Potential Capabilities */
+ jvmtiError (JNICALL *GetPotentialCapabilities) (jvmtiEnv* env,
+ jvmtiCapabilities* capabilities_ptr);
+
+ /* 141 : RESERVED */
+ void *reserved141;
+
+ /* 142 : Add Capabilities */
+ jvmtiError (JNICALL *AddCapabilities) (jvmtiEnv* env,
+ const jvmtiCapabilities* capabilities_ptr);
+
+ /* 143 : Relinquish Capabilities */
+ jvmtiError (JNICALL *RelinquishCapabilities) (jvmtiEnv* env,
+ const jvmtiCapabilities* capabilities_ptr);
+
+ /* 144 : Get Available Processors */
+ jvmtiError (JNICALL *GetAvailableProcessors) (jvmtiEnv* env,
+ jint* processor_count_ptr);
+
+ /* 145 : Get Class Version Numbers */
+ jvmtiError (JNICALL *GetClassVersionNumbers) (jvmtiEnv* env,
+ jclass klass,
+ jint* minor_version_ptr,
+ jint* major_version_ptr);
+
+ /* 146 : Get Constant Pool */
+ jvmtiError (JNICALL *GetConstantPool) (jvmtiEnv* env,
+ jclass klass,
+ jint* constant_pool_count_ptr,
+ jint* constant_pool_byte_count_ptr,
+ unsigned char** constant_pool_bytes_ptr);
+
+ /* 147 : Get Environment Local Storage */
+ jvmtiError (JNICALL *GetEnvironmentLocalStorage) (jvmtiEnv* env,
+ void** data_ptr);
+
+ /* 148 : Set Environment Local Storage */
+ jvmtiError (JNICALL *SetEnvironmentLocalStorage) (jvmtiEnv* env,
+ const void* data);
+
+ /* 149 : Add To Bootstrap Class Loader Search */
+ jvmtiError (JNICALL *AddToBootstrapClassLoaderSearch) (jvmtiEnv* env,
+ const char* segment);
+
+ /* 150 : Set Verbose Flag */
+ jvmtiError (JNICALL *SetVerboseFlag) (jvmtiEnv* env,
+ jvmtiVerboseFlag flag,
+ jboolean value);
+
+ /* 151 : Add To System Class Loader Search */
+ jvmtiError (JNICALL *AddToSystemClassLoaderSearch) (jvmtiEnv* env,
+ const char* segment);
+
+ /* 152 : Retransform Classes */
+ jvmtiError (JNICALL *RetransformClasses) (jvmtiEnv* env,
+ jint class_count,
+ const jclass* classes);
+
+ /* 153 : Get Owned Monitor Stack Depth Info */
+ jvmtiError (JNICALL *GetOwnedMonitorStackDepthInfo) (jvmtiEnv* env,
+ jthread thread,
+ jint* monitor_info_count_ptr,
+ jvmtiMonitorStackDepthInfo** monitor_info_ptr);
+
+ /* 154 : Get Object Size */
+ jvmtiError (JNICALL *GetObjectSize) (jvmtiEnv* env,
+ jobject object,
+ jlong* size_ptr);
+
+ /* 155 : Get Local Instance */
+ jvmtiError (JNICALL *GetLocalInstance) (jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jobject* value_ptr);
+
+} jvmtiInterface_1;
+
+struct _jvmtiEnv {
+ const struct jvmtiInterface_1_ *functions;
+#ifdef __cplusplus
+
+
+ jvmtiError Allocate(jlong size,
+ unsigned char** mem_ptr) {
+ return functions->Allocate(this, size, mem_ptr);
+ }
+
+ jvmtiError Deallocate(unsigned char* mem) {
+ return functions->Deallocate(this, mem);
+ }
+
+ jvmtiError GetThreadState(jthread thread,
+ jint* thread_state_ptr) {
+ return functions->GetThreadState(this, thread, thread_state_ptr);
+ }
+
+ jvmtiError GetCurrentThread(jthread* thread_ptr) {
+ return functions->GetCurrentThread(this, thread_ptr);
+ }
+
+ jvmtiError GetAllThreads(jint* threads_count_ptr,
+ jthread** threads_ptr) {
+ return functions->GetAllThreads(this, threads_count_ptr, threads_ptr);
+ }
+
+ jvmtiError SuspendThread(jthread thread) {
+ return functions->SuspendThread(this, thread);
+ }
+
+ jvmtiError SuspendThreadList(jint request_count,
+ const jthread* request_list,
+ jvmtiError* results) {
+ return functions->SuspendThreadList(this, request_count, request_list, results);
+ }
+
+ jvmtiError ResumeThread(jthread thread) {
+ return functions->ResumeThread(this, thread);
+ }
+
+ jvmtiError ResumeThreadList(jint request_count,
+ const jthread* request_list,
+ jvmtiError* results) {
+ return functions->ResumeThreadList(this, request_count, request_list, results);
+ }
+
+ jvmtiError StopThread(jthread thread,
+ jobject exception) {
+ return functions->StopThread(this, thread, exception);
+ }
+
+ jvmtiError InterruptThread(jthread thread) {
+ return functions->InterruptThread(this, thread);
+ }
+
+ jvmtiError GetThreadInfo(jthread thread,
+ jvmtiThreadInfo* info_ptr) {
+ return functions->GetThreadInfo(this, thread, info_ptr);
+ }
+
+ jvmtiError GetOwnedMonitorInfo(jthread thread,
+ jint* owned_monitor_count_ptr,
+ jobject** owned_monitors_ptr) {
+ return functions->GetOwnedMonitorInfo(this, thread, owned_monitor_count_ptr, owned_monitors_ptr);
+ }
+
+ jvmtiError GetOwnedMonitorStackDepthInfo(jthread thread,
+ jint* monitor_info_count_ptr,
+ jvmtiMonitorStackDepthInfo** monitor_info_ptr) {
+ return functions->GetOwnedMonitorStackDepthInfo(this, thread, monitor_info_count_ptr, monitor_info_ptr);
+ }
+
+ jvmtiError GetCurrentContendedMonitor(jthread thread,
+ jobject* monitor_ptr) {
+ return functions->GetCurrentContendedMonitor(this, thread, monitor_ptr);
+ }
+
+ jvmtiError RunAgentThread(jthread thread,
+ jvmtiStartFunction proc,
+ const void* arg,
+ jint priority) {
+ return functions->RunAgentThread(this, thread, proc, arg, priority);
+ }
+
+ jvmtiError SetThreadLocalStorage(jthread thread,
+ const void* data) {
+ return functions->SetThreadLocalStorage(this, thread, data);
+ }
+
+ jvmtiError GetThreadLocalStorage(jthread thread,
+ void** data_ptr) {
+ return functions->GetThreadLocalStorage(this, thread, data_ptr);
+ }
+
+ jvmtiError GetTopThreadGroups(jint* group_count_ptr,
+ jthreadGroup** groups_ptr) {
+ return functions->GetTopThreadGroups(this, group_count_ptr, groups_ptr);
+ }
+
+ jvmtiError GetThreadGroupInfo(jthreadGroup group,
+ jvmtiThreadGroupInfo* info_ptr) {
+ return functions->GetThreadGroupInfo(this, group, info_ptr);
+ }
+
+ jvmtiError GetThreadGroupChildren(jthreadGroup group,
+ jint* thread_count_ptr,
+ jthread** threads_ptr,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr) {
+ return functions->GetThreadGroupChildren(this, group, thread_count_ptr, threads_ptr, group_count_ptr, groups_ptr);
+ }
+
+ jvmtiError GetStackTrace(jthread thread,
+ jint start_depth,
+ jint max_frame_count,
+ jvmtiFrameInfo* frame_buffer,
+ jint* count_ptr) {
+ return functions->GetStackTrace(this, thread, start_depth, max_frame_count, frame_buffer, count_ptr);
+ }
+
+ jvmtiError GetAllStackTraces(jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr,
+ jint* thread_count_ptr) {
+ return functions->GetAllStackTraces(this, max_frame_count, stack_info_ptr, thread_count_ptr);
+ }
+
+ jvmtiError GetThreadListStackTraces(jint thread_count,
+ const jthread* thread_list,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr) {
+ return functions->GetThreadListStackTraces(this, thread_count, thread_list, max_frame_count, stack_info_ptr);
+ }
+
+ jvmtiError GetFrameCount(jthread thread,
+ jint* count_ptr) {
+ return functions->GetFrameCount(this, thread, count_ptr);
+ }
+
+ jvmtiError PopFrame(jthread thread) {
+ return functions->PopFrame(this, thread);
+ }
+
+ jvmtiError GetFrameLocation(jthread thread,
+ jint depth,
+ jmethodID* method_ptr,
+ jlocation* location_ptr) {
+ return functions->GetFrameLocation(this, thread, depth, method_ptr, location_ptr);
+ }
+
+ jvmtiError NotifyFramePop(jthread thread,
+ jint depth) {
+ return functions->NotifyFramePop(this, thread, depth);
+ }
+
+ jvmtiError ForceEarlyReturnObject(jthread thread,
+ jobject value) {
+ return functions->ForceEarlyReturnObject(this, thread, value);
+ }
+
+ jvmtiError ForceEarlyReturnInt(jthread thread,
+ jint value) {
+ return functions->ForceEarlyReturnInt(this, thread, value);
+ }
+
+ jvmtiError ForceEarlyReturnLong(jthread thread,
+ jlong value) {
+ return functions->ForceEarlyReturnLong(this, thread, value);
+ }
+
+ jvmtiError ForceEarlyReturnFloat(jthread thread,
+ jfloat value) {
+ return functions->ForceEarlyReturnFloat(this, thread, value);
+ }
+
+ jvmtiError ForceEarlyReturnDouble(jthread thread,
+ jdouble value) {
+ return functions->ForceEarlyReturnDouble(this, thread, value);
+ }
+
+ jvmtiError ForceEarlyReturnVoid(jthread thread) {
+ return functions->ForceEarlyReturnVoid(this, thread);
+ }
+
+ jvmtiError FollowReferences(jint heap_filter,
+ jclass klass,
+ jobject initial_object,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ return functions->FollowReferences(this, heap_filter, klass, initial_object, callbacks, user_data);
+ }
+
+ jvmtiError IterateThroughHeap(jint heap_filter,
+ jclass klass,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ return functions->IterateThroughHeap(this, heap_filter, klass, callbacks, user_data);
+ }
+
+ jvmtiError GetTag(jobject object,
+ jlong* tag_ptr) {
+ return functions->GetTag(this, object, tag_ptr);
+ }
+
+ jvmtiError SetTag(jobject object,
+ jlong tag) {
+ return functions->SetTag(this, object, tag);
+ }
+
+ jvmtiError GetObjectsWithTags(jint tag_count,
+ const jlong* tags,
+ jint* count_ptr,
+ jobject** object_result_ptr,
+ jlong** tag_result_ptr) {
+ return functions->GetObjectsWithTags(this, tag_count, tags, count_ptr, object_result_ptr, tag_result_ptr);
+ }
+
+ jvmtiError ForceGarbageCollection() {
+ return functions->ForceGarbageCollection(this);
+ }
+
+ jvmtiError IterateOverObjectsReachableFromObject(jobject object,
+ jvmtiObjectReferenceCallback object_reference_callback,
+ const void* user_data) {
+ return functions->IterateOverObjectsReachableFromObject(this, object, object_reference_callback, user_data);
+ }
+
+ jvmtiError IterateOverReachableObjects(jvmtiHeapRootCallback heap_root_callback,
+ jvmtiStackReferenceCallback stack_ref_callback,
+ jvmtiObjectReferenceCallback object_ref_callback,
+ const void* user_data) {
+ return functions->IterateOverReachableObjects(this, heap_root_callback, stack_ref_callback, object_ref_callback, user_data);
+ }
+
+ jvmtiError IterateOverHeap(jvmtiHeapObjectFilter object_filter,
+ jvmtiHeapObjectCallback heap_object_callback,
+ const void* user_data) {
+ return functions->IterateOverHeap(this, object_filter, heap_object_callback, user_data);
+ }
+
+ jvmtiError IterateOverInstancesOfClass(jclass klass,
+ jvmtiHeapObjectFilter object_filter,
+ jvmtiHeapObjectCallback heap_object_callback,
+ const void* user_data) {
+ return functions->IterateOverInstancesOfClass(this, klass, object_filter, heap_object_callback, user_data);
+ }
+
+ jvmtiError GetLocalObject(jthread thread,
+ jint depth,
+ jint slot,
+ jobject* value_ptr) {
+ return functions->GetLocalObject(this, thread, depth, slot, value_ptr);
+ }
+
+ jvmtiError GetLocalInstance(jthread thread,
+ jint depth,
+ jobject* value_ptr) {
+ return functions->GetLocalInstance(this, thread, depth, value_ptr);
+ }
+
+ jvmtiError GetLocalInt(jthread thread,
+ jint depth,
+ jint slot,
+ jint* value_ptr) {
+ return functions->GetLocalInt(this, thread, depth, slot, value_ptr);
+ }
+
+ jvmtiError GetLocalLong(jthread thread,
+ jint depth,
+ jint slot,
+ jlong* value_ptr) {
+ return functions->GetLocalLong(this, thread, depth, slot, value_ptr);
+ }
+
+ jvmtiError GetLocalFloat(jthread thread,
+ jint depth,
+ jint slot,
+ jfloat* value_ptr) {
+ return functions->GetLocalFloat(this, thread, depth, slot, value_ptr);
+ }
+
+ jvmtiError GetLocalDouble(jthread thread,
+ jint depth,
+ jint slot,
+ jdouble* value_ptr) {
+ return functions->GetLocalDouble(this, thread, depth, slot, value_ptr);
+ }
+
+ jvmtiError SetLocalObject(jthread thread,
+ jint depth,
+ jint slot,
+ jobject value) {
+ return functions->SetLocalObject(this, thread, depth, slot, value);
+ }
+
+ jvmtiError SetLocalInt(jthread thread,
+ jint depth,
+ jint slot,
+ jint value) {
+ return functions->SetLocalInt(this, thread, depth, slot, value);
+ }
+
+ jvmtiError SetLocalLong(jthread thread,
+ jint depth,
+ jint slot,
+ jlong value) {
+ return functions->SetLocalLong(this, thread, depth, slot, value);
+ }
+
+ jvmtiError SetLocalFloat(jthread thread,
+ jint depth,
+ jint slot,
+ jfloat value) {
+ return functions->SetLocalFloat(this, thread, depth, slot, value);
+ }
+
+ jvmtiError SetLocalDouble(jthread thread,
+ jint depth,
+ jint slot,
+ jdouble value) {
+ return functions->SetLocalDouble(this, thread, depth, slot, value);
+ }
+
+ jvmtiError SetBreakpoint(jmethodID method,
+ jlocation location) {
+ return functions->SetBreakpoint(this, method, location);
+ }
+
+ jvmtiError ClearBreakpoint(jmethodID method,
+ jlocation location) {
+ return functions->ClearBreakpoint(this, method, location);
+ }
+
+ jvmtiError SetFieldAccessWatch(jclass klass,
+ jfieldID field) {
+ return functions->SetFieldAccessWatch(this, klass, field);
+ }
+
+ jvmtiError ClearFieldAccessWatch(jclass klass,
+ jfieldID field) {
+ return functions->ClearFieldAccessWatch(this, klass, field);
+ }
+
+ jvmtiError SetFieldModificationWatch(jclass klass,
+ jfieldID field) {
+ return functions->SetFieldModificationWatch(this, klass, field);
+ }
+
+ jvmtiError ClearFieldModificationWatch(jclass klass,
+ jfieldID field) {
+ return functions->ClearFieldModificationWatch(this, klass, field);
+ }
+
+ jvmtiError GetLoadedClasses(jint* class_count_ptr,
+ jclass** classes_ptr) {
+ return functions->GetLoadedClasses(this, class_count_ptr, classes_ptr);
+ }
+
+ jvmtiError GetClassLoaderClasses(jobject initiating_loader,
+ jint* class_count_ptr,
+ jclass** classes_ptr) {
+ return functions->GetClassLoaderClasses(this, initiating_loader, class_count_ptr, classes_ptr);
+ }
+
+ jvmtiError GetClassSignature(jclass klass,
+ char** signature_ptr,
+ char** generic_ptr) {
+ return functions->GetClassSignature(this, klass, signature_ptr, generic_ptr);
+ }
+
+ jvmtiError GetClassStatus(jclass klass,
+ jint* status_ptr) {
+ return functions->GetClassStatus(this, klass, status_ptr);
+ }
+
+ jvmtiError GetSourceFileName(jclass klass,
+ char** source_name_ptr) {
+ return functions->GetSourceFileName(this, klass, source_name_ptr);
+ }
+
+ jvmtiError GetClassModifiers(jclass klass,
+ jint* modifiers_ptr) {
+ return functions->GetClassModifiers(this, klass, modifiers_ptr);
+ }
+
+ jvmtiError GetClassMethods(jclass klass,
+ jint* method_count_ptr,
+ jmethodID** methods_ptr) {
+ return functions->GetClassMethods(this, klass, method_count_ptr, methods_ptr);
+ }
+
+ jvmtiError GetClassFields(jclass klass,
+ jint* field_count_ptr,
+ jfieldID** fields_ptr) {
+ return functions->GetClassFields(this, klass, field_count_ptr, fields_ptr);
+ }
+
+ jvmtiError GetImplementedInterfaces(jclass klass,
+ jint* interface_count_ptr,
+ jclass** interfaces_ptr) {
+ return functions->GetImplementedInterfaces(this, klass, interface_count_ptr, interfaces_ptr);
+ }
+
+ jvmtiError GetClassVersionNumbers(jclass klass,
+ jint* minor_version_ptr,
+ jint* major_version_ptr) {
+ return functions->GetClassVersionNumbers(this, klass, minor_version_ptr, major_version_ptr);
+ }
+
+ jvmtiError GetConstantPool(jclass klass,
+ jint* constant_pool_count_ptr,
+ jint* constant_pool_byte_count_ptr,
+ unsigned char** constant_pool_bytes_ptr) {
+ return functions->GetConstantPool(this, klass, constant_pool_count_ptr, constant_pool_byte_count_ptr, constant_pool_bytes_ptr);
+ }
+
+ jvmtiError IsInterface(jclass klass,
+ jboolean* is_interface_ptr) {
+ return functions->IsInterface(this, klass, is_interface_ptr);
+ }
+
+ jvmtiError IsArrayClass(jclass klass,
+ jboolean* is_array_class_ptr) {
+ return functions->IsArrayClass(this, klass, is_array_class_ptr);
+ }
+
+ jvmtiError IsModifiableClass(jclass klass,
+ jboolean* is_modifiable_class_ptr) {
+ return functions->IsModifiableClass(this, klass, is_modifiable_class_ptr);
+ }
+
+ jvmtiError GetClassLoader(jclass klass,
+ jobject* classloader_ptr) {
+ return functions->GetClassLoader(this, klass, classloader_ptr);
+ }
+
+ jvmtiError GetSourceDebugExtension(jclass klass,
+ char** source_debug_extension_ptr) {
+ return functions->GetSourceDebugExtension(this, klass, source_debug_extension_ptr);
+ }
+
+ jvmtiError RetransformClasses(jint class_count,
+ const jclass* classes) {
+ return functions->RetransformClasses(this, class_count, classes);
+ }
+
+ jvmtiError RedefineClasses(jint class_count,
+ const jvmtiClassDefinition* class_definitions) {
+ return functions->RedefineClasses(this, class_count, class_definitions);
+ }
+
+ jvmtiError GetObjectSize(jobject object,
+ jlong* size_ptr) {
+ return functions->GetObjectSize(this, object, size_ptr);
+ }
+
+ jvmtiError GetObjectHashCode(jobject object,
+ jint* hash_code_ptr) {
+ return functions->GetObjectHashCode(this, object, hash_code_ptr);
+ }
+
+ jvmtiError GetObjectMonitorUsage(jobject object,
+ jvmtiMonitorUsage* info_ptr) {
+ return functions->GetObjectMonitorUsage(this, object, info_ptr);
+ }
+
+ jvmtiError GetFieldName(jclass klass,
+ jfieldID field,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr) {
+ return functions->GetFieldName(this, klass, field, name_ptr, signature_ptr, generic_ptr);
+ }
+
+ jvmtiError GetFieldDeclaringClass(jclass klass,
+ jfieldID field,
+ jclass* declaring_class_ptr) {
+ return functions->GetFieldDeclaringClass(this, klass, field, declaring_class_ptr);
+ }
+
+ jvmtiError GetFieldModifiers(jclass klass,
+ jfieldID field,
+ jint* modifiers_ptr) {
+ return functions->GetFieldModifiers(this, klass, field, modifiers_ptr);
+ }
+
+ jvmtiError IsFieldSynthetic(jclass klass,
+ jfieldID field,
+ jboolean* is_synthetic_ptr) {
+ return functions->IsFieldSynthetic(this, klass, field, is_synthetic_ptr);
+ }
+
+ jvmtiError GetMethodName(jmethodID method,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr) {
+ return functions->GetMethodName(this, method, name_ptr, signature_ptr, generic_ptr);
+ }
+
+ jvmtiError GetMethodDeclaringClass(jmethodID method,
+ jclass* declaring_class_ptr) {
+ return functions->GetMethodDeclaringClass(this, method, declaring_class_ptr);
+ }
+
+ jvmtiError GetMethodModifiers(jmethodID method,
+ jint* modifiers_ptr) {
+ return functions->GetMethodModifiers(this, method, modifiers_ptr);
+ }
+
+ jvmtiError GetMaxLocals(jmethodID method,
+ jint* max_ptr) {
+ return functions->GetMaxLocals(this, method, max_ptr);
+ }
+
+ jvmtiError GetArgumentsSize(jmethodID method,
+ jint* size_ptr) {
+ return functions->GetArgumentsSize(this, method, size_ptr);
+ }
+
+ jvmtiError GetLineNumberTable(jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLineNumberEntry** table_ptr) {
+ return functions->GetLineNumberTable(this, method, entry_count_ptr, table_ptr);
+ }
+
+ jvmtiError GetMethodLocation(jmethodID method,
+ jlocation* start_location_ptr,
+ jlocation* end_location_ptr) {
+ return functions->GetMethodLocation(this, method, start_location_ptr, end_location_ptr);
+ }
+
+ jvmtiError GetLocalVariableTable(jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLocalVariableEntry** table_ptr) {
+ return functions->GetLocalVariableTable(this, method, entry_count_ptr, table_ptr);
+ }
+
+ jvmtiError GetBytecodes(jmethodID method,
+ jint* bytecode_count_ptr,
+ unsigned char** bytecodes_ptr) {
+ return functions->GetBytecodes(this, method, bytecode_count_ptr, bytecodes_ptr);
+ }
+
+ jvmtiError IsMethodNative(jmethodID method,
+ jboolean* is_native_ptr) {
+ return functions->IsMethodNative(this, method, is_native_ptr);
+ }
+
+ jvmtiError IsMethodSynthetic(jmethodID method,
+ jboolean* is_synthetic_ptr) {
+ return functions->IsMethodSynthetic(this, method, is_synthetic_ptr);
+ }
+
+ jvmtiError IsMethodObsolete(jmethodID method,
+ jboolean* is_obsolete_ptr) {
+ return functions->IsMethodObsolete(this, method, is_obsolete_ptr);
+ }
+
+ jvmtiError SetNativeMethodPrefix(const char* prefix) {
+ return functions->SetNativeMethodPrefix(this, prefix);
+ }
+
+ jvmtiError SetNativeMethodPrefixes(jint prefix_count,
+ char** prefixes) {
+ return functions->SetNativeMethodPrefixes(this, prefix_count, prefixes);
+ }
+
+ jvmtiError CreateRawMonitor(const char* name,
+ jrawMonitorID* monitor_ptr) {
+ return functions->CreateRawMonitor(this, name, monitor_ptr);
+ }
+
+ jvmtiError DestroyRawMonitor(jrawMonitorID monitor) {
+ return functions->DestroyRawMonitor(this, monitor);
+ }
+
+ jvmtiError RawMonitorEnter(jrawMonitorID monitor) {
+ return functions->RawMonitorEnter(this, monitor);
+ }
+
+ jvmtiError RawMonitorExit(jrawMonitorID monitor) {
+ return functions->RawMonitorExit(this, monitor);
+ }
+
+ jvmtiError RawMonitorWait(jrawMonitorID monitor,
+ jlong millis) {
+ return functions->RawMonitorWait(this, monitor, millis);
+ }
+
+ jvmtiError RawMonitorNotify(jrawMonitorID monitor) {
+ return functions->RawMonitorNotify(this, monitor);
+ }
+
+ jvmtiError RawMonitorNotifyAll(jrawMonitorID monitor) {
+ return functions->RawMonitorNotifyAll(this, monitor);
+ }
+
+ jvmtiError SetJNIFunctionTable(const jniNativeInterface* function_table) {
+ return functions->SetJNIFunctionTable(this, function_table);
+ }
+
+ jvmtiError GetJNIFunctionTable(jniNativeInterface** function_table) {
+ return functions->GetJNIFunctionTable(this, function_table);
+ }
+
+ jvmtiError SetEventCallbacks(const jvmtiEventCallbacks* callbacks,
+ jint size_of_callbacks) {
+ return functions->SetEventCallbacks(this, callbacks, size_of_callbacks);
+ }
+
+ jvmtiError SetEventNotificationMode(jvmtiEventMode mode,
+ jvmtiEvent event_type,
+ jthread event_thread,
+ ...) {
+ return functions->SetEventNotificationMode(this, mode, event_type, event_thread);
+ }
+
+ jvmtiError GenerateEvents(jvmtiEvent event_type) {
+ return functions->GenerateEvents(this, event_type);
+ }
+
+ jvmtiError GetExtensionFunctions(jint* extension_count_ptr,
+ jvmtiExtensionFunctionInfo** extensions) {
+ return functions->GetExtensionFunctions(this, extension_count_ptr, extensions);
+ }
+
+ jvmtiError GetExtensionEvents(jint* extension_count_ptr,
+ jvmtiExtensionEventInfo** extensions) {
+ return functions->GetExtensionEvents(this, extension_count_ptr, extensions);
+ }
+
+ jvmtiError SetExtensionEventCallback(jint extension_event_index,
+ jvmtiExtensionEvent callback) {
+ return functions->SetExtensionEventCallback(this, extension_event_index, callback);
+ }
+
+ jvmtiError GetPotentialCapabilities(jvmtiCapabilities* capabilities_ptr) {
+ return functions->GetPotentialCapabilities(this, capabilities_ptr);
+ }
+
+ jvmtiError AddCapabilities(const jvmtiCapabilities* capabilities_ptr) {
+ return functions->AddCapabilities(this, capabilities_ptr);
+ }
+
+ jvmtiError RelinquishCapabilities(const jvmtiCapabilities* capabilities_ptr) {
+ return functions->RelinquishCapabilities(this, capabilities_ptr);
+ }
+
+ jvmtiError GetCapabilities(jvmtiCapabilities* capabilities_ptr) {
+ return functions->GetCapabilities(this, capabilities_ptr);
+ }
+
+ jvmtiError GetCurrentThreadCpuTimerInfo(jvmtiTimerInfo* info_ptr) {
+ return functions->GetCurrentThreadCpuTimerInfo(this, info_ptr);
+ }
+
+ jvmtiError GetCurrentThreadCpuTime(jlong* nanos_ptr) {
+ return functions->GetCurrentThreadCpuTime(this, nanos_ptr);
+ }
+
+ jvmtiError GetThreadCpuTimerInfo(jvmtiTimerInfo* info_ptr) {
+ return functions->GetThreadCpuTimerInfo(this, info_ptr);
+ }
+
+ jvmtiError GetThreadCpuTime(jthread thread,
+ jlong* nanos_ptr) {
+ return functions->GetThreadCpuTime(this, thread, nanos_ptr);
+ }
+
+ jvmtiError GetTimerInfo(jvmtiTimerInfo* info_ptr) {
+ return functions->GetTimerInfo(this, info_ptr);
+ }
+
+ jvmtiError GetTime(jlong* nanos_ptr) {
+ return functions->GetTime(this, nanos_ptr);
+ }
+
+ jvmtiError GetAvailableProcessors(jint* processor_count_ptr) {
+ return functions->GetAvailableProcessors(this, processor_count_ptr);
+ }
+
+ jvmtiError AddToBootstrapClassLoaderSearch(const char* segment) {
+ return functions->AddToBootstrapClassLoaderSearch(this, segment);
+ }
+
+ jvmtiError AddToSystemClassLoaderSearch(const char* segment) {
+ return functions->AddToSystemClassLoaderSearch(this, segment);
+ }
+
+ jvmtiError GetSystemProperties(jint* count_ptr,
+ char*** property_ptr) {
+ return functions->GetSystemProperties(this, count_ptr, property_ptr);
+ }
+
+ jvmtiError GetSystemProperty(const char* property,
+ char** value_ptr) {
+ return functions->GetSystemProperty(this, property, value_ptr);
+ }
+
+ jvmtiError SetSystemProperty(const char* property,
+ const char* value) {
+ return functions->SetSystemProperty(this, property, value);
+ }
+
+ jvmtiError GetPhase(jvmtiPhase* phase_ptr) {
+ return functions->GetPhase(this, phase_ptr);
+ }
+
+ jvmtiError DisposeEnvironment() {
+ return functions->DisposeEnvironment(this);
+ }
+
+ jvmtiError SetEnvironmentLocalStorage(const void* data) {
+ return functions->SetEnvironmentLocalStorage(this, data);
+ }
+
+ jvmtiError GetEnvironmentLocalStorage(void** data_ptr) {
+ return functions->GetEnvironmentLocalStorage(this, data_ptr);
+ }
+
+ jvmtiError GetVersionNumber(jint* version_ptr) {
+ return functions->GetVersionNumber(this, version_ptr);
+ }
+
+ jvmtiError GetErrorName(jvmtiError error,
+ char** name_ptr) {
+ return functions->GetErrorName(this, error, name_ptr);
+ }
+
+ jvmtiError SetVerboseFlag(jvmtiVerboseFlag flag,
+ jboolean value) {
+ return functions->SetVerboseFlag(this, flag, value);
+ }
+
+ jvmtiError GetJLocationFormat(jvmtiJlocationFormat* format_ptr) {
+ return functions->GetJLocationFormat(this, format_ptr);
+ }
+
+#endif /* __cplusplus */
+};
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_JAVA_JVMTI_H_ */
+
diff --git a/openjdkjvmti/jvmti_allocator.h b/openjdkjvmti/jvmti_allocator.h
new file mode 100644
index 0000000..e29e034
--- /dev/null
+++ b/openjdkjvmti/jvmti_allocator.h
@@ -0,0 +1,174 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+#define ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "jvmti.h"
+
+#include "ti_allocator.h"
+
+namespace openjdkjvmti {
+
+template <typename T> class JvmtiAllocator;
+
+template <>
+class JvmtiAllocator<void> {
+ public:
+ typedef void value_type;
+ typedef void* pointer;
+ typedef const void* const_pointer;
+
+ template <typename U>
+ struct rebind {
+ typedef JvmtiAllocator<U> other;
+ };
+
+ explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {}
+ explicit JvmtiAllocator() : env_(nullptr) {}
+
+ template <typename U>
+ JvmtiAllocator(const JvmtiAllocator<U>& other) // NOLINT, implicit
+ : env_(other.env_) {}
+
+ JvmtiAllocator(const JvmtiAllocator& other) = default;
+ JvmtiAllocator& operator=(const JvmtiAllocator& other) = default;
+ ~JvmtiAllocator() = default;
+
+ private:
+ jvmtiEnv* env_;
+
+ template <typename U>
+ friend class JvmtiAllocator;
+
+ template <typename U>
+ friend bool operator==(const JvmtiAllocator<U>& lhs, const JvmtiAllocator<U>& rhs);
+};
+
+template <typename T>
+class JvmtiAllocator {
+ public:
+ typedef T value_type;
+ typedef T* pointer;
+ typedef T& reference;
+ typedef const T* const_pointer;
+ typedef const T& const_reference;
+ typedef size_t size_type;
+ typedef ptrdiff_t difference_type;
+
+ template <typename U>
+ struct rebind {
+ typedef JvmtiAllocator<U> other;
+ };
+
+ explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {}
+ explicit JvmtiAllocator() : env_(nullptr) {}
+
+ template <typename U>
+ JvmtiAllocator(const JvmtiAllocator<U>& other) // NOLINT, implicit
+ : env_(other.env_) {}
+
+ JvmtiAllocator(const JvmtiAllocator& other) = default;
+ JvmtiAllocator& operator=(const JvmtiAllocator& other) = default;
+ ~JvmtiAllocator() = default;
+
+ size_type max_size() const {
+ return static_cast<size_type>(-1) / sizeof(T);
+ }
+
+ pointer address(reference x) const { return &x; }
+ const_pointer address(const_reference x) const { return &x; }
+
+ pointer allocate(size_type n, JvmtiAllocator<void>::pointer hint ATTRIBUTE_UNUSED = nullptr) {
+ DCHECK_LE(n, max_size());
+ if (env_ == nullptr) {
+ T* result = reinterpret_cast<T*>(AllocUtil::AllocateImpl(n * sizeof(T)));
+ CHECK(result != nullptr || n == 0u); // Abort if AllocateImpl() fails.
+ return result;
+ } else {
+ unsigned char* result;
+ jvmtiError alloc_error = env_->Allocate(n * sizeof(T), &result);
+ CHECK(alloc_error == JVMTI_ERROR_NONE);
+ return reinterpret_cast<T*>(result);
+ }
+ }
+ void deallocate(pointer p, size_type n ATTRIBUTE_UNUSED) {
+ if (env_ == nullptr) {
+ AllocUtil::DeallocateImpl(reinterpret_cast<unsigned char*>(p));
+ } else {
+ jvmtiError dealloc_error = env_->Deallocate(reinterpret_cast<unsigned char*>(p));
+ CHECK(dealloc_error == JVMTI_ERROR_NONE);
+ }
+ }
+
+ void construct(pointer p, const_reference val) {
+ new (static_cast<void*>(p)) value_type(val);
+ }
+ template <class U, class... Args>
+ void construct(U* p, Args&&... args) {
+ ::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
+ }
+ void destroy(pointer p) {
+ p->~value_type();
+ }
+
+ inline bool operator==(JvmtiAllocator const& other) {
+ return env_ == other.env_;
+ }
+ inline bool operator!=(JvmtiAllocator const& other) {
+ return !operator==(other);
+ }
+
+ private:
+ jvmtiEnv* env_;
+
+ template <typename U>
+ friend class JvmtiAllocator;
+
+ template <typename U>
+ friend bool operator==(const JvmtiAllocator<U>& lhs, const JvmtiAllocator<U>& rhs);
+};
+
+template <typename T>
+inline bool operator==(const JvmtiAllocator<T>& lhs, const JvmtiAllocator<T>& rhs) {
+ return lhs.env_ == rhs.env_;
+}
+
+template <typename T>
+inline bool operator!=(const JvmtiAllocator<T>& lhs, const JvmtiAllocator<T>& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
diff --git a/openjdkjvmti/jvmti_weak_table-inl.h b/openjdkjvmti/jvmti_weak_table-inl.h
new file mode 100644
index 0000000..1c82255
--- /dev/null
+++ b/openjdkjvmti/jvmti_weak_table-inl.h
@@ -0,0 +1,406 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+#define ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+
+#include "jvmti_weak_table.h"
+
+#include <limits>
+
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "gc/allocation_listener.h"
+#include "instrumentation.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti_allocator.h"
+#include "mirror/class.h"
+#include "mirror/object.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime.h"
+
+namespace openjdkjvmti {
+
+template <typename T>
+void JvmtiWeakTable<T>::Lock() {
+ allow_disallow_lock_.ExclusiveLock(art::Thread::Current());
+}
+template <typename T>
+void JvmtiWeakTable<T>::Unlock() {
+ allow_disallow_lock_.ExclusiveUnlock(art::Thread::Current());
+}
+template <typename T>
+void JvmtiWeakTable<T>::AssertLocked() {
+ allow_disallow_lock_.AssertHeld(art::Thread::Current());
+}
+
+template <typename T>
+void JvmtiWeakTable<T>::UpdateTableWithReadBarrier() {
+ update_since_last_sweep_ = true;
+
+ auto WithReadBarrierUpdater = [&](const art::GcRoot<art::mirror::Object>& original_root,
+ art::mirror::Object* original_obj ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return original_root.Read<art::kWithReadBarrier>();
+ };
+
+ UpdateTableWith<decltype(WithReadBarrierUpdater), kIgnoreNull>(WithReadBarrierUpdater);
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::GetTagSlowPath(art::Thread* self, art::mirror::Object* obj, T* result) {
+ // Under concurrent GC, there is a window between moving objects and sweeping of system
+ // weaks in which mutators are active. We may receive a to-space object pointer in obj,
+ // but still have from-space pointers in the table. Explicitly update the table once.
+ // Note: this will keep *all* objects in the table live, but should be a rare occurrence.
+ UpdateTableWithReadBarrier();
+ return GetTagLocked(self, obj, result);
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::Remove(art::mirror::Object* obj, /* out */ T* tag) {
+ art::Thread* self = art::Thread::Current();
+ art::MutexLock mu(self, allow_disallow_lock_);
+ Wait(self);
+
+ return RemoveLocked(self, obj, tag);
+}
+template <typename T>
+bool JvmtiWeakTable<T>::RemoveLocked(art::mirror::Object* obj, T* tag) {
+ art::Thread* self = art::Thread::Current();
+ allow_disallow_lock_.AssertHeld(self);
+ Wait(self);
+
+ return RemoveLocked(self, obj, tag);
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::RemoveLocked(art::Thread* self, art::mirror::Object* obj, T* tag) {
+ auto it = tagged_objects_.find(art::GcRoot<art::mirror::Object>(obj));
+ if (it != tagged_objects_.end()) {
+ if (tag != nullptr) {
+ *tag = it->second;
+ }
+ tagged_objects_.erase(it);
+ return true;
+ }
+
+ if (art::kUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
+ // Under concurrent GC, there is a window between moving objects and sweeping of system
+ // weaks in which mutators are active. We may receive a to-space object pointer in obj,
+ // but still have from-space pointers in the table. Explicitly update the table once.
+ // Note: this will keep *all* objects in the table live, but should be a rare occurrence.
+
+ // Update the table.
+ UpdateTableWithReadBarrier();
+
+ // And try again.
+ return RemoveLocked(self, obj, tag);
+ }
+
+ // Not in here.
+ return false;
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::Set(art::mirror::Object* obj, T new_tag) {
+ art::Thread* self = art::Thread::Current();
+ art::MutexLock mu(self, allow_disallow_lock_);
+ Wait(self);
+
+ return SetLocked(self, obj, new_tag);
+}
+template <typename T>
+bool JvmtiWeakTable<T>::SetLocked(art::mirror::Object* obj, T new_tag) {
+ art::Thread* self = art::Thread::Current();
+ allow_disallow_lock_.AssertHeld(self);
+ Wait(self);
+
+ return SetLocked(self, obj, new_tag);
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::SetLocked(art::Thread* self, art::mirror::Object* obj, T new_tag) {
+ auto it = tagged_objects_.find(art::GcRoot<art::mirror::Object>(obj));
+ if (it != tagged_objects_.end()) {
+ it->second = new_tag;
+ return true;
+ }
+
+ if (art::kUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
+ // Under concurrent GC, there is a window between moving objects and sweeping of system
+ // weaks in which mutators are active. We may receive a to-space object pointer in obj,
+ // but still have from-space pointers in the table. Explicitly update the table once.
+ // Note: this will keep *all* objects in the table live, but should be a rare occurrence.
+
+ // Update the table.
+ UpdateTableWithReadBarrier();
+
+ // And try again.
+ return SetLocked(self, obj, new_tag);
+ }
+
+ // New element.
+ auto insert_it = tagged_objects_.emplace(art::GcRoot<art::mirror::Object>(obj), new_tag);
+ DCHECK(insert_it.second);
+ return false;
+}
+
+template <typename T>
+void JvmtiWeakTable<T>::Sweep(art::IsMarkedVisitor* visitor) {
+ if (DoesHandleNullOnSweep()) {
+ SweepImpl<true>(visitor);
+ } else {
+ SweepImpl<false>(visitor);
+ }
+
+ // Under concurrent GC, there is a window between moving objects and sweeping of system
+ // weaks in which mutators are active. We may receive a to-space object pointer in obj,
+ // but still have from-space pointers in the table. We explicitly update the table then
+ // to ensure we compare against to-space pointers. But we want to do this only once. Once
+ // sweeping is done, we know all objects are to-space pointers until the next GC cycle,
+ // so we re-enable the explicit update for the next marking.
+ update_since_last_sweep_ = false;
+}
+
+template <typename T>
+template <bool kHandleNull>
+void JvmtiWeakTable<T>::SweepImpl(art::IsMarkedVisitor* visitor) {
+ art::Thread* self = art::Thread::Current();
+ art::MutexLock mu(self, allow_disallow_lock_);
+
+ auto IsMarkedUpdater = [&](const art::GcRoot<art::mirror::Object>& original_root ATTRIBUTE_UNUSED,
+ art::mirror::Object* original_obj) {
+ return visitor->IsMarked(original_obj);
+ };
+
+ UpdateTableWith<decltype(IsMarkedUpdater),
+ kHandleNull ? kCallHandleNull : kRemoveNull>(IsMarkedUpdater);
+}
+
+template <typename T>
+template <typename Updater, typename JvmtiWeakTable<T>::TableUpdateNullTarget kTargetNull>
+ALWAYS_INLINE inline void JvmtiWeakTable<T>::UpdateTableWith(Updater& updater) {
+ // We optimistically hope that elements will still be well-distributed when re-inserting them.
+ // So play with the map mechanics, and postpone rehashing. This avoids the need of a side
+ // vector and two passes.
+ float original_max_load_factor = tagged_objects_.max_load_factor();
+ tagged_objects_.max_load_factor(std::numeric_limits<float>::max());
+ // For checking that a max load-factor actually does what we expect.
+ size_t original_bucket_count = tagged_objects_.bucket_count();
+
+ for (auto it = tagged_objects_.begin(); it != tagged_objects_.end();) {
+ DCHECK(!it->first.IsNull());
+ art::mirror::Object* original_obj = it->first.template Read<art::kWithoutReadBarrier>();
+ art::mirror::Object* target_obj = updater(it->first, original_obj);
+ if (original_obj != target_obj) {
+ if (kTargetNull == kIgnoreNull && target_obj == nullptr) {
+ // Ignore null target, don't do anything.
+ } else {
+ T tag = it->second;
+ it = tagged_objects_.erase(it);
+ if (target_obj != nullptr) {
+ tagged_objects_.emplace(art::GcRoot<art::mirror::Object>(target_obj), tag);
+ DCHECK_EQ(original_bucket_count, tagged_objects_.bucket_count());
+ } else if (kTargetNull == kCallHandleNull) {
+ HandleNullSweep(tag);
+ }
+ continue; // Iterator was implicitly updated by erase.
+ }
+ }
+ it++;
+ }
+
+ tagged_objects_.max_load_factor(original_max_load_factor);
+ // TODO: consider rehash here.
+}
+
+template <typename T>
+template <typename Storage, class Allocator>
+struct JvmtiWeakTable<T>::ReleasableContainer {
+ using allocator_type = Allocator;
+
+ explicit ReleasableContainer(const allocator_type& alloc, size_t reserve = 10)
+ : allocator(alloc),
+ data(reserve > 0 ? allocator.allocate(reserve) : nullptr),
+ size(0),
+ capacity(reserve) {
+ }
+
+ ~ReleasableContainer() {
+ if (data != nullptr) {
+ allocator.deallocate(data, capacity);
+ capacity = 0;
+ size = 0;
+ }
+ }
+
+ Storage* Release() {
+ Storage* tmp = data;
+
+ data = nullptr;
+ size = 0;
+ capacity = 0;
+
+ return tmp;
+ }
+
+ void Resize(size_t new_capacity) {
+ CHECK_GT(new_capacity, capacity);
+
+ Storage* tmp = allocator.allocate(new_capacity);
+ DCHECK(tmp != nullptr);
+ if (data != nullptr) {
+ memcpy(tmp, data, sizeof(Storage) * size);
+ }
+ Storage* old = data;
+ data = tmp;
+ allocator.deallocate(old, capacity);
+ capacity = new_capacity;
+ }
+
+ void Pushback(const Storage& elem) {
+ if (size == capacity) {
+ size_t new_capacity = 2 * capacity + 1;
+ Resize(new_capacity);
+ }
+ data[size++] = elem;
+ }
+
+ Allocator allocator;
+ Storage* data;
+ size_t size;
+ size_t capacity;
+};
+
+template <typename T>
+jvmtiError JvmtiWeakTable<T>::GetTaggedObjects(jvmtiEnv* jvmti_env,
+ jint tag_count,
+ const T* tags,
+ jint* count_ptr,
+ jobject** object_result_ptr,
+ T** tag_result_ptr) {
+ if (tag_count < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ if (tag_count > 0) {
+ for (size_t i = 0; i != static_cast<size_t>(tag_count); ++i) {
+ if (tags[i] == 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ }
+ }
+ if (tags == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ if (count_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Thread* self = art::Thread::Current();
+ art::MutexLock mu(self, allow_disallow_lock_);
+ Wait(self);
+
+ art::JNIEnvExt* jni_env = self->GetJniEnv();
+
+ constexpr size_t kDefaultSize = 10;
+ size_t initial_object_size;
+ size_t initial_tag_size;
+ if (tag_count == 0) {
+ initial_object_size = (object_result_ptr != nullptr) ? tagged_objects_.size() : 0;
+ initial_tag_size = (tag_result_ptr != nullptr) ? tagged_objects_.size() : 0;
+ } else {
+ initial_object_size = initial_tag_size = kDefaultSize;
+ }
+ JvmtiAllocator<void> allocator(jvmti_env);
+ ReleasableContainer<jobject, JvmtiAllocator<jobject>> selected_objects(allocator,
+ initial_object_size);
+ ReleasableContainer<T, JvmtiAllocator<T>> selected_tags(allocator, initial_tag_size);
+
+ size_t count = 0;
+ for (auto& pair : tagged_objects_) {
+ bool select;
+ if (tag_count > 0) {
+ select = false;
+ for (size_t i = 0; i != static_cast<size_t>(tag_count); ++i) {
+ if (tags[i] == pair.second) {
+ select = true;
+ break;
+ }
+ }
+ } else {
+ select = true;
+ }
+
+ if (select) {
+ art::mirror::Object* obj = pair.first.template Read<art::kWithReadBarrier>();
+ if (obj != nullptr) {
+ count++;
+ if (object_result_ptr != nullptr) {
+ selected_objects.Pushback(jni_env->AddLocalReference<jobject>(obj));
+ }
+ if (tag_result_ptr != nullptr) {
+ selected_tags.Pushback(pair.second);
+ }
+ }
+ }
+ }
+
+ if (object_result_ptr != nullptr) {
+ *object_result_ptr = selected_objects.Release();
+ }
+ if (tag_result_ptr != nullptr) {
+ *tag_result_ptr = selected_tags.Release();
+ }
+ *count_ptr = static_cast<jint>(count);
+ return ERR(NONE);
+}
+
+template <typename T>
+art::mirror::Object* JvmtiWeakTable<T>::Find(T tag) {
+ art::Thread* self = art::Thread::Current();
+ art::MutexLock mu(self, allow_disallow_lock_);
+ Wait(self);
+
+ for (auto& pair : tagged_objects_) {
+ if (tag == pair.second) {
+ art::mirror::Object* obj = pair.first.template Read<art::kWithReadBarrier>();
+ if (obj != nullptr) {
+ return obj;
+ }
+ }
+ }
+ return nullptr;
+}
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
diff --git a/openjdkjvmti/jvmti_weak_table.h b/openjdkjvmti/jvmti_weak_table.h
new file mode 100644
index 0000000..5a821c9
--- /dev/null
+++ b/openjdkjvmti/jvmti_weak_table.h
@@ -0,0 +1,227 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+#define ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "gc/system_weak.h"
+#include "gc_root-inl.h"
+#include "globals.h"
+#include "jvmti.h"
+#include "jvmti_allocator.h"
+#include "mirror/object.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+// A system-weak container mapping objects to elements of the template type. This corresponds
+// to a weak hash map. For historical reasons the stored value is called "tag."
+template <typename T>
+class JvmtiWeakTable : public art::gc::SystemWeakHolder {
+ public:
+ JvmtiWeakTable()
+ : art::gc::SystemWeakHolder(art::kTaggingLockLevel),
+ update_since_last_sweep_(false) {
+ }
+
+ // Remove the mapping for the given object, returning whether such a mapping existed (and the old
+ // value).
+ ALWAYS_INLINE bool Remove(art::mirror::Object* obj, /* out */ T* tag)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_);
+ ALWAYS_INLINE bool RemoveLocked(art::mirror::Object* obj, /* out */ T* tag)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_);
+
+ // Set the mapping for the given object. Returns true if this overwrites an already existing
+ // mapping.
+ ALWAYS_INLINE virtual bool Set(art::mirror::Object* obj, T tag)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_);
+ ALWAYS_INLINE virtual bool SetLocked(art::mirror::Object* obj, T tag)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_);
+
+ // Return the value associated with the given object. Returns true if the mapping exists, false
+ // otherwise.
+ bool GetTag(art::mirror::Object* obj, /* out */ T* result)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_) {
+ art::Thread* self = art::Thread::Current();
+ art::MutexLock mu(self, allow_disallow_lock_);
+ Wait(self);
+
+ return GetTagLocked(self, obj, result);
+ }
+ bool GetTagLocked(art::mirror::Object* obj, /* out */ T* result)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_) {
+ art::Thread* self = art::Thread::Current();
+ allow_disallow_lock_.AssertHeld(self);
+ Wait(self);
+
+ return GetTagLocked(self, obj, result);
+ }
+
+ // Sweep the container. DO NOT CALL MANUALLY.
+ ALWAYS_INLINE void Sweep(art::IsMarkedVisitor* visitor)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_);
+
+ // Return all objects that have a value mapping in tags.
+ ALWAYS_INLINE
+ jvmtiError GetTaggedObjects(jvmtiEnv* jvmti_env,
+ jint tag_count,
+ const T* tags,
+ /* out */ jint* count_ptr,
+ /* out */ jobject** object_result_ptr,
+ /* out */ T** tag_result_ptr)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_);
+
+ // Locking functions, to allow coarse-grained locking and amortization.
+ ALWAYS_INLINE void Lock() ACQUIRE(allow_disallow_lock_);
+ ALWAYS_INLINE void Unlock() RELEASE(allow_disallow_lock_);
+ ALWAYS_INLINE void AssertLocked() ASSERT_CAPABILITY(allow_disallow_lock_);
+
+ ALWAYS_INLINE art::mirror::Object* Find(T tag)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_);
+
+ protected:
+ // Should HandleNullSweep be called when Sweep detects the release of an object?
+ virtual bool DoesHandleNullOnSweep() {
+ return false;
+ }
+ // If DoesHandleNullOnSweep returns true, this function will be called.
+ virtual void HandleNullSweep(T tag ATTRIBUTE_UNUSED) {}
+
+ private:
+ ALWAYS_INLINE
+ bool SetLocked(art::Thread* self, art::mirror::Object* obj, T tag)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_);
+
+ ALWAYS_INLINE
+ bool RemoveLocked(art::Thread* self, art::mirror::Object* obj, /* out */ T* tag)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_);
+
+ bool GetTagLocked(art::Thread* self, art::mirror::Object* obj, /* out */ T* result)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_) {
+ auto it = tagged_objects_.find(art::GcRoot<art::mirror::Object>(obj));
+ if (it != tagged_objects_.end()) {
+ *result = it->second;
+ return true;
+ }
+
+ // Performance optimization: To avoid multiple table updates, ensure that during GC we
+ // only update once. See the comment on the implementation of GetTagSlowPath.
+ if (art::kUseReadBarrier &&
+ self != nullptr &&
+ self->GetIsGcMarking() &&
+ !update_since_last_sweep_) {
+ return GetTagSlowPath(self, obj, result);
+ }
+
+ return false;
+ }
+
+ // Slow-path for GetTag. We didn't find the object, but we might be storing from-pointers and
+ // are asked to retrieve with a to-pointer.
+ ALWAYS_INLINE
+ bool GetTagSlowPath(art::Thread* self, art::mirror::Object* obj, /* out */ T* result)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_);
+
+ // Update the table by doing read barriers on each element, ensuring that to-space pointers
+ // are stored.
+ ALWAYS_INLINE
+ void UpdateTableWithReadBarrier()
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_);
+
+ template <bool kHandleNull>
+ void SweepImpl(art::IsMarkedVisitor* visitor)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_);
+
+ enum TableUpdateNullTarget {
+ kIgnoreNull,
+ kRemoveNull,
+ kCallHandleNull
+ };
+
+ template <typename Updater, TableUpdateNullTarget kTargetNull>
+ void UpdateTableWith(Updater& updater)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_);
+
+ template <typename Storage, class Allocator = JvmtiAllocator<T>>
+ struct ReleasableContainer;
+
+ struct HashGcRoot {
+ size_t operator()(const art::GcRoot<art::mirror::Object>& r) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return reinterpret_cast<uintptr_t>(r.Read<art::kWithoutReadBarrier>());
+ }
+ };
+
+ struct EqGcRoot {
+ bool operator()(const art::GcRoot<art::mirror::Object>& r1,
+ const art::GcRoot<art::mirror::Object>& r2) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return r1.Read<art::kWithoutReadBarrier>() == r2.Read<art::kWithoutReadBarrier>();
+ }
+ };
+
+ using TagAllocator = JvmtiAllocator<std::pair<const art::GcRoot<art::mirror::Object>, T>>;
+ std::unordered_map<art::GcRoot<art::mirror::Object>,
+ T,
+ HashGcRoot,
+ EqGcRoot,
+ TagAllocator> tagged_objects_
+ GUARDED_BY(allow_disallow_lock_)
+ GUARDED_BY(art::Locks::mutator_lock_);
+ // To avoid repeatedly scanning the whole table, remember if we did that since the last sweep.
+ bool update_since_last_sweep_;
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
diff --git a/openjdkjvmti/object_tagging.cc b/openjdkjvmti/object_tagging.cc
new file mode 100644
index 0000000..dcdd3ed
--- /dev/null
+++ b/openjdkjvmti/object_tagging.cc
@@ -0,0 +1,67 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "object_tagging.h"
+
+#include <limits>
+
+#include "art_jvmti.h"
+#include "events-inl.h"
+#include "jvmti_weak_table-inl.h"
+
+namespace openjdkjvmti {
+
+// Instantiate for jlong = JVMTI tags.
+template class JvmtiWeakTable<jlong>;
+
+bool ObjectTagTable::Set(art::mirror::Object* obj, jlong new_tag) {
+ if (new_tag == 0) {
+ jlong tmp;
+ return Remove(obj, &tmp);
+ }
+ return JvmtiWeakTable<jlong>::Set(obj, new_tag);
+}
+bool ObjectTagTable::SetLocked(art::mirror::Object* obj, jlong new_tag) {
+ if (new_tag == 0) {
+ jlong tmp;
+ return RemoveLocked(obj, &tmp);
+ }
+ return JvmtiWeakTable<jlong>::SetLocked(obj, new_tag);
+}
+
+bool ObjectTagTable::DoesHandleNullOnSweep() {
+ return event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kObjectFree);
+}
+void ObjectTagTable::HandleNullSweep(jlong tag) {
+ event_handler_->DispatchEvent<ArtJvmtiEvent::kObjectFree>(jvmti_env_, nullptr, tag);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/object_tagging.h b/openjdkjvmti/object_tagging.h
new file mode 100644
index 0000000..b474845
--- /dev/null
+++ b/openjdkjvmti/object_tagging.h
@@ -0,0 +1,86 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
+#define ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
+
+#include <unordered_map>
+
+#include "base/mutex.h"
+#include "globals.h"
+#include "jvmti.h"
+#include "jvmti_weak_table.h"
+#include "mirror/object.h"
+
+namespace openjdkjvmti {
+
+struct ArtJvmTiEnv;
+class EventHandler;
+
+class ObjectTagTable FINAL : public JvmtiWeakTable<jlong> {
+ public:
+ ObjectTagTable(EventHandler* event_handler, ArtJvmTiEnv* env)
+ : event_handler_(event_handler), jvmti_env_(env) {}
+
+ bool Set(art::mirror::Object* obj, jlong tag) OVERRIDE
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_);
+ bool SetLocked(art::mirror::Object* obj, jlong tag) OVERRIDE
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_);
+
+ jlong GetTagOrZero(art::mirror::Object* obj)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!allow_disallow_lock_) {
+ jlong tmp = 0;
+ GetTag(obj, &tmp);
+ return tmp;
+ }
+ jlong GetTagOrZeroLocked(art::mirror::Object* obj)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(allow_disallow_lock_) {
+ jlong tmp = 0;
+ GetTagLocked(obj, &tmp);
+ return tmp;
+ }
+
+ protected:
+ bool DoesHandleNullOnSweep() OVERRIDE;
+ void HandleNullSweep(jlong tag) OVERRIDE;
+
+ private:
+ EventHandler* event_handler_;
+ ArtJvmTiEnv* jvmti_env_;
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
diff --git a/openjdkjvmti/ti_allocator.cc b/openjdkjvmti/ti_allocator.cc
new file mode 100644
index 0000000..575558d
--- /dev/null
+++ b/openjdkjvmti/ti_allocator.cc
@@ -0,0 +1,95 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_allocator.h"
+
+#if defined(__APPLE__)
+// Apple doesn't have malloc.h. Just give this function a non-functional definition.
+#define malloc_usable_size(P) 0
+#else
+#include <malloc.h>
+#endif
+
+#include <atomic>
+
+#include "art_jvmti.h"
+#include "base/enums.h"
+
+namespace openjdkjvmti {
+
+std::atomic<jlong> AllocUtil::allocated;
+
+jvmtiError AllocUtil::GetGlobalJvmtiAllocationState(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jlong* allocated_ptr) {
+ if (allocated_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ *allocated_ptr = allocated.load();
+ return OK;
+}
+
+jvmtiError AllocUtil::Allocate(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jlong size,
+ unsigned char** mem_ptr) {
+ if (size < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ } else if (size == 0) {
+ *mem_ptr = nullptr;
+ return OK;
+ }
+ *mem_ptr = AllocateImpl(size);
+ if (UNLIKELY(*mem_ptr == nullptr)) {
+ return ERR(OUT_OF_MEMORY);
+ }
+ return OK;
+}
+
+unsigned char* AllocUtil::AllocateImpl(jlong size) {
+ unsigned char* ret = size != 0 ? reinterpret_cast<unsigned char*>(malloc(size)) : nullptr;
+ if (LIKELY(ret != nullptr)) {
+ allocated += malloc_usable_size(ret);
+ }
+ return ret;
+}
+
+jvmtiError AllocUtil::Deallocate(jvmtiEnv* env ATTRIBUTE_UNUSED, unsigned char* mem) {
+ DeallocateImpl(mem);
+ return OK;
+}
+
+void AllocUtil::DeallocateImpl(unsigned char* mem) {
+ if (mem != nullptr) {
+ allocated -= malloc_usable_size(mem);
+ free(mem);
+ }
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_allocator.h b/openjdkjvmti/ti_allocator.h
new file mode 100644
index 0000000..776cc5e
--- /dev/null
+++ b/openjdkjvmti/ti_allocator.h
@@ -0,0 +1,65 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
+#define ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include <atomic>
+#include <memory>
+
+namespace openjdkjvmti {
+
+template<typename T>
+class JvmtiAllocator;
+
+class AllocUtil {
+ public:
+ static jvmtiError Allocate(jvmtiEnv* env, jlong size, unsigned char** mem_ptr);
+ static jvmtiError Deallocate(jvmtiEnv* env, unsigned char* mem);
+ static jvmtiError GetGlobalJvmtiAllocationState(jvmtiEnv* env, jlong* total_allocated);
+
+ private:
+ static void DeallocateImpl(unsigned char* mem);
+ static unsigned char* AllocateImpl(jlong size);
+
+ static std::atomic<jlong> allocated;
+
+ template <typename T>
+ friend class JvmtiAllocator;
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
+
diff --git a/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc
new file mode 100644
index 0000000..f5116a8
--- /dev/null
+++ b/openjdkjvmti/ti_breakpoint.cc
@@ -0,0 +1,114 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <functional>
+
+#include "ti_breakpoint.h"
+
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/enums.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+size_t Breakpoint::hash() const {
+ return std::hash<uintptr_t> {}(reinterpret_cast<uintptr_t>(method_))
+ ^ std::hash<jlocation> {}(location_);
+}
+
+Breakpoint::Breakpoint(art::ArtMethod* m, jlocation loc) : method_(m), location_(loc) {
+ DCHECK(!m->IsDefault() || !m->IsCopied() || !m->IsInvokable())
+ << "Flags are: 0x" << std::hex << m->GetAccessFlags();
+}
+
+void BreakpointUtil::RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) {
+ std::vector<Breakpoint> to_remove;
+ for (const Breakpoint& b : env->breakpoints) {
+ if (b.GetMethod()->GetDeclaringClass() == klass) {
+ to_remove.push_back(b);
+ }
+ }
+ for (const Breakpoint& b : to_remove) {
+ auto it = env->breakpoints.find(b);
+ DCHECK(it != env->breakpoints.end());
+ env->breakpoints.erase(it);
+ }
+}
+
+jvmtiError BreakpointUtil::SetBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ // Need to get mutator_lock_ so we can find the interface version of any default methods.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod();
+ if (location < 0 || static_cast<uint32_t>(location) >=
+ art_method->GetCodeItem()->insns_size_in_code_units_) {
+ return ERR(INVALID_LOCATION);
+ }
+ auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location});
+ if (!res_pair.second) {
+ // Didn't get inserted because it's already present!
+ return ERR(DUPLICATE);
+ }
+ return OK;
+}
+
+jvmtiError BreakpointUtil::ClearBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ // Need to get mutator_lock_ so we can find the interface version of any default methods.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ auto pos = env->breakpoints.find(
+ /* Breakpoint */ {art::jni::DecodeArtMethod(method)->GetCanonicalMethod(), location});
+ if (pos == env->breakpoints.end()) {
+ return ERR(NOT_FOUND);
+ }
+ env->breakpoints.erase(pos);
+ return OK;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_breakpoint.h b/openjdkjvmti/ti_breakpoint.h
new file mode 100644
index 0000000..9b08b42
--- /dev/null
+++ b/openjdkjvmti/ti_breakpoint.h
@@ -0,0 +1,94 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
+#define ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/mutex.h"
+
+namespace art {
+class ArtMethod;
+namespace mirror {
+class Class;
+} // namespace mirror
+} // namespace art
+
+namespace openjdkjvmti {
+
+struct ArtJvmTiEnv;
+
+class Breakpoint {
+ public:
+ Breakpoint(art::ArtMethod* m, jlocation loc);
+
+ // Get the hash code of this breakpoint.
+ size_t hash() const;
+
+ bool operator==(const Breakpoint& other) const {
+ return method_ == other.method_ && location_ == other.location_;
+ }
+
+ art::ArtMethod* GetMethod() const {
+ return method_;
+ }
+
+ jlocation GetLocation() const {
+ return location_;
+ }
+
+ private:
+ art::ArtMethod* method_;
+ jlocation location_;
+};
+
+class BreakpointUtil {
+ public:
+ static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location);
+ static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location);
+ // Used by class redefinition to remove breakpoints on redefined classes.
+ static void RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass)
+ REQUIRES(art::Locks::mutator_lock_);
+};
+
+} // namespace openjdkjvmti
+
+namespace std {
+template<> struct hash<openjdkjvmti::Breakpoint> {
+ size_t operator()(const openjdkjvmti::Breakpoint& b) const {
+ return b.hash();
+ }
+};
+
+} // namespace std
+#endif // ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
new file mode 100644
index 0000000..954b5d1
--- /dev/null
+++ b/openjdkjvmti/ti_class.cc
@@ -0,0 +1,1086 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_class.h"
+
+#include "android-base/stringprintf.h"
+
+#include <mutex>
+#include <unordered_set>
+
+#include "art_jvmti.h"
+#include "base/array_ref.h"
+#include "base/macros.h"
+#include "class_linker.h"
+#include "class_table-inl.h"
+#include "common_throws.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "fixed_up_dex_file.h"
+#include "gc/heap-visit-objects-inl.h"
+#include "gc/heap.h"
+#include "gc_root.h"
+#include "handle.h"
+#include "jni_env_ext-inl.h"
+#include "jni_internal.h"
+#include "mirror/array-inl.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
+#include "mirror/object-inl.h"
+#include "mirror/object-refvisitor-inl.h"
+#include "mirror/object_array-inl.h"
+#include "mirror/object_reference.h"
+#include "mirror/reference.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "primitive.h"
+#include "reflection.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_class_loader.h"
+#include "ti_phase.h"
+#include "ti_redefine.h"
+#include "utils.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+using android::base::StringPrintf;
+
+static std::unique_ptr<const art::DexFile> MakeSingleDexFile(art::Thread* self,
+ const char* descriptor,
+ const std::string& orig_location,
+ jint final_len,
+ const unsigned char* final_dex_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // Make the mmap
+ std::string error_msg;
+ art::ArrayRef<const unsigned char> final_data(final_dex_data, final_len);
+ std::unique_ptr<art::MemMap> map(Redefiner::MoveDataToMemMap(orig_location,
+ final_data,
+ &error_msg));
+ if (map.get() == nullptr) {
+ LOG(WARNING) << "Unable to allocate mmap for redefined dex file! Error was: " << error_msg;
+ self->ThrowOutOfMemoryError(StringPrintf(
+ "Unable to allocate dex file for transformation of %s", descriptor).c_str());
+ return nullptr;
+ }
+
+ // Make a dex-file
+ if (map->Size() < sizeof(art::DexFile::Header)) {
+ LOG(WARNING) << "Could not read dex file header because dex_data was too short";
+ art::ThrowClassFormatError(nullptr,
+ "Unable to read transformed dex file of %s",
+ descriptor);
+ return nullptr;
+ }
+ uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_;
+ std::string map_name = map->GetName();
+ std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map_name,
+ checksum,
+ std::move(map),
+ /*verify*/true,
+ /*verify_checksum*/true,
+ &error_msg));
+ if (dex_file.get() == nullptr) {
+ LOG(WARNING) << "Unable to load modified dex file for " << descriptor << ": " << error_msg;
+ art::ThrowClassFormatError(nullptr,
+ "Unable to read transformed dex file of %s because %s",
+ descriptor,
+ error_msg.c_str());
+ return nullptr;
+ }
+ if (dex_file->NumClassDefs() != 1) {
+ LOG(WARNING) << "Dex file contains more than 1 class_def. Ignoring.";
+ // TODO Throw some other sort of error here maybe?
+ art::ThrowClassFormatError(
+ nullptr,
+ "Unable to use transformed dex file of %s because it contained too many classes",
+ descriptor);
+ return nullptr;
+ }
+ return dex_file;
+}
+
+// A deleter that acts like the jvmtiEnv->Deallocate so that asan does not get tripped up.
+// TODO We should everything use the actual jvmtiEnv->Allocate/Deallocate functions once we can
+// figure out which env to use.
+template <typename T>
+class FakeJvmtiDeleter {
+ public:
+ FakeJvmtiDeleter() {}
+
+ FakeJvmtiDeleter(FakeJvmtiDeleter&) = default;
+ FakeJvmtiDeleter(FakeJvmtiDeleter&&) = default;
+ FakeJvmtiDeleter& operator=(const FakeJvmtiDeleter&) = default;
+
+ template <typename U> void operator()(const U* ptr) const {
+ if (ptr != nullptr) {
+ free(const_cast<U*>(ptr));
+ }
+ }
+};
+
+struct ClassCallback : public art::ClassLoadCallback {
+ void ClassPreDefine(const char* descriptor,
+ art::Handle<art::mirror::Class> klass,
+ art::Handle<art::mirror::ClassLoader> class_loader,
+ const art::DexFile& initial_dex_file,
+ const art::DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED,
+ /*out*/art::DexFile const** final_dex_file,
+ /*out*/art::DexFile::ClassDef const** final_class_def)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ bool is_enabled =
+ event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookRetransformable) ||
+ event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable);
+ if (!is_enabled) {
+ return;
+ }
+ if (descriptor[0] != 'L') {
+ // It is a primitive or array. Just return
+ return;
+ }
+ jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked();
+ if (UNLIKELY(phase != JVMTI_PHASE_START && phase != JVMTI_PHASE_LIVE)) {
+ // We want to wait until we are at least in the START phase so that all WellKnownClasses and
+ // mirror classes have been initialized and loaded. The runtime relies on these classes having
+ // specific fields and methods present. Since PreDefine hooks don't need to abide by this
+ // restriction we will simply not send the event for these classes.
+ LOG(WARNING) << "Ignoring load of class <" << descriptor << "> as it is being loaded during "
+ << "runtime initialization.";
+ return;
+ }
+
+ // Strip the 'L' and ';' from the descriptor
+ std::string name(std::string(descriptor).substr(1, strlen(descriptor) - 2));
+
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* env = self->GetJniEnv();
+ ScopedLocalRef<jobject> loader(
+ env, class_loader.IsNull() ? nullptr : env->AddLocalReference<jobject>(class_loader.Get()));
+ std::unique_ptr<FixedUpDexFile> dex_file_copy(FixedUpDexFile::Create(initial_dex_file));
+
+ // Go back to native.
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+ // Call all Non-retransformable agents.
+ jint post_no_redefine_len = 0;
+ unsigned char* post_no_redefine_dex_data = nullptr;
+ std::unique_ptr<const unsigned char, FakeJvmtiDeleter<const unsigned char>>
+ post_no_redefine_unique_ptr(nullptr, FakeJvmtiDeleter<const unsigned char>());
+ event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
+ self,
+ static_cast<JNIEnv*>(env),
+ static_cast<jclass>(nullptr), // The class doesn't really exist yet so send null.
+ loader.get(),
+ name.c_str(),
+ static_cast<jobject>(nullptr), // Android doesn't seem to have protection domains
+ static_cast<jint>(dex_file_copy->Size()),
+ static_cast<const unsigned char*>(dex_file_copy->Begin()),
+ static_cast<jint*>(&post_no_redefine_len),
+ static_cast<unsigned char**>(&post_no_redefine_dex_data));
+ if (post_no_redefine_dex_data == nullptr) {
+ DCHECK_EQ(post_no_redefine_len, 0);
+ post_no_redefine_dex_data = const_cast<unsigned char*>(dex_file_copy->Begin());
+ post_no_redefine_len = dex_file_copy->Size();
+ } else {
+ post_no_redefine_unique_ptr =
+ std::unique_ptr<const unsigned char, FakeJvmtiDeleter<const unsigned char>>(
+ post_no_redefine_dex_data, FakeJvmtiDeleter<const unsigned char>());
+ DCHECK_GT(post_no_redefine_len, 0);
+ }
+ // Call all retransformable agents.
+ jint final_len = 0;
+ unsigned char* final_dex_data = nullptr;
+ std::unique_ptr<const unsigned char, FakeJvmtiDeleter<const unsigned char>>
+ final_dex_unique_ptr(nullptr, FakeJvmtiDeleter<const unsigned char>());
+ event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+ self,
+ static_cast<JNIEnv*>(env),
+ static_cast<jclass>(nullptr), // The class doesn't really exist yet so send null.
+ loader.get(),
+ name.c_str(),
+ static_cast<jobject>(nullptr), // Android doesn't seem to have protection domains
+ static_cast<jint>(post_no_redefine_len),
+ static_cast<const unsigned char*>(post_no_redefine_dex_data),
+ static_cast<jint*>(&final_len),
+ static_cast<unsigned char**>(&final_dex_data));
+ if (final_dex_data == nullptr) {
+ DCHECK_EQ(final_len, 0);
+ final_dex_data = post_no_redefine_dex_data;
+ final_len = post_no_redefine_len;
+ } else {
+ final_dex_unique_ptr =
+ std::unique_ptr<const unsigned char, FakeJvmtiDeleter<const unsigned char>>(
+ final_dex_data, FakeJvmtiDeleter<const unsigned char>());
+ DCHECK_GT(final_len, 0);
+ }
+
+ if (final_dex_data != dex_file_copy->Begin()) {
+ LOG(WARNING) << "Changing class " << descriptor;
+ art::ScopedObjectAccess soa(self);
+ art::StackHandleScope<2> hs(self);
+ // Save the results of all the non-retransformable agents.
+ // First allocate the ClassExt
+ art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(self)));
+ // Make sure we have a ClassExt. This is fine even though we are a temporary since it will
+ // get copied.
+ if (ext.IsNull()) {
+ // We will just return failure if we fail to allocate
+ LOG(WARNING) << "Could not allocate ext-data for class '" << descriptor << "'. "
+ << "Aborting transformation since we will be unable to store it.";
+ self->AssertPendingOOMException();
+ return;
+ }
+
+ // Allocate the byte array to store the dex file bytes in.
+ art::MutableHandle<art::mirror::Object> arr(hs.NewHandle<art::mirror::Object>(nullptr));
+ if (post_no_redefine_dex_data == dex_file_copy->Begin() && name != "java/lang/Long") {
+ // we didn't have any non-retransformable agents. We can just cache a pointer to the
+ // initial_dex_file. It will be kept live by the class_loader.
+ jlong dex_ptr = reinterpret_cast<uintptr_t>(&initial_dex_file);
+ art::JValue val;
+ val.SetJ(dex_ptr);
+ arr.Assign(art::BoxPrimitive(art::Primitive::kPrimLong, val));
+ } else {
+ arr.Assign(art::mirror::ByteArray::AllocateAndFill(
+ self,
+ reinterpret_cast<const signed char*>(post_no_redefine_dex_data),
+ post_no_redefine_len));
+ }
+ if (arr.IsNull()) {
+ LOG(WARNING) << "Unable to allocate memory for initial dex-file. Aborting transformation";
+ self->AssertPendingOOMException();
+ return;
+ }
+
+ std::unique_ptr<const art::DexFile> dex_file(MakeSingleDexFile(self,
+ descriptor,
+ initial_dex_file.GetLocation(),
+ final_len,
+ final_dex_data));
+ if (dex_file.get() == nullptr) {
+ return;
+ }
+
+ // TODO Check Redefined dex file for all invariants.
+ LOG(WARNING) << "Dex file created by class-definition time transformation of "
+ << descriptor << " is not checked for all retransformation invariants.";
+
+ if (!ClassLoaderHelper::AddToClassLoader(self, class_loader, dex_file.get())) {
+ LOG(ERROR) << "Unable to add " << descriptor << " to class loader!";
+ return;
+ }
+
+ // Actually set the ClassExt's original bytes once we have actually succeeded.
+ ext->SetOriginalDexFile(arr.Get());
+ // Set the return values
+ *final_class_def = &dex_file->GetClassDef(0);
+ *final_dex_file = dex_file.release();
+ }
+ }
+
+ void ClassLoad(art::Handle<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassLoad)) {
+ art::Thread* thread = art::Thread::Current();
+ ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+ thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+ art::ObjPtr<art::mirror::Object> peer(thread->GetPeer());
+ ScopedLocalRef<jthread> thread_jni(
+ thread->GetJniEnv(),
+ peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer));
+ {
+ art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>(
+ thread,
+ static_cast<JNIEnv*>(thread->GetJniEnv()),
+ thread_jni.get(),
+ jklass.get());
+ }
+ if (klass->IsTemp()) {
+ AddTempClass(thread, jklass.get());
+ }
+ }
+ }
+
+ void ClassPrepare(art::Handle<art::mirror::Class> temp_klass,
+ art::Handle<art::mirror::Class> klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassPrepare)) {
+ art::Thread* thread = art::Thread::Current();
+ if (temp_klass.Get() != klass.Get()) {
+ DCHECK(temp_klass->IsTemp());
+ DCHECK(temp_klass->IsRetired());
+ HandleTempClass(thread, temp_klass, klass);
+ }
+ ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+ thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+ art::ObjPtr<art::mirror::Object> peer(thread->GetPeer());
+ ScopedLocalRef<jthread> thread_jni(
+ thread->GetJniEnv(),
+ peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer));
+ art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>(
+ thread,
+ static_cast<JNIEnv*>(thread->GetJniEnv()),
+ thread_jni.get(),
+ jklass.get());
+ }
+ }
+
+ // To support parallel class-loading, we need to perform some locking dances here. Namely,
+ // the fixup stage must not be holding the temp_classes lock when it fixes up the system
+ // (as that requires suspending all mutators).
+
+ void AddTempClass(art::Thread* self, jclass klass) {
+ std::unique_lock<std::mutex> mu(temp_classes_lock);
+ jclass global_klass = reinterpret_cast<jclass>(self->GetJniEnv()->NewGlobalRef(klass));
+ temp_classes.push_back(global_klass);
+ }
+
+ void HandleTempClass(art::Thread* self,
+ art::Handle<art::mirror::Class> temp_klass,
+ art::Handle<art::mirror::Class> klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ bool requires_fixup = false;
+ {
+ std::unique_lock<std::mutex> mu(temp_classes_lock);
+ if (temp_classes.empty()) {
+ return;
+ }
+
+ for (auto it = temp_classes.begin(); it != temp_classes.end(); ++it) {
+ if (temp_klass.Get() == art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(*it))) {
+ self->GetJniEnv()->DeleteGlobalRef(*it);
+ temp_classes.erase(it);
+ requires_fixup = true;
+ break;
+ }
+ }
+ }
+ if (requires_fixup) {
+ FixupTempClass(self, temp_klass, klass);
+ }
+ }
+
+ void FixupTempClass(art::Thread* self,
+ art::Handle<art::mirror::Class> temp_klass,
+ art::Handle<art::mirror::Class> klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // Suspend everything.
+ art::gc::Heap* heap = art::Runtime::Current()->GetHeap();
+ if (heap->IsGcConcurrentAndMoving()) {
+ // Need to take a heap dump while GC isn't running. See the
+ // comment in Heap::VisitObjects().
+ heap->IncrementDisableMovingGC(self);
+ }
+ {
+ art::ScopedThreadSuspension sts(self, art::kWaitingForVisitObjects);
+ art::ScopedSuspendAll ssa("FixupTempClass");
+
+ art::mirror::Class* input = temp_klass.Get();
+ art::mirror::Class* output = klass.Get();
+
+ FixupGlobalReferenceTables(input, output);
+ FixupLocalReferenceTables(self, input, output);
+ FixupHeap(input, output);
+ }
+ if (heap->IsGcConcurrentAndMoving()) {
+ heap->DecrementDisableMovingGC(self);
+ }
+ }
+
+ class RootUpdater : public art::RootVisitor {
+ public:
+ RootUpdater(const art::mirror::Class* input, art::mirror::Class* output)
+ : input_(input), output_(output) {}
+
+ void VisitRoots(art::mirror::Object*** roots,
+ size_t count,
+ const art::RootInfo& info ATTRIBUTE_UNUSED)
+ OVERRIDE {
+ for (size_t i = 0; i != count; ++i) {
+ if (*roots[i] == input_) {
+ *roots[i] = output_;
+ }
+ }
+ }
+
+ void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots,
+ size_t count,
+ const art::RootInfo& info ATTRIBUTE_UNUSED)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ for (size_t i = 0; i != count; ++i) {
+ if (roots[i]->AsMirrorPtr() == input_) {
+ roots[i]->Assign(output_);
+ }
+ }
+ }
+
+ private:
+ const art::mirror::Class* input_;
+ art::mirror::Class* output_;
+ };
+
+ void FixupGlobalReferenceTables(art::mirror::Class* input, art::mirror::Class* output)
+ REQUIRES(art::Locks::mutator_lock_) {
+ art::JavaVMExt* java_vm = art::Runtime::Current()->GetJavaVM();
+
+ // Fix up the global table with a root visitor.
+ RootUpdater global_update(input, output);
+ java_vm->VisitRoots(&global_update);
+
+ class WeakGlobalUpdate : public art::IsMarkedVisitor {
+ public:
+ WeakGlobalUpdate(art::mirror::Class* root_input, art::mirror::Class* root_output)
+ : input_(root_input), output_(root_output) {}
+
+ art::mirror::Object* IsMarked(art::mirror::Object* obj) OVERRIDE {
+ if (obj == input_) {
+ return output_;
+ }
+ return obj;
+ }
+
+ private:
+ const art::mirror::Class* input_;
+ art::mirror::Class* output_;
+ };
+ WeakGlobalUpdate weak_global_update(input, output);
+ java_vm->SweepJniWeakGlobals(&weak_global_update);
+ }
+
+ void FixupLocalReferenceTables(art::Thread* self,
+ art::mirror::Class* input,
+ art::mirror::Class* output)
+ REQUIRES(art::Locks::mutator_lock_) {
+ class LocalUpdate {
+ public:
+ LocalUpdate(const art::mirror::Class* root_input, art::mirror::Class* root_output)
+ : input_(root_input), output_(root_output) {}
+
+ static void Callback(art::Thread* t, void* arg) REQUIRES(art::Locks::mutator_lock_) {
+ LocalUpdate* local = reinterpret_cast<LocalUpdate*>(arg);
+
+ // Fix up the local table with a root visitor.
+ RootUpdater local_update(local->input_, local->output_);
+ t->GetJniEnv()->locals.VisitRoots(
+ &local_update, art::RootInfo(art::kRootJNILocal, t->GetThreadId()));
+ }
+
+ private:
+ const art::mirror::Class* input_;
+ art::mirror::Class* output_;
+ };
+ LocalUpdate local_upd(input, output);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Runtime::Current()->GetThreadList()->ForEach(LocalUpdate::Callback, &local_upd);
+ }
+
+ void FixupHeap(art::mirror::Class* input, art::mirror::Class* output)
+ REQUIRES(art::Locks::mutator_lock_) {
+ class HeapFixupVisitor {
+ public:
+ HeapFixupVisitor(const art::mirror::Class* root_input, art::mirror::Class* root_output)
+ : input_(root_input), output_(root_output) {}
+
+ void operator()(art::mirror::Object* src,
+ art::MemberOffset field_offset,
+ bool is_static ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::mirror::HeapReference<art::mirror::Object>* trg =
+ src->GetFieldObjectReferenceAddr(field_offset);
+ if (trg->AsMirrorPtr() == input_) {
+ DCHECK_NE(field_offset.Uint32Value(), 0u); // This shouldn't be the class field of
+ // an object.
+ trg->Assign(output_);
+ }
+ }
+
+ void operator()(art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED,
+ art::ObjPtr<art::mirror::Reference> reference) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::mirror::Object* val = reference->GetReferent();
+ if (val == input_) {
+ reference->SetReferent<false>(output_);
+ }
+ }
+
+ void VisitRoot(art::mirror::CompressedReference<art::mirror::Object>* root ATTRIBUTE_UNUSED)
+ const {
+ LOG(FATAL) << "Unreachable";
+ }
+
+ void VisitRootIfNonNull(
+ art::mirror::CompressedReference<art::mirror::Object>* root ATTRIBUTE_UNUSED) const {
+ LOG(FATAL) << "Unreachable";
+ }
+
+ private:
+ const art::mirror::Class* input_;
+ art::mirror::Class* output_;
+ };
+ HeapFixupVisitor hfv(input, output);
+ auto object_visitor = [&](art::mirror::Object* obj) {
+ obj->VisitReferences<false>(hfv, hfv); // Visit references, not native roots.
+ };
+ art::Runtime::Current()->GetHeap()->VisitObjectsPaused(object_visitor);
+ }
+
+ // A set of all the temp classes we have handed out. We have to fix up references to these.
+ // For simplicity, we store the temp classes as JNI global references in a vector. Normally a
+ // Prepare event will closely follow, so the vector should be small.
+ std::mutex temp_classes_lock;
+ std::vector<jclass> temp_classes;
+
+ EventHandler* event_handler = nullptr;
+};
+
+ClassCallback gClassCallback;
+
+void ClassUtil::Register(EventHandler* handler) {
+ gClassCallback.event_handler = handler;
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add load callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&gClassCallback);
+}
+
+void ClassUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove thread callback");
+ art::Runtime* runtime = art::Runtime::Current();
+ runtime->GetRuntimeCallbacks()->RemoveClassLoadCallback(&gClassCallback);
+}
+
+jvmtiError ClassUtil::GetClassFields(jvmtiEnv* env,
+ jclass jklass,
+ jint* field_count_ptr,
+ jfieldID** fields_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+
+ // Check if this class is a temporary class object used for loading. Since we are seeing it the
+ // class must not have been prepared yet since otherwise the fixup would have gotten the jobject
+ // to point to the final class object.
+ if (klass->IsTemp() || klass->IsRetired()) {
+ return ERR(CLASS_NOT_PREPARED);
+ }
+
+ if (field_count_ptr == nullptr || fields_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::IterationRange<art::StrideIterator<art::ArtField>> ifields = klass->GetIFields();
+ art::IterationRange<art::StrideIterator<art::ArtField>> sfields = klass->GetSFields();
+ size_t array_size = klass->NumInstanceFields() + klass->NumStaticFields();
+
+ unsigned char* out_ptr;
+ jvmtiError allocError = env->Allocate(array_size * sizeof(jfieldID), &out_ptr);
+ if (allocError != ERR(NONE)) {
+ return allocError;
+ }
+ jfieldID* field_array = reinterpret_cast<jfieldID*>(out_ptr);
+
+ size_t array_idx = 0;
+ for (art::ArtField& field : sfields) {
+ field_array[array_idx] = art::jni::EncodeArtField(&field);
+ ++array_idx;
+ }
+ for (art::ArtField& field : ifields) {
+ field_array[array_idx] = art::jni::EncodeArtField(&field);
+ ++array_idx;
+ }
+
+ *field_count_ptr = static_cast<jint>(array_size);
+ *fields_ptr = field_array;
+
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassMethods(jvmtiEnv* env,
+ jclass jklass,
+ jint* method_count_ptr,
+ jmethodID** methods_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+
+ // Check if this class is a temporary class object used for loading. Since we are seeing it the
+ // class must not have been prepared yet since otherwise the fixup would have gotten the jobject
+ // to point to the final class object.
+ if (klass->IsTemp() || klass->IsRetired()) {
+ return ERR(CLASS_NOT_PREPARED);
+ }
+
+ if (method_count_ptr == nullptr || methods_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ size_t array_size = klass->NumDeclaredVirtualMethods() + klass->NumDirectMethods();
+ unsigned char* out_ptr;
+ jvmtiError allocError = env->Allocate(array_size * sizeof(jmethodID), &out_ptr);
+ if (allocError != ERR(NONE)) {
+ return allocError;
+ }
+ jmethodID* method_array = reinterpret_cast<jmethodID*>(out_ptr);
+
+ if (art::kIsDebugBuild) {
+ size_t count = 0;
+ for (auto& m ATTRIBUTE_UNUSED : klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+ count++;
+ }
+ CHECK_EQ(count, klass->NumDirectMethods() + klass->NumDeclaredVirtualMethods());
+ }
+
+ size_t array_idx = 0;
+ for (auto& m : klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+ method_array[array_idx] = art::jni::EncodeArtMethod(&m);
+ ++array_idx;
+ }
+
+ *method_count_ptr = static_cast<jint>(array_size);
+ *methods_ptr = method_array;
+
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetImplementedInterfaces(jvmtiEnv* env,
+ jclass jklass,
+ jint* interface_count_ptr,
+ jclass** interfaces_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+
+ if (interface_count_ptr == nullptr || interfaces_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ // Need to handle array specifically. Arrays implement Serializable and Cloneable, but the
+ // spec says these should not be reported.
+ if (klass->IsArrayClass()) {
+ *interface_count_ptr = 0;
+ *interfaces_ptr = nullptr; // TODO: Should we allocate a dummy here?
+ return ERR(NONE);
+ }
+
+ size_t array_size = klass->NumDirectInterfaces();
+ unsigned char* out_ptr;
+ jvmtiError allocError = env->Allocate(array_size * sizeof(jclass), &out_ptr);
+ if (allocError != ERR(NONE)) {
+ return allocError;
+ }
+ jclass* interface_array = reinterpret_cast<jclass*>(out_ptr);
+
+ art::StackHandleScope<1> hs(soa.Self());
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle(klass));
+
+ for (uint32_t idx = 0; idx != array_size; ++idx) {
+ art::ObjPtr<art::mirror::Class> inf_klass =
+ art::mirror::Class::ResolveDirectInterface(soa.Self(), h_klass, idx);
+ if (inf_klass == nullptr) {
+ soa.Self()->ClearException();
+ env->Deallocate(out_ptr);
+ // TODO: What is the right error code here?
+ return ERR(INTERNAL);
+ }
+ interface_array[idx] = soa.AddLocalReference<jclass>(inf_klass);
+ }
+
+ *interface_count_ptr = static_cast<jint>(array_size);
+ *interfaces_ptr = interface_array;
+
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassSignature(jvmtiEnv* env,
+ jclass jklass,
+ char** signature_ptr,
+ char** generic_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+
+ JvmtiUniquePtr<char[]> sig_copy;
+ if (signature_ptr != nullptr) {
+ std::string storage;
+ const char* descriptor = klass->GetDescriptor(&storage);
+
+ jvmtiError ret;
+ sig_copy = CopyString(env, descriptor, &ret);
+ if (sig_copy == nullptr) {
+ return ret;
+ }
+ *signature_ptr = sig_copy.get();
+ }
+
+ if (generic_ptr != nullptr) {
+ *generic_ptr = nullptr;
+ if (!klass->IsProxyClass() && klass->GetDexCache() != nullptr) {
+ art::StackHandleScope<1> hs(soa.Self());
+ art::Handle<art::mirror::Class> h_klass = hs.NewHandle(klass);
+ art::mirror::ObjectArray<art::mirror::String>* str_array =
+ art::annotations::GetSignatureAnnotationForClass(h_klass);
+ if (str_array != nullptr) {
+ std::ostringstream oss;
+ for (int32_t i = 0; i != str_array->GetLength(); ++i) {
+ oss << str_array->Get(i)->ToModifiedUtf8();
+ }
+ std::string output_string = oss.str();
+ jvmtiError ret;
+ JvmtiUniquePtr<char[]> copy = CopyString(env, output_string.c_str(), &ret);
+ if (copy == nullptr) {
+ return ret;
+ }
+ *generic_ptr = copy.release();
+ } else if (soa.Self()->IsExceptionPending()) {
+ // TODO: Should we report an error here?
+ soa.Self()->ClearException();
+ }
+ }
+ }
+
+ // Everything is fine, release the buffers.
+ sig_copy.release();
+
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassStatus(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass jklass,
+ jint* status_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+
+ if (status_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ if (klass->IsArrayClass()) {
+ *status_ptr = JVMTI_CLASS_STATUS_ARRAY;
+ } else if (klass->IsPrimitive()) {
+ *status_ptr = JVMTI_CLASS_STATUS_PRIMITIVE;
+ } else {
+ *status_ptr = JVMTI_CLASS_STATUS_VERIFIED; // All loaded classes are structurally verified.
+ // This is finicky. If there's an error, we'll say it wasn't prepared.
+ if (klass->IsResolved()) {
+ *status_ptr |= JVMTI_CLASS_STATUS_PREPARED;
+ }
+ if (klass->IsInitialized()) {
+ *status_ptr |= JVMTI_CLASS_STATUS_INITIALIZED;
+ }
+ // Technically the class may be erroneous for other reasons, but we do not have enough info.
+ if (klass->IsErroneous()) {
+ *status_ptr |= JVMTI_CLASS_STATUS_ERROR;
+ }
+ }
+
+ return ERR(NONE);
+}
+
+template <typename T>
+static jvmtiError ClassIsT(jclass jklass, T test, jboolean* is_t_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+
+ if (is_t_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ *is_t_ptr = test(klass) ? JNI_TRUE : JNI_FALSE;
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::IsInterface(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass jklass,
+ jboolean* is_interface_ptr) {
+ auto test = [](art::ObjPtr<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return klass->IsInterface();
+ };
+ return ClassIsT(jklass, test, is_interface_ptr);
+}
+
+jvmtiError ClassUtil::IsArrayClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass jklass,
+ jboolean* is_array_class_ptr) {
+ auto test = [](art::ObjPtr<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return klass->IsArrayClass();
+ };
+ return ClassIsT(jklass, test, is_array_class_ptr);
+}
+
+// Keep this in sync with Class.getModifiers().
+static uint32_t ClassGetModifiers(art::Thread* self, art::ObjPtr<art::mirror::Class> klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (klass->IsArrayClass()) {
+ uint32_t component_modifiers = ClassGetModifiers(self, klass->GetComponentType());
+ if ((component_modifiers & art::kAccInterface) != 0) {
+ component_modifiers &= ~(art::kAccInterface | art::kAccStatic);
+ }
+ return art::kAccAbstract | art::kAccFinal | component_modifiers;
+ }
+
+ uint32_t modifiers = klass->GetAccessFlags() & art::kAccJavaFlagsMask;
+
+ art::StackHandleScope<1> hs(self);
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle(klass));
+ return art::mirror::Class::GetInnerClassFlags(h_klass, modifiers);
+}
+
+jvmtiError ClassUtil::GetClassModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass jklass,
+ jint* modifiers_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+
+ if (modifiers_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ *modifiers_ptr = ClassGetModifiers(soa.Self(), klass);
+
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassLoader(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass jklass,
+ jobject* classloader_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+
+ if (classloader_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ *classloader_ptr = soa.AddLocalReference<jobject>(klass->GetClassLoader());
+
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassLoaderClasses(jvmtiEnv* env,
+ jobject initiating_loader,
+ jint* class_count_ptr,
+ jclass** classes_ptr) {
+ UNUSED(env, initiating_loader, class_count_ptr, classes_ptr);
+
+ if (class_count_ptr == nullptr || classes_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ art::Thread* self = art::Thread::Current();
+ if (!self->GetJniEnv()->IsInstanceOf(initiating_loader,
+ art::WellKnownClasses::java_lang_ClassLoader)) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ if (self->GetJniEnv()->IsInstanceOf(initiating_loader,
+ art::WellKnownClasses::java_lang_BootClassLoader)) {
+ // Need to use null for the BootClassLoader.
+ initiating_loader = nullptr;
+ }
+
+ art::ScopedObjectAccess soa(self);
+ art::ObjPtr<art::mirror::ClassLoader> class_loader =
+ soa.Decode<art::mirror::ClassLoader>(initiating_loader);
+
+ art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
+
+ art::ReaderMutexLock mu(self, *art::Locks::classlinker_classes_lock_);
+
+ art::ClassTable* class_table = class_linker->ClassTableForClassLoader(class_loader);
+ if (class_table == nullptr) {
+ // Nothing loaded.
+ *class_count_ptr = 0;
+ *classes_ptr = nullptr;
+ return ERR(NONE);
+ }
+
+ struct ClassTableCount {
+ bool operator()(art::ObjPtr<art::mirror::Class> klass) {
+ DCHECK(klass != nullptr);
+ ++count;
+ return true;
+ }
+
+ size_t count = 0;
+ };
+ ClassTableCount ctc;
+ class_table->Visit(ctc);
+
+ if (ctc.count == 0) {
+ // Nothing loaded.
+ *class_count_ptr = 0;
+ *classes_ptr = nullptr;
+ return ERR(NONE);
+ }
+
+ unsigned char* data;
+ jvmtiError data_result = env->Allocate(ctc.count * sizeof(jclass), &data);
+ if (data_result != ERR(NONE)) {
+ return data_result;
+ }
+ jclass* class_array = reinterpret_cast<jclass*>(data);
+
+ struct ClassTableFill {
+ bool operator()(art::ObjPtr<art::mirror::Class> klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK(klass != nullptr);
+ DCHECK_LT(count, ctc_ref.count);
+ local_class_array[count++] = soa_ptr->AddLocalReference<jclass>(klass);
+ return true;
+ }
+
+ jclass* local_class_array;
+ const ClassTableCount& ctc_ref;
+ art::ScopedObjectAccess* soa_ptr;
+ size_t count;
+ };
+ ClassTableFill ctf = { class_array, ctc, &soa, 0 };
+ class_table->Visit(ctf);
+ DCHECK_EQ(ctc.count, ctf.count);
+
+ *class_count_ptr = ctc.count;
+ *classes_ptr = class_array;
+
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassVersionNumbers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass jklass,
+ jint* minor_version_ptr,
+ jint* major_version_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (jklass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+ if (!jklass_obj->IsClass()) {
+ return ERR(INVALID_CLASS);
+ }
+ art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass();
+ if (klass->IsPrimitive() || klass->IsArrayClass()) {
+ return ERR(INVALID_CLASS);
+ }
+
+ if (minor_version_ptr == nullptr || major_version_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ // Note: proxies will show the dex file version of java.lang.reflect.Proxy, as that is
+ // what their dex cache copies from.
+ uint32_t version = klass->GetDexFile().GetHeader().GetVersion();
+
+ *major_version_ptr = static_cast<jint>(version);
+ *minor_version_ptr = 0;
+
+ return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetSourceFileName(jvmtiEnv* env, jclass jklass, char** source_name_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (jklass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+ if (!jklass_obj->IsClass()) {
+ return ERR(INVALID_CLASS);
+ }
+ art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass();
+ if (klass->IsPrimitive() || klass->IsArrayClass()) {
+ return ERR(ABSENT_INFORMATION);
+ }
+ JvmtiUniquePtr<char[]> source_copy;
+ const char* file_name = klass->GetSourceFile();
+ if (file_name == nullptr) {
+ return ERR(ABSENT_INFORMATION);
+ }
+ jvmtiError ret;
+ source_copy = CopyString(env, file_name, &ret);
+ if (source_copy == nullptr) {
+ return ret;
+ }
+ *source_name_ptr = source_copy.release();
+ return OK;
+}
+
+jvmtiError ClassUtil::GetSourceDebugExtension(jvmtiEnv* env,
+ jclass jklass,
+ char** source_debug_extension_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (jklass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+ if (!jklass_obj->IsClass()) {
+ return ERR(INVALID_CLASS);
+ }
+ art::StackHandleScope<1> hs(art::Thread::Current());
+ art::Handle<art::mirror::Class> klass(hs.NewHandle(jklass_obj->AsClass()));
+ if (klass->IsPrimitive() || klass->IsArrayClass()) {
+ return ERR(ABSENT_INFORMATION);
+ }
+ JvmtiUniquePtr<char[]> ext_copy;
+ const char* data = art::annotations::GetSourceDebugExtension(klass);
+ if (data == nullptr) {
+ return ERR(ABSENT_INFORMATION);
+ }
+ jvmtiError ret;
+ ext_copy = CopyString(env, data, &ret);
+ if (ext_copy == nullptr) {
+ return ret;
+ }
+ *source_debug_extension_ptr = ext_copy.release();
+ return OK;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_class.h b/openjdkjvmti/ti_class.h
new file mode 100644
index 0000000..dd99e36
--- /dev/null
+++ b/openjdkjvmti/ti_class.h
@@ -0,0 +1,95 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class ClassUtil {
+ public:
+ static void Register(EventHandler* event_handler);
+ static void Unregister();
+
+ static jvmtiError GetClassFields(jvmtiEnv* env,
+ jclass klass,
+ jint* field_count_ptr,
+ jfieldID** fields_ptr);
+
+ static jvmtiError GetClassMethods(jvmtiEnv* env,
+ jclass klass,
+ jint* method_count_ptr,
+ jmethodID** methods_ptr);
+
+ static jvmtiError GetImplementedInterfaces(jvmtiEnv* env,
+ jclass klass,
+ jint* interface_count_ptr,
+ jclass** interfaces_ptr);
+
+ static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr);
+
+ static jvmtiError GetClassSignature(jvmtiEnv* env,
+ jclass klass,
+ char** signature_ptr,
+ char** generic_ptr);
+
+ static jvmtiError GetClassStatus(jvmtiEnv* env, jclass klass, jint* status_ptr);
+
+ static jvmtiError GetClassLoader(jvmtiEnv* env, jclass klass, jobject* classloader_ptr);
+
+ static jvmtiError GetClassLoaderClasses(jvmtiEnv* env,
+ jobject initiating_loader,
+ jint* class_count_ptr,
+ jclass** classes_ptr);
+
+ static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr);
+ static jvmtiError IsArrayClass(jvmtiEnv* env, jclass klass, jboolean* is_array_class_ptr);
+
+ static jvmtiError GetClassVersionNumbers(jvmtiEnv* env,
+ jclass klass,
+ jint* minor_version_ptr,
+ jint* major_version_ptr);
+
+ static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr);
+
+ static jvmtiError GetSourceDebugExtension(jvmtiEnv* env,
+ jclass klass,
+ char** source_debug_extension_ptr);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_CLASS_H_
diff --git a/openjdkjvmti/ti_class_definition.cc b/openjdkjvmti/ti_class_definition.cc
new file mode 100644
index 0000000..c73ef0d
--- /dev/null
+++ b/openjdkjvmti/ti_class_definition.cc
@@ -0,0 +1,179 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_class_definition.h"
+
+#include "base/array_slice.h"
+#include "class_linker-inl.h"
+#include "dex_file.h"
+#include "fixed_up_dex_file.h"
+#include "handle.h"
+#include "handle_scope-inl.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
+#include "mirror/object-inl.h"
+#include "reflection.h"
+#include "thread.h"
+
+namespace openjdkjvmti {
+
+bool ArtClassDefinition::IsModified() const {
+ // RedefineClasses calls always are 'modified' since they need to change the original_dex_file of
+ // the class.
+ if (redefined_) {
+ return true;
+ }
+ // Check if the dex file we want to set is the same as the current one.
+ // Unfortunately we need to do this check even if no modifications have been done since it could
+ // be that agents were removed in the mean-time so we still have a different dex file. The dex
+ // checksum means this is likely to be fairly fast.
+ return static_cast<jint>(original_dex_file_.size()) != dex_len_ ||
+ memcmp(original_dex_file_.data(), dex_data_.get(), dex_len_) != 0;
+}
+
+jvmtiError ArtClassDefinition::InitCommon(ArtJvmTiEnv* env, jclass klass) {
+ JNIEnv* jni_env = GetJniEnv(env);
+ if (jni_env == nullptr) {
+ return ERR(INTERNAL);
+ }
+ art::ScopedObjectAccess soa(jni_env);
+ art::ObjPtr<art::mirror::Class> m_klass(soa.Decode<art::mirror::Class>(klass));
+ if (m_klass.IsNull()) {
+ return ERR(INVALID_CLASS);
+ }
+ klass_ = klass;
+ loader_ = soa.AddLocalReference<jobject>(m_klass->GetClassLoader());
+ std::string descriptor_store;
+ std::string descriptor(m_klass->GetDescriptor(&descriptor_store));
+ name_ = descriptor.substr(1, descriptor.size() - 2);
+ // Android doesn't really have protection domains.
+ protection_domain_ = nullptr;
+ return OK;
+}
+
+// Gets the data surrounding the given class.
+static jvmtiError GetDexDataForRetransformation(ArtJvmTiEnv* env,
+ art::Handle<art::mirror::Class> klass,
+ /*out*/jint* dex_data_len,
+ /*out*/unsigned char** dex_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::StackHandleScope<3> hs(art::Thread::Current());
+ art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->GetExtData()));
+ const art::DexFile* dex_file = nullptr;
+ if (!ext.IsNull()) {
+ art::Handle<art::mirror::Object> orig_dex(hs.NewHandle(ext->GetOriginalDexFile()));
+ if (!orig_dex.IsNull()) {
+ if (orig_dex->IsArrayInstance()) {
+ DCHECK(orig_dex->GetClass()->GetComponentType()->IsPrimitiveByte());
+ art::Handle<art::mirror::ByteArray> orig_dex_bytes(
+ hs.NewHandle(art::down_cast<art::mirror::ByteArray*>(orig_dex->AsArray())));
+ *dex_data_len = static_cast<jint>(orig_dex_bytes->GetLength());
+ return CopyDataIntoJvmtiBuffer(
+ env,
+ reinterpret_cast<const unsigned char*>(orig_dex_bytes->GetData()),
+ *dex_data_len,
+ /*out*/dex_data);
+ } else if (orig_dex->IsDexCache()) {
+ dex_file = orig_dex->AsDexCache()->GetDexFile();
+ } else {
+ DCHECK(orig_dex->GetClass()->DescriptorEquals("Ljava/lang/Long;"))
+ << "Expected java/lang/Long but found object of type "
+ << orig_dex->GetClass()->PrettyClass();
+ art::ObjPtr<art::mirror::Class> prim_long_class(
+ art::Runtime::Current()->GetClassLinker()->GetClassRoot(
+ art::ClassLinker::kPrimitiveLong));
+ art::JValue val;
+ if (!art::UnboxPrimitiveForResult(orig_dex.Get(), prim_long_class, &val)) {
+ // This should never happen.
+ return ERR(INTERNAL);
+ }
+ dex_file = reinterpret_cast<const art::DexFile*>(static_cast<uintptr_t>(val.GetJ()));
+ }
+ }
+ }
+ if (dex_file == nullptr) {
+ dex_file = &klass->GetDexFile();
+ }
+ std::unique_ptr<FixedUpDexFile> fixed_dex_file(FixedUpDexFile::Create(*dex_file));
+ *dex_data_len = static_cast<jint>(fixed_dex_file->Size());
+ return CopyDataIntoJvmtiBuffer(env,
+ fixed_dex_file->Begin(),
+ fixed_dex_file->Size(),
+ /*out*/dex_data);
+}
+
+jvmtiError ArtClassDefinition::Init(ArtJvmTiEnv* env, jclass klass) {
+ jvmtiError res = InitCommon(env, klass);
+ if (res != OK) {
+ return res;
+ }
+ unsigned char* new_data = nullptr;
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::StackHandleScope<1> hs(self);
+ art::Handle<art::mirror::Class> m_klass(hs.NewHandle(self->DecodeJObject(klass)->AsClass()));
+ res = GetDexDataForRetransformation(env, m_klass, &dex_len_, &new_data);
+ if (res != OK) {
+ return res;
+ }
+ dex_data_ = MakeJvmtiUniquePtr(env, new_data);
+ if (m_klass->GetExtData() == nullptr || m_klass->GetExtData()->GetOriginalDexFile() == nullptr) {
+ // We have never redefined class this yet. Keep track of what the (de-quickened) dex file looks
+ // like so we can tell if anything has changed. Really we would like to just always do the
+ // 'else' block but the fact that we de-quickened stuff screws us over.
+ unsigned char* original_data_memory = nullptr;
+ res = CopyDataIntoJvmtiBuffer(env, dex_data_.get(), dex_len_, &original_data_memory);
+ original_dex_file_memory_ = MakeJvmtiUniquePtr(env, original_data_memory);
+ original_dex_file_ = art::ArrayRef<const unsigned char>(original_data_memory, dex_len_);
+ } else {
+ // We know that we have been redefined at least once (there is an original_dex_file set in
+ // the class) so we can just use the current dex file directly.
+ const art::DexFile& dex_file = m_klass->GetDexFile();
+ original_dex_file_ = art::ArrayRef<const unsigned char>(dex_file.Begin(), dex_file.Size());
+ }
+ return res;
+}
+
+jvmtiError ArtClassDefinition::Init(ArtJvmTiEnv* env, const jvmtiClassDefinition& def) {
+ jvmtiError res = InitCommon(env, def.klass);
+ if (res != OK) {
+ return res;
+ }
+ unsigned char* new_data = nullptr;
+ original_dex_file_ = art::ArrayRef<const unsigned char>(def.class_bytes, def.class_byte_count);
+ redefined_ = true;
+ dex_len_ = def.class_byte_count;
+ res = CopyDataIntoJvmtiBuffer(env, def.class_bytes, def.class_byte_count, /*out*/ &new_data);
+ dex_data_ = MakeJvmtiUniquePtr(env, new_data);
+ return res;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h
new file mode 100644
index 0000000..accc456
--- /dev/null
+++ b/openjdkjvmti/ti_class_definition.h
@@ -0,0 +1,131 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+
+#include "art_jvmti.h"
+
+#include "base/array_ref.h"
+
+namespace openjdkjvmti {
+
+// A struct that stores data needed for redefining/transforming classes. This structure should only
+// even be accessed from a single thread and must not survive past the completion of the
+// redefinition/retransformation function that created it.
+class ArtClassDefinition {
+ public:
+ ArtClassDefinition()
+ : klass_(nullptr),
+ loader_(nullptr),
+ name_(),
+ protection_domain_(nullptr),
+ dex_len_(0),
+ dex_data_(nullptr),
+ original_dex_file_memory_(nullptr),
+ original_dex_file_(),
+ redefined_(false) {}
+
+ jvmtiError Init(ArtJvmTiEnv* env, jclass klass);
+ jvmtiError Init(ArtJvmTiEnv* env, const jvmtiClassDefinition& def);
+
+ ArtClassDefinition(ArtClassDefinition&& o) = default;
+ ArtClassDefinition& operator=(ArtClassDefinition&& o) = default;
+
+ void SetNewDexData(ArtJvmTiEnv* env, jint new_dex_len, unsigned char* new_dex_data) {
+ DCHECK(IsInitialized());
+ if (new_dex_data == nullptr) {
+ return;
+ } else if (new_dex_data != dex_data_.get() || new_dex_len != dex_len_) {
+ dex_len_ = new_dex_len;
+ dex_data_ = MakeJvmtiUniquePtr(env, new_dex_data);
+ }
+ }
+
+ art::ArrayRef<const unsigned char> GetNewOriginalDexFile() const {
+ DCHECK(IsInitialized());
+ if (redefined_) {
+ return original_dex_file_;
+ } else {
+ return art::ArrayRef<const unsigned char>();
+ }
+ }
+
+ bool IsModified() const;
+
+ bool IsInitialized() const {
+ return klass_ != nullptr;
+ }
+
+ jclass GetClass() const {
+ DCHECK(IsInitialized());
+ return klass_;
+ }
+
+ jobject GetLoader() const {
+ DCHECK(IsInitialized());
+ return loader_;
+ }
+
+ const std::string& GetName() const {
+ DCHECK(IsInitialized());
+ return name_;
+ }
+
+ jobject GetProtectionDomain() const {
+ DCHECK(IsInitialized());
+ return protection_domain_;
+ }
+
+ art::ArrayRef<const unsigned char> GetDexData() const {
+ DCHECK(IsInitialized());
+ return art::ArrayRef<const unsigned char>(dex_data_.get(), dex_len_);
+ }
+
+ private:
+ jvmtiError InitCommon(ArtJvmTiEnv* env, jclass klass);
+
+ jclass klass_;
+ jobject loader_;
+ std::string name_;
+ jobject protection_domain_;
+ jint dex_len_;
+ JvmtiUniquePtr<unsigned char> dex_data_;
+ JvmtiUniquePtr<unsigned char> original_dex_file_memory_;
+ art::ArrayRef<const unsigned char> original_dex_file_;
+ bool redefined_;
+
+ DISALLOW_COPY_AND_ASSIGN(ArtClassDefinition);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
diff --git a/openjdkjvmti/ti_class_loader.cc b/openjdkjvmti/ti_class_loader.cc
new file mode 100644
index 0000000..e81e4bc
--- /dev/null
+++ b/openjdkjvmti/ti_class_loader.cc
@@ -0,0 +1,207 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_class_loader.h"
+
+#include <limits>
+
+#include "android-base/stringprintf.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "dex_file.h"
+#include "dex_file_types.h"
+#include "events-inl.h"
+#include "gc/allocation_listener.h"
+#include "gc/heap.h"
+#include "instrumentation.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti_allocator.h"
+#include "mirror/class.h"
+#include "mirror/class_ext.h"
+#include "mirror/object.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "object_lock.h"
+#include "runtime.h"
+#include "transform.h"
+
+namespace openjdkjvmti {
+
+bool ClassLoaderHelper::AddToClassLoader(art::Thread* self,
+ art::Handle<art::mirror::ClassLoader> loader,
+ const art::DexFile* dex_file) {
+ art::ScopedObjectAccessUnchecked soa(self);
+ art::StackHandleScope<3> hs(self);
+ if (art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
+ art::Runtime::Current()->GetClassLinker()->AppendToBootClassPath(self, *dex_file);
+ return true;
+ }
+ art::Handle<art::mirror::Object> java_dex_file_obj(
+ hs.NewHandle(FindSourceDexFileObject(self, loader)));
+ if (java_dex_file_obj.IsNull()) {
+ return false;
+ }
+ art::Handle<art::mirror::LongArray> old_cookie(hs.NewHandle(GetDexFileCookie(java_dex_file_obj)));
+ art::Handle<art::mirror::LongArray> cookie(hs.NewHandle(
+ AllocateNewDexFileCookie(self, old_cookie, dex_file)));
+ if (cookie.IsNull()) {
+ return false;
+ }
+ art::ScopedAssertNoThreadSuspension nts("Replacing cookie fields in j.l.DexFile object");
+ UpdateJavaDexFile(java_dex_file_obj.Get(), cookie.Get());
+ return true;
+}
+
+void ClassLoaderHelper::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+ art::ObjPtr<art::mirror::LongArray> new_cookie) {
+ art::ArtField* internal_cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
+ "mInternalCookie", "Ljava/lang/Object;");
+ art::ArtField* cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
+ "mCookie", "Ljava/lang/Object;");
+ CHECK(internal_cookie_field != nullptr);
+ art::ObjPtr<art::mirror::LongArray> orig_internal_cookie(
+ internal_cookie_field->GetObject(java_dex_file)->AsLongArray());
+ art::ObjPtr<art::mirror::LongArray> orig_cookie(
+ cookie_field->GetObject(java_dex_file)->AsLongArray());
+ internal_cookie_field->SetObject<false>(java_dex_file, new_cookie);
+ if (!orig_cookie.IsNull()) {
+ cookie_field->SetObject<false>(java_dex_file, new_cookie);
+ }
+}
+
+art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::GetDexFileCookie(
+ art::Handle<art::mirror::Object> java_dex_file_obj) {
+ // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until
+ // the object is finalized. Since they always point to the same array if mCookie is not null we
+ // just use the mInternalCookie field. We will update one or both of these fields later.
+ art::ArtField* internal_cookie_field = java_dex_file_obj->GetClass()->FindDeclaredInstanceField(
+ "mInternalCookie", "Ljava/lang/Object;");
+ // TODO Add check that mCookie is either null or same as mInternalCookie
+ CHECK(internal_cookie_field != nullptr);
+ return internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray();
+}
+
+art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
+ art::Thread* self,
+ art::Handle<art::mirror::LongArray> cookie,
+ const art::DexFile* dex_file) {
+ art::StackHandleScope<1> hs(self);
+ CHECK(cookie != nullptr);
+ CHECK_GE(cookie->GetLength(), 1);
+ art::Handle<art::mirror::LongArray> new_cookie(
+ hs.NewHandle(art::mirror::LongArray::Alloc(self, cookie->GetLength() + 1)));
+ if (new_cookie == nullptr) {
+ self->AssertPendingOOMException();
+ return nullptr;
+ }
+ // Copy the oat-dex field at the start.
+ new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0));
+ // This must match the casts in runtime/native/dalvik_system_DexFile.cc:ConvertDexFilesToJavaArray
+ new_cookie->SetWithoutChecks<false>(
+ 1, static_cast<int64_t>(reinterpret_cast<uintptr_t>(dex_file)));
+ new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1);
+ return new_cookie.Get();
+}
+
+// TODO This should return the actual source java.lang.DexFile object for the klass being loaded.
+art::ObjPtr<art::mirror::Object> ClassLoaderHelper::FindSourceDexFileObject(
+ art::Thread* self, art::Handle<art::mirror::ClassLoader> loader) {
+ const char* dex_path_list_element_array_name = "[Ldalvik/system/DexPathList$Element;";
+ const char* dex_path_list_element_name = "Ldalvik/system/DexPathList$Element;";
+ const char* dex_file_name = "Ldalvik/system/DexFile;";
+ const char* dex_path_list_name = "Ldalvik/system/DexPathList;";
+ const char* dex_class_loader_name = "Ldalvik/system/BaseDexClassLoader;";
+
+ CHECK(!self->IsExceptionPending());
+ art::StackHandleScope<5> hs(self);
+ art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
+
+ art::Handle<art::mirror::ClassLoader> null_loader(hs.NewHandle<art::mirror::ClassLoader>(
+ nullptr));
+ art::Handle<art::mirror::Class> base_dex_loader_class(hs.NewHandle(class_linker->FindClass(
+ self, dex_class_loader_name, null_loader)));
+
+ // Get all the ArtFields so we can look in the BaseDexClassLoader
+ art::ArtField* path_list_field = base_dex_loader_class->FindDeclaredInstanceField(
+ "pathList", dex_path_list_name);
+ CHECK(path_list_field != nullptr);
+
+ art::ArtField* dex_path_list_element_field =
+ class_linker->FindClass(self, dex_path_list_name, null_loader)
+ ->FindDeclaredInstanceField("dexElements", dex_path_list_element_array_name);
+ CHECK(dex_path_list_element_field != nullptr);
+
+ art::ArtField* element_dex_file_field =
+ class_linker->FindClass(self, dex_path_list_element_name, null_loader)
+ ->FindDeclaredInstanceField("dexFile", dex_file_name);
+ CHECK(element_dex_file_field != nullptr);
+
+ // Check if loader is a BaseDexClassLoader
+ art::Handle<art::mirror::Class> loader_class(hs.NewHandle(loader->GetClass()));
+ // Currently only base_dex_loader is allowed to actually define classes but if this changes in the
+ // future we should make sure to support all class loader types.
+ if (!loader_class->IsSubClass(base_dex_loader_class.Get())) {
+ LOG(ERROR) << "The classloader is not a BaseDexClassLoader which is currently the only "
+ << "supported class loader type!";
+ return nullptr;
+ }
+ // Start navigating the fields of the loader (now known to be a BaseDexClassLoader derivative)
+ art::Handle<art::mirror::Object> path_list(
+ hs.NewHandle(path_list_field->GetObject(loader.Get())));
+ CHECK(path_list != nullptr);
+ CHECK(!self->IsExceptionPending());
+ art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle(
+ dex_path_list_element_field->GetObject(path_list.Get())->
+ AsObjectArray<art::mirror::Object>()));
+ CHECK(!self->IsExceptionPending());
+ CHECK(dex_elements_list != nullptr);
+ size_t num_elements = dex_elements_list->GetLength();
+ // Iterate over the DexPathList$Element to find the right one
+ for (size_t i = 0; i < num_elements; i++) {
+ art::ObjPtr<art::mirror::Object> current_element = dex_elements_list->Get(i);
+ CHECK(!current_element.IsNull());
+ // TODO It would be cleaner to put the art::DexFile into the dalvik.system.DexFile the class
+ // comes from but it is more annoying because we would need to find this class. It is not
+ // necessary for proper function since we just need to be in front of the classes old dex file
+ // in the path.
+ art::ObjPtr<art::mirror::Object> first_dex_file(
+ element_dex_file_field->GetObject(current_element));
+ if (!first_dex_file.IsNull()) {
+ return first_dex_file;
+ }
+ }
+ return nullptr;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_class_loader.h b/openjdkjvmti/ti_class_loader.h
new file mode 100644
index 0000000..767e258
--- /dev/null
+++ b/openjdkjvmti/ti_class_loader.h
@@ -0,0 +1,99 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+
+#include <string>
+
+#include <jni.h>
+
+#include "art_jvmti.h"
+#include "art_method.h"
+#include "base/array_slice.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "gc_root-inl.h"
+#include "globals.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti.h"
+#include "linear_alloc.h"
+#include "mem_map.h"
+#include "mirror/array-inl.h"
+#include "mirror/array.h"
+#include "mirror/class-inl.h"
+#include "mirror/class.h"
+#include "mirror/class_loader-inl.h"
+#include "mirror/string-inl.h"
+#include "oat_file.h"
+#include "obj_ptr.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread_list.h"
+#include "ti_class_definition.h"
+#include "transform.h"
+#include "utf.h"
+#include "utils/dex_cache_arrays_layout-inl.h"
+
+namespace openjdkjvmti {
+
+// Class that can redefine a single class's methods.
+// TODO We should really make this be driven by an outside class so we can do multiple classes at
+// the same time and have less required cleanup.
+class ClassLoaderHelper {
+ public:
+ static bool AddToClassLoader(art::Thread* self,
+ art::Handle<art::mirror::ClassLoader> loader,
+ const art::DexFile* dex_file)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // Finds a java.lang.DexFile object that is associated with the given ClassLoader. Each of these
+ // j.l.DexFile objects holds several art::DexFile*s in it.
+ // TODO This should return the actual source java.lang.DexFile object for the klass being loaded.
+ static art::ObjPtr<art::mirror::Object> FindSourceDexFileObject(
+ art::Thread* self, art::Handle<art::mirror::ClassLoader> loader)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ static art::ObjPtr<art::mirror::LongArray> GetDexFileCookie(
+ art::Handle<art::mirror::Object> java_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ static art::ObjPtr<art::mirror::LongArray> AllocateNewDexFileCookie(
+ art::Thread* self,
+ art::Handle<art::mirror::LongArray> old_dex_file_cookie,
+ const art::DexFile* new_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ static void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+ art::ObjPtr<art::mirror::LongArray> new_cookie)
+ REQUIRES(art::Roles::uninterruptible_) REQUIRES_SHARED(art::Locks::mutator_lock_);
+};
+
+} // namespace openjdkjvmti
+#endif // ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
diff --git a/openjdkjvmti/ti_dump.cc b/openjdkjvmti/ti_dump.cc
new file mode 100644
index 0000000..809a5e4
--- /dev/null
+++ b/openjdkjvmti/ti_dump.cc
@@ -0,0 +1,73 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_dump.h"
+
+#include <limits>
+
+#include "art_jvmti.h"
+#include "base/mutex.h"
+#include "events-inl.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+
+namespace openjdkjvmti {
+
+struct DumpCallback : public art::RuntimeSigQuitCallback {
+ void SigQuit() OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::Thread* thread = art::Thread::Current();
+ art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kDataDumpRequest>(nullptr);
+ }
+
+ EventHandler* event_handler = nullptr;
+};
+
+static DumpCallback gDumpCallback;
+
+void DumpUtil::Register(EventHandler* handler) {
+ gDumpCallback.event_handler = handler;
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add sigquit callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&gDumpCallback);
+}
+
+void DumpUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove sigquit callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&gDumpCallback);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_dump.h b/openjdkjvmti/ti_dump.h
new file mode 100644
index 0000000..323bf56
--- /dev/null
+++ b/openjdkjvmti/ti_dump.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_DUMP_H_
+#define ART_OPENJDKJVMTI_TI_DUMP_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class DumpUtil {
+ public:
+ static void Register(EventHandler* event_handler);
+ static void Unregister();
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_DUMP_H_
diff --git a/openjdkjvmti/ti_field.cc b/openjdkjvmti/ti_field.cc
new file mode 100644
index 0000000..c45b926
--- /dev/null
+++ b/openjdkjvmti/ti_field.cc
@@ -0,0 +1,254 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_field.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/enums.h"
+#include "dex_file_annotations.h"
+#include "jni_internal.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+// Note: For all these functions, we could do a check that the field actually belongs to the given
+// class. But the spec seems to assume a certain encoding of the field ID, and so doesn't
+// specify any errors.
+
+jvmtiError FieldUtil::GetFieldName(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr) {
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ if (field == nullptr) {
+ return ERR(INVALID_FIELDID);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ArtField* art_field = art::jni::DecodeArtField(field);
+
+ JvmtiUniquePtr<char[]> name_copy;
+ if (name_ptr != nullptr) {
+ const char* field_name = art_field->GetName();
+ if (field_name == nullptr) {
+ field_name = "<error>";
+ }
+ jvmtiError ret;
+ name_copy = CopyString(env, field_name, &ret);
+ if (name_copy == nullptr) {
+ return ret;
+ }
+ *name_ptr = name_copy.get();
+ }
+
+ JvmtiUniquePtr<char[]> signature_copy;
+ if (signature_ptr != nullptr) {
+ const char* sig = art_field->GetTypeDescriptor();
+ jvmtiError ret;
+ signature_copy = CopyString(env, sig, &ret);
+ if (signature_copy == nullptr) {
+ return ret;
+ }
+ *signature_ptr = signature_copy.get();
+ }
+
+ if (generic_ptr != nullptr) {
+ *generic_ptr = nullptr;
+ if (!art_field->GetDeclaringClass()->IsProxyClass()) {
+ art::mirror::ObjectArray<art::mirror::String>* str_array =
+ art::annotations::GetSignatureAnnotationForField(art_field);
+ if (str_array != nullptr) {
+ std::ostringstream oss;
+ for (int32_t i = 0; i != str_array->GetLength(); ++i) {
+ oss << str_array->Get(i)->ToModifiedUtf8();
+ }
+ std::string output_string = oss.str();
+ jvmtiError ret;
+ JvmtiUniquePtr<char[]> copy = CopyString(env, output_string.c_str(), &ret);
+ if (copy == nullptr) {
+ return ret;
+ }
+ *generic_ptr = copy.release();
+ } else if (soa.Self()->IsExceptionPending()) {
+ // TODO: Should we report an error here?
+ soa.Self()->ClearException();
+ }
+ }
+ }
+
+ // Everything is fine, release the buffers.
+ name_copy.release();
+ signature_copy.release();
+
+ return ERR(NONE);
+}
+
+jvmtiError FieldUtil::GetFieldDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass klass,
+ jfieldID field,
+ jclass* declaring_class_ptr) {
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ if (field == nullptr) {
+ return ERR(INVALID_FIELDID);
+ }
+ if (declaring_class_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ArtField* art_field = art::jni::DecodeArtField(field);
+ art::ObjPtr<art::mirror::Class> field_klass = art_field->GetDeclaringClass();
+
+ *declaring_class_ptr = soa.AddLocalReference<jclass>(field_klass);
+
+ return ERR(NONE);
+}
+
+jvmtiError FieldUtil::GetFieldModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass klass,
+ jfieldID field,
+ jint* modifiers_ptr) {
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ if (field == nullptr) {
+ return ERR(INVALID_FIELDID);
+ }
+ if (modifiers_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ArtField* art_field = art::jni::DecodeArtField(field);
+ // Note: Keep this code in sync with Field.getModifiers.
+ uint32_t modifiers = art_field->GetAccessFlags() & 0xFFFF;
+
+ *modifiers_ptr = modifiers;
+ return ERR(NONE);
+}
+
+jvmtiError FieldUtil::IsFieldSynthetic(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass klass,
+ jfieldID field,
+ jboolean* is_synthetic_ptr) {
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ if (field == nullptr) {
+ return ERR(INVALID_FIELDID);
+ }
+ if (is_synthetic_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ArtField* art_field = art::jni::DecodeArtField(field);
+ uint32_t modifiers = art_field->GetAccessFlags();
+
+ *is_synthetic_ptr = ((modifiers & art::kAccSynthetic) != 0) ? JNI_TRUE : JNI_FALSE;
+ return ERR(NONE);
+}
+
+jvmtiError FieldUtil::SetFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ if (field == nullptr) {
+ return ERR(INVALID_FIELDID);
+ }
+ auto res_pair = env->modify_watched_fields.insert(art::jni::DecodeArtField(field));
+ if (!res_pair.second) {
+ // Didn't get inserted because it's already present!
+ return ERR(DUPLICATE);
+ }
+ return OK;
+}
+
+jvmtiError FieldUtil::ClearFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ if (field == nullptr) {
+ return ERR(INVALID_FIELDID);
+ }
+ auto pos = env->modify_watched_fields.find(art::jni::DecodeArtField(field));
+ if (pos == env->modify_watched_fields.end()) {
+ return ERR(NOT_FOUND);
+ }
+ env->modify_watched_fields.erase(pos);
+ return OK;
+}
+
+jvmtiError FieldUtil::SetFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ if (field == nullptr) {
+ return ERR(INVALID_FIELDID);
+ }
+ auto res_pair = env->access_watched_fields.insert(art::jni::DecodeArtField(field));
+ if (!res_pair.second) {
+ // Didn't get inserted because it's already present!
+ return ERR(DUPLICATE);
+ }
+ return OK;
+}
+
+jvmtiError FieldUtil::ClearFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (klass == nullptr) {
+ return ERR(INVALID_CLASS);
+ }
+ if (field == nullptr) {
+ return ERR(INVALID_FIELDID);
+ }
+ auto pos = env->access_watched_fields.find(art::jni::DecodeArtField(field));
+ if (pos == env->access_watched_fields.end()) {
+ return ERR(NOT_FOUND);
+ }
+ env->access_watched_fields.erase(pos);
+ return OK;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_field.h b/openjdkjvmti/ti_field.h
new file mode 100644
index 0000000..8a229ed
--- /dev/null
+++ b/openjdkjvmti/ti_field.h
@@ -0,0 +1,72 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_FIELD_H_
+#define ART_OPENJDKJVMTI_TI_FIELD_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class FieldUtil {
+ public:
+ static jvmtiError GetFieldName(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr);
+
+ static jvmtiError GetFieldDeclaringClass(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jclass* declaring_class_ptr);
+
+ static jvmtiError GetFieldModifiers(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jint* modifiers_ptr);
+
+ static jvmtiError IsFieldSynthetic(jvmtiEnv* env,
+ jclass klass,
+ jfieldID field,
+ jboolean* is_synthetic_ptr);
+
+ static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+ static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+ static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+ static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_FIELD_H_
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
new file mode 100644
index 0000000..3397210
--- /dev/null
+++ b/openjdkjvmti/ti_heap.cc
@@ -0,0 +1,1522 @@
+/*
+ * Copyright (C) 2016 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 "ti_heap.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "class_linker.h"
+#include "gc/heap-visit-objects-inl.h"
+#include "gc/heap.h"
+#include "gc_root-inl.h"
+#include "java_frame_root_info.h"
+#include "jni_env_ext.h"
+#include "jni_internal.h"
+#include "jvmti_weak_table-inl.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "mirror/object_array-inl.h"
+#include "obj_ptr-inl.h"
+#include "object_tagging.h"
+#include "primitive.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+
+namespace openjdkjvmti {
+
+namespace {
+
+struct IndexCache {
+ // The number of interface fields implemented by the class. This is a prefix to all assigned
+ // field indices.
+ size_t interface_fields;
+
+ // It would be nice to also cache the following, but it is complicated to wire up into the
+ // generic visit:
+ // The number of fields in interfaces and superclasses. This is the first index assigned to
+ // fields of the class.
+ // size_t superclass_fields;
+};
+using IndexCachingTable = JvmtiWeakTable<IndexCache>;
+
+static IndexCachingTable gIndexCachingTable;
+
+// Report the contents of a string, if a callback is set.
+jint ReportString(art::ObjPtr<art::mirror::Object> obj,
+ jvmtiEnv* env,
+ ObjectTagTable* tag_table,
+ const jvmtiHeapCallbacks* cb,
+ const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (UNLIKELY(cb->string_primitive_value_callback != nullptr) && obj->IsString()) {
+ art::ObjPtr<art::mirror::String> str = obj->AsString();
+ int32_t string_length = str->GetLength();
+ JvmtiUniquePtr<uint16_t[]> data;
+
+ if (string_length > 0) {
+ jvmtiError alloc_error;
+ data = AllocJvmtiUniquePtr<uint16_t[]>(env, string_length, &alloc_error);
+ if (data == nullptr) {
+ // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+ // back? For now just warn.
+ LOG(WARNING) << "Unable to allocate buffer for string reporting! Silently dropping value."
+ << " >" << str->ToModifiedUtf8() << "<";
+ return 0;
+ }
+
+ if (str->IsCompressed()) {
+ uint8_t* compressed_data = str->GetValueCompressed();
+ for (int32_t i = 0; i != string_length; ++i) {
+ data[i] = compressed_data[i];
+ }
+ } else {
+ // Can copy directly.
+ memcpy(data.get(), str->GetValue(), string_length * sizeof(uint16_t));
+ }
+ }
+
+ const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+ jlong string_tag = tag_table->GetTagOrZero(obj.Ptr());
+ const jlong saved_string_tag = string_tag;
+
+ jint result = cb->string_primitive_value_callback(class_tag,
+ obj->SizeOf(),
+ &string_tag,
+ data.get(),
+ string_length,
+ const_cast<void*>(user_data));
+ if (string_tag != saved_string_tag) {
+ tag_table->Set(obj.Ptr(), string_tag);
+ }
+
+ return result;
+ }
+ return 0;
+}
+
+// Report the contents of a primitive array, if a callback is set.
+jint ReportPrimitiveArray(art::ObjPtr<art::mirror::Object> obj,
+ jvmtiEnv* env,
+ ObjectTagTable* tag_table,
+ const jvmtiHeapCallbacks* cb,
+ const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (UNLIKELY(cb->array_primitive_value_callback != nullptr) &&
+ obj->IsArrayInstance() &&
+ !obj->IsObjectArray()) {
+ art::ObjPtr<art::mirror::Array> array = obj->AsArray();
+ int32_t array_length = array->GetLength();
+ size_t component_size = array->GetClass()->GetComponentSize();
+ art::Primitive::Type art_prim_type = array->GetClass()->GetComponentType()->GetPrimitiveType();
+ jvmtiPrimitiveType prim_type =
+ static_cast<jvmtiPrimitiveType>(art::Primitive::Descriptor(art_prim_type)[0]);
+ DCHECK(prim_type == JVMTI_PRIMITIVE_TYPE_BOOLEAN ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_BYTE ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_CHAR ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_SHORT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_INT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_LONG ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_FLOAT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_DOUBLE);
+
+ const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+ jlong array_tag = tag_table->GetTagOrZero(obj.Ptr());
+ const jlong saved_array_tag = array_tag;
+
+ jint result;
+ if (array_length == 0) {
+ result = cb->array_primitive_value_callback(class_tag,
+ obj->SizeOf(),
+ &array_tag,
+ 0,
+ prim_type,
+ nullptr,
+ const_cast<void*>(user_data));
+ } else {
+ jvmtiError alloc_error;
+ JvmtiUniquePtr<char[]> data = AllocJvmtiUniquePtr<char[]>(env,
+ array_length * component_size,
+ &alloc_error);
+ if (data == nullptr) {
+ // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+ // back? For now just warn.
+ LOG(WARNING) << "Unable to allocate buffer for array reporting! Silently dropping value.";
+ return 0;
+ }
+
+ memcpy(data.get(), array->GetRawData(component_size, 0), array_length * component_size);
+
+ result = cb->array_primitive_value_callback(class_tag,
+ obj->SizeOf(),
+ &array_tag,
+ array_length,
+ prim_type,
+ data.get(),
+ const_cast<void*>(user_data));
+ }
+
+ if (array_tag != saved_array_tag) {
+ tag_table->Set(obj.Ptr(), array_tag);
+ }
+
+ return result;
+ }
+ return 0;
+}
+
+template <typename UserData>
+bool VisitorFalse(art::ObjPtr<art::mirror::Object> obj ATTRIBUTE_UNUSED,
+ art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED,
+ art::ArtField& field ATTRIBUTE_UNUSED,
+ size_t field_index ATTRIBUTE_UNUSED,
+ UserData* user_data ATTRIBUTE_UNUSED) {
+ return false;
+}
+
+template <typename UserData, bool kCallVisitorOnRecursion>
+class FieldVisitor {
+ public:
+ // Report the contents of a primitive fields of the given object, if a callback is set.
+ template <typename StaticPrimitiveVisitor,
+ typename StaticReferenceVisitor,
+ typename InstancePrimitiveVisitor,
+ typename InstanceReferenceVisitor>
+ static bool ReportFields(art::ObjPtr<art::mirror::Object> obj,
+ UserData* user_data,
+ StaticPrimitiveVisitor& static_prim_visitor,
+ StaticReferenceVisitor& static_ref_visitor,
+ InstancePrimitiveVisitor& instance_prim_visitor,
+ InstanceReferenceVisitor& instance_ref_visitor)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ FieldVisitor fv(user_data);
+
+ if (obj->IsClass()) {
+ // When visiting a class, we only visit the static fields of the given class. No field of
+ // superclasses is visited.
+ art::ObjPtr<art::mirror::Class> klass = obj->AsClass();
+ // Only report fields on resolved classes. We need valid field data.
+ if (!klass->IsResolved()) {
+ return false;
+ }
+ return fv.ReportFieldsImpl(nullptr,
+ obj->AsClass(),
+ obj->AsClass()->IsInterface(),
+ static_prim_visitor,
+ static_ref_visitor,
+ instance_prim_visitor,
+ instance_ref_visitor);
+ } else {
+ // See comment above. Just double-checking here, but an instance *should* mean the class was
+ // resolved.
+ DCHECK(obj->GetClass()->IsResolved() || obj->GetClass()->IsErroneousResolved());
+ return fv.ReportFieldsImpl(obj,
+ obj->GetClass(),
+ false,
+ static_prim_visitor,
+ static_ref_visitor,
+ instance_prim_visitor,
+ instance_ref_visitor);
+ }
+ }
+
+ private:
+ explicit FieldVisitor(UserData* user_data) : user_data_(user_data) {}
+
+ // Report the contents of fields of the given object. If obj is null, report the static fields,
+ // otherwise the instance fields.
+ template <typename StaticPrimitiveVisitor,
+ typename StaticReferenceVisitor,
+ typename InstancePrimitiveVisitor,
+ typename InstanceReferenceVisitor>
+ bool ReportFieldsImpl(art::ObjPtr<art::mirror::Object> obj,
+ art::ObjPtr<art::mirror::Class> klass,
+ bool skip_java_lang_object,
+ StaticPrimitiveVisitor& static_prim_visitor,
+ StaticReferenceVisitor& static_ref_visitor,
+ InstancePrimitiveVisitor& instance_prim_visitor,
+ InstanceReferenceVisitor& instance_ref_visitor)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // Compute the offset of field indices.
+ size_t interface_field_count = CountInterfaceFields(klass);
+
+ size_t tmp;
+ bool aborted = ReportFieldsRecursive(obj,
+ klass,
+ interface_field_count,
+ skip_java_lang_object,
+ static_prim_visitor,
+ static_ref_visitor,
+ instance_prim_visitor,
+ instance_ref_visitor,
+ &tmp);
+ return aborted;
+ }
+
+ // Visit primitive fields in an object (instance). Return true if the visit was aborted.
+ template <typename StaticPrimitiveVisitor,
+ typename StaticReferenceVisitor,
+ typename InstancePrimitiveVisitor,
+ typename InstanceReferenceVisitor>
+ bool ReportFieldsRecursive(art::ObjPtr<art::mirror::Object> obj,
+ art::ObjPtr<art::mirror::Class> klass,
+ size_t interface_fields,
+ bool skip_java_lang_object,
+ StaticPrimitiveVisitor& static_prim_visitor,
+ StaticReferenceVisitor& static_ref_visitor,
+ InstancePrimitiveVisitor& instance_prim_visitor,
+ InstanceReferenceVisitor& instance_ref_visitor,
+ size_t* field_index_out)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK(klass != nullptr);
+ size_t field_index;
+ if (klass->GetSuperClass() == nullptr) {
+ // j.l.Object. Start with the fields from interfaces.
+ field_index = interface_fields;
+ if (skip_java_lang_object) {
+ *field_index_out = field_index;
+ return false;
+ }
+ } else {
+ // Report superclass fields.
+ if (kCallVisitorOnRecursion) {
+ if (ReportFieldsRecursive(obj,
+ klass->GetSuperClass(),
+ interface_fields,
+ skip_java_lang_object,
+ static_prim_visitor,
+ static_ref_visitor,
+ instance_prim_visitor,
+ instance_ref_visitor,
+ &field_index)) {
+ return true;
+ }
+ } else {
+ // Still call, but with empty visitor. This is required for correct counting.
+ ReportFieldsRecursive(obj,
+ klass->GetSuperClass(),
+ interface_fields,
+ skip_java_lang_object,
+ VisitorFalse<UserData>,
+ VisitorFalse<UserData>,
+ VisitorFalse<UserData>,
+ VisitorFalse<UserData>,
+ &field_index);
+ }
+ }
+
+ // Now visit fields for the current klass.
+
+ for (auto& static_field : klass->GetSFields()) {
+ if (static_field.IsPrimitiveType()) {
+ if (static_prim_visitor(obj,
+ klass,
+ static_field,
+ field_index,
+ user_data_)) {
+ return true;
+ }
+ } else {
+ if (static_ref_visitor(obj,
+ klass,
+ static_field,
+ field_index,
+ user_data_)) {
+ return true;
+ }
+ }
+ field_index++;
+ }
+
+ for (auto& instance_field : klass->GetIFields()) {
+ if (instance_field.IsPrimitiveType()) {
+ if (instance_prim_visitor(obj,
+ klass,
+ instance_field,
+ field_index,
+ user_data_)) {
+ return true;
+ }
+ } else {
+ if (instance_ref_visitor(obj,
+ klass,
+ instance_field,
+ field_index,
+ user_data_)) {
+ return true;
+ }
+ }
+ field_index++;
+ }
+
+ *field_index_out = field_index;
+ return false;
+ }
+
+ // Implements a visit of the implemented interfaces of a given class.
+ template <typename T>
+ struct RecursiveInterfaceVisit {
+ static void VisitStatic(art::Thread* self, art::ObjPtr<art::mirror::Class> klass, T& visitor)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ RecursiveInterfaceVisit rv;
+ rv.Visit(self, klass, visitor);
+ }
+
+ void Visit(art::Thread* self, art::ObjPtr<art::mirror::Class> klass, T& visitor)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // First visit the parent, to get the order right.
+ // (We do this in preparation for actual visiting of interface fields.)
+ if (klass->GetSuperClass() != nullptr) {
+ Visit(self, klass->GetSuperClass(), visitor);
+ }
+ for (uint32_t i = 0; i != klass->NumDirectInterfaces(); ++i) {
+ art::ObjPtr<art::mirror::Class> inf_klass =
+ art::mirror::Class::GetDirectInterface(self, klass, i);
+ DCHECK(inf_klass != nullptr);
+ VisitInterface(self, inf_klass, visitor);
+ }
+ }
+
+ void VisitInterface(art::Thread* self, art::ObjPtr<art::mirror::Class> inf_klass, T& visitor)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ auto it = visited_interfaces.find(inf_klass.Ptr());
+ if (it != visited_interfaces.end()) {
+ return;
+ }
+ visited_interfaces.insert(inf_klass.Ptr());
+
+ // Let the visitor know about this one. Note that this order is acceptable, as the ordering
+ // of these fields never matters for known visitors.
+ visitor(inf_klass);
+
+ // Now visit the superinterfaces.
+ for (uint32_t i = 0; i != inf_klass->NumDirectInterfaces(); ++i) {
+ art::ObjPtr<art::mirror::Class> super_inf_klass =
+ art::mirror::Class::GetDirectInterface(self, inf_klass, i);
+ DCHECK(super_inf_klass != nullptr);
+ VisitInterface(self, super_inf_klass, visitor);
+ }
+ }
+
+ std::unordered_set<art::mirror::Class*> visited_interfaces;
+ };
+
+ // Counting interface fields. Note that we cannot use the interface table, as that only contains
+ // "non-marker" interfaces (= interfaces with methods).
+ static size_t CountInterfaceFields(art::ObjPtr<art::mirror::Class> klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // Do we have a cached value?
+ IndexCache tmp;
+ if (gIndexCachingTable.GetTag(klass.Ptr(), &tmp)) {
+ return tmp.interface_fields;
+ }
+
+ size_t count = 0;
+ auto visitor = [&count](art::ObjPtr<art::mirror::Class> inf_klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK(inf_klass->IsInterface());
+ DCHECK_EQ(0u, inf_klass->NumInstanceFields());
+ count += inf_klass->NumStaticFields();
+ };
+ RecursiveInterfaceVisit<decltype(visitor)>::VisitStatic(art::Thread::Current(), klass, visitor);
+
+ // Store this into the cache.
+ tmp.interface_fields = count;
+ gIndexCachingTable.Set(klass.Ptr(), tmp);
+
+ return count;
+ }
+
+ UserData* user_data_;
+};
+
+// Debug helper. Prints the structure of an object.
+template <bool kStatic, bool kRef>
+struct DumpVisitor {
+ static bool Callback(art::ObjPtr<art::mirror::Object> obj ATTRIBUTE_UNUSED,
+ art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED,
+ art::ArtField& field,
+ size_t field_index,
+ void* user_data ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ LOG(ERROR) << (kStatic ? "static " : "instance ")
+ << (kRef ? "ref " : "primitive ")
+ << field.PrettyField()
+ << " @ "
+ << field_index;
+ return false;
+ }
+};
+ATTRIBUTE_UNUSED
+void DumpObjectFields(art::ObjPtr<art::mirror::Object> obj)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (obj->IsClass()) {
+ FieldVisitor<void, false>:: ReportFields(obj,
+ nullptr,
+ DumpVisitor<true, false>::Callback,
+ DumpVisitor<true, true>::Callback,
+ DumpVisitor<false, false>::Callback,
+ DumpVisitor<false, true>::Callback);
+ } else {
+ FieldVisitor<void, true>::ReportFields(obj,
+ nullptr,
+ DumpVisitor<true, false>::Callback,
+ DumpVisitor<true, true>::Callback,
+ DumpVisitor<false, false>::Callback,
+ DumpVisitor<false, true>::Callback);
+ }
+}
+
+class ReportPrimitiveField {
+ public:
+ static bool Report(art::ObjPtr<art::mirror::Object> obj,
+ ObjectTagTable* tag_table,
+ const jvmtiHeapCallbacks* cb,
+ const void* user_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (UNLIKELY(cb->primitive_field_callback != nullptr)) {
+ jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+ ReportPrimitiveField rpf(tag_table, class_tag, cb, user_data);
+ if (obj->IsClass()) {
+ return FieldVisitor<ReportPrimitiveField, false>::ReportFields(
+ obj,
+ &rpf,
+ ReportPrimitiveFieldCallback<true>,
+ VisitorFalse<ReportPrimitiveField>,
+ VisitorFalse<ReportPrimitiveField>,
+ VisitorFalse<ReportPrimitiveField>);
+ } else {
+ return FieldVisitor<ReportPrimitiveField, true>::ReportFields(
+ obj,
+ &rpf,
+ VisitorFalse<ReportPrimitiveField>,
+ VisitorFalse<ReportPrimitiveField>,
+ ReportPrimitiveFieldCallback<false>,
+ VisitorFalse<ReportPrimitiveField>);
+ }
+ }
+ return false;
+ }
+
+
+ private:
+ ReportPrimitiveField(ObjectTagTable* tag_table,
+ jlong class_tag,
+ const jvmtiHeapCallbacks* cb,
+ const void* user_data)
+ : tag_table_(tag_table), class_tag_(class_tag), cb_(cb), user_data_(user_data) {}
+
+ template <bool kReportStatic>
+ static bool ReportPrimitiveFieldCallback(art::ObjPtr<art::mirror::Object> obj,
+ art::ObjPtr<art::mirror::Class> klass,
+ art::ArtField& field,
+ size_t field_index,
+ ReportPrimitiveField* user_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::Primitive::Type art_prim_type = field.GetTypeAsPrimitiveType();
+ jvmtiPrimitiveType prim_type =
+ static_cast<jvmtiPrimitiveType>(art::Primitive::Descriptor(art_prim_type)[0]);
+ DCHECK(prim_type == JVMTI_PRIMITIVE_TYPE_BOOLEAN ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_BYTE ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_CHAR ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_SHORT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_INT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_LONG ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_FLOAT ||
+ prim_type == JVMTI_PRIMITIVE_TYPE_DOUBLE);
+ jvmtiHeapReferenceInfo info;
+ info.field.index = field_index;
+
+ jvalue value;
+ memset(&value, 0, sizeof(jvalue));
+ art::ObjPtr<art::mirror::Object> src = kReportStatic ? klass : obj;
+ switch (art_prim_type) {
+ case art::Primitive::Type::kPrimBoolean:
+ value.z = field.GetBoolean(src) == 0 ? JNI_FALSE : JNI_TRUE;
+ break;
+ case art::Primitive::Type::kPrimByte:
+ value.b = field.GetByte(src);
+ break;
+ case art::Primitive::Type::kPrimChar:
+ value.c = field.GetChar(src);
+ break;
+ case art::Primitive::Type::kPrimShort:
+ value.s = field.GetShort(src);
+ break;
+ case art::Primitive::Type::kPrimInt:
+ value.i = field.GetInt(src);
+ break;
+ case art::Primitive::Type::kPrimLong:
+ value.j = field.GetLong(src);
+ break;
+ case art::Primitive::Type::kPrimFloat:
+ value.f = field.GetFloat(src);
+ break;
+ case art::Primitive::Type::kPrimDouble:
+ value.d = field.GetDouble(src);
+ break;
+ case art::Primitive::Type::kPrimVoid:
+ case art::Primitive::Type::kPrimNot: {
+ LOG(FATAL) << "Should not reach here";
+ UNREACHABLE();
+ }
+ }
+
+ jlong obj_tag = user_data->tag_table_->GetTagOrZero(src.Ptr());
+ const jlong saved_obj_tag = obj_tag;
+
+ jint ret = user_data->cb_->primitive_field_callback(kReportStatic
+ ? JVMTI_HEAP_REFERENCE_STATIC_FIELD
+ : JVMTI_HEAP_REFERENCE_FIELD,
+ &info,
+ user_data->class_tag_,
+ &obj_tag,
+ value,
+ prim_type,
+ const_cast<void*>(user_data->user_data_));
+
+ if (saved_obj_tag != obj_tag) {
+ user_data->tag_table_->Set(src.Ptr(), obj_tag);
+ }
+
+ if ((ret & JVMTI_VISIT_ABORT) != 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ ObjectTagTable* tag_table_;
+ jlong class_tag_;
+ const jvmtiHeapCallbacks* cb_;
+ const void* user_data_;
+};
+
+struct HeapFilter {
+ explicit HeapFilter(jint heap_filter)
+ : filter_out_tagged((heap_filter & JVMTI_HEAP_FILTER_TAGGED) != 0),
+ filter_out_untagged((heap_filter & JVMTI_HEAP_FILTER_UNTAGGED) != 0),
+ filter_out_class_tagged((heap_filter & JVMTI_HEAP_FILTER_CLASS_TAGGED) != 0),
+ filter_out_class_untagged((heap_filter & JVMTI_HEAP_FILTER_CLASS_UNTAGGED) != 0),
+ any_filter(filter_out_tagged ||
+ filter_out_untagged ||
+ filter_out_class_tagged ||
+ filter_out_class_untagged) {
+ }
+
+ bool ShouldReportByHeapFilter(jlong tag, jlong class_tag) const {
+ if (!any_filter) {
+ return true;
+ }
+
+ if ((tag == 0 && filter_out_untagged) || (tag != 0 && filter_out_tagged)) {
+ return false;
+ }
+
+ if ((class_tag == 0 && filter_out_class_untagged) ||
+ (class_tag != 0 && filter_out_class_tagged)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ const bool filter_out_tagged;
+ const bool filter_out_untagged;
+ const bool filter_out_class_tagged;
+ const bool filter_out_class_untagged;
+ const bool any_filter;
+};
+
+} // namespace
+
+void HeapUtil::Register() {
+ art::Runtime::Current()->AddSystemWeakHolder(&gIndexCachingTable);
+}
+
+void HeapUtil::Unregister() {
+ art::Runtime::Current()->RemoveSystemWeakHolder(&gIndexCachingTable);
+}
+
+template <typename T>
+static jvmtiError DoIterateThroughHeap(T fn,
+ jvmtiEnv* env,
+ ObjectTagTable* tag_table,
+ jint heap_filter_int,
+ jclass klass,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ if (callbacks == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self); // Now we know we have the shared lock.
+
+ bool stop_reports = false;
+ const HeapFilter heap_filter(heap_filter_int);
+ art::ObjPtr<art::mirror::Class> filter_klass = soa.Decode<art::mirror::Class>(klass);
+ auto visitor = [&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // Early return, as we can't really stop visiting.
+ if (stop_reports) {
+ return;
+ }
+
+ art::ScopedAssertNoThreadSuspension no_suspension("IterateThroughHeapCallback");
+
+ jlong tag = 0;
+ tag_table->GetTag(obj, &tag);
+
+ jlong class_tag = 0;
+ art::ObjPtr<art::mirror::Class> klass = obj->GetClass();
+ tag_table->GetTag(klass.Ptr(), &class_tag);
+ // For simplicity, even if we find a tag = 0, assume 0 = not tagged.
+
+ if (!heap_filter.ShouldReportByHeapFilter(tag, class_tag)) {
+ return;
+ }
+
+ if (filter_klass != nullptr) {
+ if (filter_klass != klass) {
+ return;
+ }
+ }
+
+ jlong size = obj->SizeOf();
+
+ jint length = -1;
+ if (obj->IsArrayInstance()) {
+ length = obj->AsArray()->GetLength();
+ }
+
+ jlong saved_tag = tag;
+ jint ret = fn(obj, callbacks, class_tag, size, &tag, length, const_cast<void*>(user_data));
+
+ if (tag != saved_tag) {
+ tag_table->Set(obj, tag);
+ }
+
+ stop_reports = (ret & JVMTI_VISIT_ABORT) != 0;
+
+ if (!stop_reports) {
+ jint string_ret = ReportString(obj, env, tag_table, callbacks, user_data);
+ stop_reports = (string_ret & JVMTI_VISIT_ABORT) != 0;
+ }
+
+ if (!stop_reports) {
+ jint array_ret = ReportPrimitiveArray(obj, env, tag_table, callbacks, user_data);
+ stop_reports = (array_ret & JVMTI_VISIT_ABORT) != 0;
+ }
+
+ if (!stop_reports) {
+ stop_reports = ReportPrimitiveField::Report(obj, tag_table, callbacks, user_data);
+ }
+ };
+ art::Runtime::Current()->GetHeap()->VisitObjects(visitor);
+
+ return ERR(NONE);
+}
+
+jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ auto JvmtiIterateHeap = [](art::mirror::Object* obj ATTRIBUTE_UNUSED,
+ const jvmtiHeapCallbacks* cb_callbacks,
+ jlong class_tag,
+ jlong size,
+ jlong* tag,
+ jint length,
+ void* cb_user_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return cb_callbacks->heap_iteration_callback(class_tag,
+ size,
+ tag,
+ length,
+ cb_user_data);
+ };
+ return DoIterateThroughHeap(JvmtiIterateHeap,
+ env,
+ ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get(),
+ heap_filter,
+ klass,
+ callbacks,
+ user_data);
+}
+
+class FollowReferencesHelper FINAL {
+ public:
+ FollowReferencesHelper(HeapUtil* h,
+ jvmtiEnv* jvmti_env,
+ art::ObjPtr<art::mirror::Object> initial_object,
+ const jvmtiHeapCallbacks* callbacks,
+ art::ObjPtr<art::mirror::Class> class_filter,
+ jint heap_filter,
+ const void* user_data)
+ : env(jvmti_env),
+ tag_table_(h->GetTags()),
+ initial_object_(initial_object),
+ callbacks_(callbacks),
+ class_filter_(class_filter),
+ heap_filter_(heap_filter),
+ user_data_(user_data),
+ start_(0),
+ stop_reports_(false) {
+ }
+
+ void Init()
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ if (initial_object_.IsNull()) {
+ CollectAndReportRootsVisitor carrv(this, tag_table_, &worklist_, &visited_);
+
+ // We need precise info (e.g., vregs).
+ constexpr art::VisitRootFlags kRootFlags = static_cast<art::VisitRootFlags>(
+ art::VisitRootFlags::kVisitRootFlagAllRoots | art::VisitRootFlags::kVisitRootFlagPrecise);
+ art::Runtime::Current()->VisitRoots(&carrv, kRootFlags);
+
+ art::Runtime::Current()->VisitImageRoots(&carrv);
+ stop_reports_ = carrv.IsStopReports();
+
+ if (stop_reports_) {
+ worklist_.clear();
+ }
+ } else {
+ visited_.insert(initial_object_.Ptr());
+ worklist_.push_back(initial_object_.Ptr());
+ }
+ }
+
+ void Work()
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ // Currently implemented as a BFS. To lower overhead, we don't erase elements immediately
+ // from the head of the work list, instead postponing until there's a gap that's "large."
+ //
+ // Alternatively, we can implement a DFS and use the work list as a stack.
+ while (start_ < worklist_.size()) {
+ art::mirror::Object* cur_obj = worklist_[start_];
+ start_++;
+
+ if (start_ >= kMaxStart) {
+ worklist_.erase(worklist_.begin(), worklist_.begin() + start_);
+ start_ = 0;
+ }
+
+ VisitObject(cur_obj);
+
+ if (stop_reports_) {
+ break;
+ }
+ }
+ }
+
+ private:
+ class CollectAndReportRootsVisitor FINAL : public art::RootVisitor {
+ public:
+ CollectAndReportRootsVisitor(FollowReferencesHelper* helper,
+ ObjectTagTable* tag_table,
+ std::vector<art::mirror::Object*>* worklist,
+ std::unordered_set<art::mirror::Object*>* visited)
+ : helper_(helper),
+ tag_table_(tag_table),
+ worklist_(worklist),
+ visited_(visited),
+ stop_reports_(false) {}
+
+ void VisitRoots(art::mirror::Object*** roots, size_t count, const art::RootInfo& info)
+ OVERRIDE
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*helper_->tag_table_->GetAllowDisallowLock()) {
+ for (size_t i = 0; i != count; ++i) {
+ AddRoot(*roots[i], info);
+ }
+ }
+
+ void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots,
+ size_t count,
+ const art::RootInfo& info)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*helper_->tag_table_->GetAllowDisallowLock()) {
+ for (size_t i = 0; i != count; ++i) {
+ AddRoot(roots[i]->AsMirrorPtr(), info);
+ }
+ }
+
+ bool IsStopReports() {
+ return stop_reports_;
+ }
+
+ private:
+ void AddRoot(art::mirror::Object* root_obj, const art::RootInfo& info)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ if (stop_reports_) {
+ return;
+ }
+ bool add_to_worklist = ReportRoot(root_obj, info);
+ // We use visited_ to mark roots already so we do not need another set.
+ if (visited_->find(root_obj) == visited_->end()) {
+ if (add_to_worklist) {
+ visited_->insert(root_obj);
+ worklist_->push_back(root_obj);
+ }
+ }
+ }
+
+ // Remove NO_THREAD_SAFETY_ANALYSIS once ASSERT_CAPABILITY works correctly.
+ art::Thread* FindThread(const art::RootInfo& info) NO_THREAD_SAFETY_ANALYSIS {
+ art::Locks::thread_list_lock_->AssertExclusiveHeld(art::Thread::Current());
+ return art::Runtime::Current()->GetThreadList()->FindThreadByThreadId(info.GetThreadId());
+ }
+
+ jvmtiHeapReferenceKind GetReferenceKind(const art::RootInfo& info,
+ jvmtiHeapReferenceInfo* ref_info)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ // TODO: Fill in ref_info.
+ memset(ref_info, 0, sizeof(jvmtiHeapReferenceInfo));
+
+ switch (info.GetType()) {
+ case art::RootType::kRootJNIGlobal:
+ return JVMTI_HEAP_REFERENCE_JNI_GLOBAL;
+
+ case art::RootType::kRootJNILocal:
+ {
+ uint32_t thread_id = info.GetThreadId();
+ ref_info->jni_local.thread_id = thread_id;
+
+ art::Thread* thread = FindThread(info);
+ if (thread != nullptr) {
+ art::mirror::Object* thread_obj;
+ if (thread->IsStillStarting()) {
+ thread_obj = nullptr;
+ } else {
+ thread_obj = thread->GetPeerFromOtherThread();
+ }
+ if (thread_obj != nullptr) {
+ ref_info->jni_local.thread_tag = tag_table_->GetTagOrZero(thread_obj);
+ }
+ }
+
+ // TODO: We don't have this info.
+ if (thread != nullptr) {
+ ref_info->jni_local.depth = 0;
+ art::ArtMethod* method = thread->GetCurrentMethod(nullptr, false /* abort_on_error */);
+ if (method != nullptr) {
+ ref_info->jni_local.method = art::jni::EncodeArtMethod(method);
+ }
+ }
+
+ return JVMTI_HEAP_REFERENCE_JNI_LOCAL;
+ }
+
+ case art::RootType::kRootJavaFrame:
+ {
+ uint32_t thread_id = info.GetThreadId();
+ ref_info->stack_local.thread_id = thread_id;
+
+ art::Thread* thread = FindThread(info);
+ if (thread != nullptr) {
+ art::mirror::Object* thread_obj;
+ if (thread->IsStillStarting()) {
+ thread_obj = nullptr;
+ } else {
+ thread_obj = thread->GetPeerFromOtherThread();
+ }
+ if (thread_obj != nullptr) {
+ ref_info->stack_local.thread_tag = tag_table_->GetTagOrZero(thread_obj);
+ }
+ }
+
+ auto& java_info = static_cast<const art::JavaFrameRootInfo&>(info);
+ ref_info->stack_local.slot = static_cast<jint>(java_info.GetVReg());
+ const art::StackVisitor* visitor = java_info.GetVisitor();
+ ref_info->stack_local.location =
+ static_cast<jlocation>(visitor->GetDexPc(false /* abort_on_failure */));
+ ref_info->stack_local.depth = static_cast<jint>(visitor->GetFrameDepth());
+ art::ArtMethod* method = visitor->GetMethod();
+ if (method != nullptr) {
+ ref_info->stack_local.method = art::jni::EncodeArtMethod(method);
+ }
+
+ return JVMTI_HEAP_REFERENCE_STACK_LOCAL;
+ }
+
+ case art::RootType::kRootNativeStack:
+ case art::RootType::kRootThreadBlock:
+ case art::RootType::kRootThreadObject:
+ return JVMTI_HEAP_REFERENCE_THREAD;
+
+ case art::RootType::kRootStickyClass:
+ case art::RootType::kRootInternedString:
+ // Note: this isn't a root in the RI.
+ return JVMTI_HEAP_REFERENCE_SYSTEM_CLASS;
+
+ case art::RootType::kRootMonitorUsed:
+ case art::RootType::kRootJNIMonitor:
+ return JVMTI_HEAP_REFERENCE_MONITOR;
+
+ case art::RootType::kRootFinalizing:
+ case art::RootType::kRootDebugger:
+ case art::RootType::kRootReferenceCleanup:
+ case art::RootType::kRootVMInternal:
+ case art::RootType::kRootUnknown:
+ return JVMTI_HEAP_REFERENCE_OTHER;
+ }
+ LOG(FATAL) << "Unreachable";
+ UNREACHABLE();
+ }
+
+ bool ReportRoot(art::mirror::Object* root_obj, const art::RootInfo& info)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ jvmtiHeapReferenceInfo ref_info;
+ jvmtiHeapReferenceKind kind = GetReferenceKind(info, &ref_info);
+ jint result = helper_->ReportReference(kind, &ref_info, nullptr, root_obj);
+ if ((result & JVMTI_VISIT_ABORT) != 0) {
+ stop_reports_ = true;
+ }
+ return (result & JVMTI_VISIT_OBJECTS) != 0;
+ }
+
+ private:
+ FollowReferencesHelper* helper_;
+ ObjectTagTable* tag_table_;
+ std::vector<art::mirror::Object*>* worklist_;
+ std::unordered_set<art::mirror::Object*>* visited_;
+ bool stop_reports_;
+ };
+
+ void VisitObject(art::mirror::Object* obj)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ if (obj->IsClass()) {
+ VisitClass(obj->AsClass());
+ return;
+ }
+ if (obj->IsArrayInstance()) {
+ VisitArray(obj);
+ return;
+ }
+
+ // All instance fields.
+ auto report_instance_field = [&](art::ObjPtr<art::mirror::Object> src,
+ art::ObjPtr<art::mirror::Class> obj_klass ATTRIBUTE_UNUSED,
+ art::ArtField& field,
+ size_t field_index,
+ void* user_data ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ art::ObjPtr<art::mirror::Object> field_value = field.GetObject(src);
+ if (field_value != nullptr) {
+ jvmtiHeapReferenceInfo reference_info;
+ memset(&reference_info, 0, sizeof(reference_info));
+
+ reference_info.field.index = field_index;
+
+ jvmtiHeapReferenceKind kind =
+ field.GetOffset().Int32Value() == art::mirror::Object::ClassOffset().Int32Value()
+ ? JVMTI_HEAP_REFERENCE_CLASS
+ : JVMTI_HEAP_REFERENCE_FIELD;
+ const jvmtiHeapReferenceInfo* reference_info_ptr =
+ kind == JVMTI_HEAP_REFERENCE_CLASS ? nullptr : &reference_info;
+
+ return !ReportReferenceMaybeEnqueue(kind, reference_info_ptr, src.Ptr(), field_value.Ptr());
+ }
+ return false;
+ };
+ stop_reports_ = FieldVisitor<void, true>::ReportFields(obj,
+ nullptr,
+ VisitorFalse<void>,
+ VisitorFalse<void>,
+ VisitorFalse<void>,
+ report_instance_field);
+ if (stop_reports_) {
+ return;
+ }
+
+ jint string_ret = ReportString(obj, env, tag_table_, callbacks_, user_data_);
+ stop_reports_ = (string_ret & JVMTI_VISIT_ABORT) != 0;
+ if (stop_reports_) {
+ return;
+ }
+
+ stop_reports_ = ReportPrimitiveField::Report(obj, tag_table_, callbacks_, user_data_);
+ }
+
+ void VisitArray(art::mirror::Object* array)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_CLASS,
+ nullptr,
+ array,
+ array->GetClass());
+ if (stop_reports_) {
+ return;
+ }
+
+ if (array->IsObjectArray()) {
+ art::mirror::ObjectArray<art::mirror::Object>* obj_array =
+ array->AsObjectArray<art::mirror::Object>();
+ int32_t length = obj_array->GetLength();
+ for (int32_t i = 0; i != length; ++i) {
+ art::mirror::Object* elem = obj_array->GetWithoutChecks(i);
+ if (elem != nullptr) {
+ jvmtiHeapReferenceInfo reference_info;
+ reference_info.array.index = i;
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT,
+ &reference_info,
+ array,
+ elem);
+ if (stop_reports_) {
+ break;
+ }
+ }
+ }
+ } else {
+ if (!stop_reports_) {
+ jint array_ret = ReportPrimitiveArray(array, env, tag_table_, callbacks_, user_data_);
+ stop_reports_ = (array_ret & JVMTI_VISIT_ABORT) != 0;
+ }
+ }
+ }
+
+ void VisitClass(art::mirror::Class* klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ // TODO: Are erroneous classes reported? Are non-prepared ones? For now, just use resolved ones.
+ if (!klass->IsResolved()) {
+ return;
+ }
+
+ // Superclass.
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_SUPERCLASS,
+ nullptr,
+ klass,
+ klass->GetSuperClass());
+ if (stop_reports_) {
+ return;
+ }
+
+ // Directly implemented or extended interfaces.
+ art::Thread* self = art::Thread::Current();
+ art::StackHandleScope<1> hs(self);
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle<art::mirror::Class>(klass));
+ for (size_t i = 0; i < h_klass->NumDirectInterfaces(); ++i) {
+ art::ObjPtr<art::mirror::Class> inf_klass =
+ art::mirror::Class::ResolveDirectInterface(self, h_klass, i);
+ if (inf_klass == nullptr) {
+ // TODO: With a resolved class this should not happen...
+ self->ClearException();
+ break;
+ }
+
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_INTERFACE,
+ nullptr,
+ klass,
+ inf_klass.Ptr());
+ if (stop_reports_) {
+ return;
+ }
+ }
+
+ // Classloader.
+ // TODO: What about the boot classpath loader? We'll skip for now, but do we have to find the
+ // fake BootClassLoader?
+ if (klass->GetClassLoader() != nullptr) {
+ stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_CLASS_LOADER,
+ nullptr,
+ klass,
+ klass->GetClassLoader());
+ if (stop_reports_) {
+ return;
+ }
+ }
+ DCHECK_EQ(h_klass.Get(), klass);
+
+ // Declared static fields.
+ auto report_static_field = [&](art::ObjPtr<art::mirror::Object> obj ATTRIBUTE_UNUSED,
+ art::ObjPtr<art::mirror::Class> obj_klass,
+ art::ArtField& field,
+ size_t field_index,
+ void* user_data ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ art::ObjPtr<art::mirror::Object> field_value = field.GetObject(obj_klass);
+ if (field_value != nullptr) {
+ jvmtiHeapReferenceInfo reference_info;
+ memset(&reference_info, 0, sizeof(reference_info));
+
+ reference_info.field.index = static_cast<jint>(field_index);
+
+ return !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_STATIC_FIELD,
+ &reference_info,
+ obj_klass.Ptr(),
+ field_value.Ptr());
+ }
+ return false;
+ };
+ stop_reports_ = FieldVisitor<void, false>::ReportFields(klass,
+ nullptr,
+ VisitorFalse<void>,
+ report_static_field,
+ VisitorFalse<void>,
+ VisitorFalse<void>);
+ if (stop_reports_) {
+ return;
+ }
+
+ stop_reports_ = ReportPrimitiveField::Report(klass, tag_table_, callbacks_, user_data_);
+ }
+
+ void MaybeEnqueue(art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (visited_.find(obj) == visited_.end()) {
+ worklist_.push_back(obj);
+ visited_.insert(obj);
+ }
+ }
+
+ bool ReportReferenceMaybeEnqueue(jvmtiHeapReferenceKind kind,
+ const jvmtiHeapReferenceInfo* reference_info,
+ art::mirror::Object* referree,
+ art::mirror::Object* referrer)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ jint result = ReportReference(kind, reference_info, referree, referrer);
+ if ((result & JVMTI_VISIT_ABORT) == 0) {
+ if ((result & JVMTI_VISIT_OBJECTS) != 0) {
+ MaybeEnqueue(referrer);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ jint ReportReference(jvmtiHeapReferenceKind kind,
+ const jvmtiHeapReferenceInfo* reference_info,
+ art::mirror::Object* referrer,
+ art::mirror::Object* referree)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+ if (referree == nullptr || stop_reports_) {
+ return 0;
+ }
+
+ if (UNLIKELY(class_filter_ != nullptr) && class_filter_ != referree->GetClass()) {
+ return JVMTI_VISIT_OBJECTS;
+ }
+
+ const jlong class_tag = tag_table_->GetTagOrZero(referree->GetClass());
+ jlong tag = tag_table_->GetTagOrZero(referree);
+
+ if (!heap_filter_.ShouldReportByHeapFilter(tag, class_tag)) {
+ return JVMTI_VISIT_OBJECTS;
+ }
+
+ const jlong referrer_class_tag =
+ referrer == nullptr ? 0 : tag_table_->GetTagOrZero(referrer->GetClass());
+ const jlong size = static_cast<jlong>(referree->SizeOf());
+ jlong saved_tag = tag;
+ jlong referrer_tag = 0;
+ jlong saved_referrer_tag = 0;
+ jlong* referrer_tag_ptr;
+ if (referrer == nullptr) {
+ referrer_tag_ptr = nullptr;
+ } else {
+ if (referrer == referree) {
+ referrer_tag_ptr = &tag;
+ } else {
+ referrer_tag = saved_referrer_tag = tag_table_->GetTagOrZero(referrer);
+ referrer_tag_ptr = &referrer_tag;
+ }
+ }
+
+ jint length = -1;
+ if (referree->IsArrayInstance()) {
+ length = referree->AsArray()->GetLength();
+ }
+
+ jint result = callbacks_->heap_reference_callback(kind,
+ reference_info,
+ class_tag,
+ referrer_class_tag,
+ size,
+ &tag,
+ referrer_tag_ptr,
+ length,
+ const_cast<void*>(user_data_));
+
+ if (tag != saved_tag) {
+ tag_table_->Set(referree, tag);
+ }
+ if (referrer_tag != saved_referrer_tag) {
+ tag_table_->Set(referrer, referrer_tag);
+ }
+
+ return result;
+ }
+
+ jvmtiEnv* env;
+ ObjectTagTable* tag_table_;
+ art::ObjPtr<art::mirror::Object> initial_object_;
+ const jvmtiHeapCallbacks* callbacks_;
+ art::ObjPtr<art::mirror::Class> class_filter_;
+ const HeapFilter heap_filter_;
+ const void* user_data_;
+
+ std::vector<art::mirror::Object*> worklist_;
+ size_t start_;
+ static constexpr size_t kMaxStart = 1000000U;
+
+ std::unordered_set<art::mirror::Object*> visited_;
+
+ bool stop_reports_;
+
+ friend class CollectAndReportRootsVisitor;
+};
+
+jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ jobject initial_object,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ if (callbacks == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Thread* self = art::Thread::Current();
+
+ art::gc::Heap* heap = art::Runtime::Current()->GetHeap();
+ if (heap->IsGcConcurrentAndMoving()) {
+ // Need to take a heap dump while GC isn't running. See the
+ // comment in Heap::VisitObjects().
+ heap->IncrementDisableMovingGC(self);
+ }
+ {
+ art::ScopedObjectAccess soa(self); // Now we know we have the shared lock.
+ art::ScopedThreadSuspension sts(self, art::kWaitingForVisitObjects);
+ art::ScopedSuspendAll ssa("FollowReferences");
+
+ art::ObjPtr<art::mirror::Class> class_filter = klass == nullptr
+ ? nullptr
+ : art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(klass));
+ FollowReferencesHelper frh(this,
+ env,
+ self->DecodeJObject(initial_object),
+ callbacks,
+ class_filter,
+ heap_filter,
+ user_data);
+ frh.Init();
+ frh.Work();
+ }
+ if (heap->IsGcConcurrentAndMoving()) {
+ heap->DecrementDisableMovingGC(self);
+ }
+
+ return ERR(NONE);
+}
+
+jvmtiError HeapUtil::GetLoadedClasses(jvmtiEnv* env,
+ jint* class_count_ptr,
+ jclass** classes_ptr) {
+ if (class_count_ptr == nullptr || classes_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ class ReportClassVisitor : public art::ClassVisitor {
+ public:
+ explicit ReportClassVisitor(art::Thread* self) : self_(self) {}
+
+ bool operator()(art::ObjPtr<art::mirror::Class> klass)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (klass->IsLoaded() || klass->IsErroneous()) {
+ classes_.push_back(self_->GetJniEnv()->AddLocalReference<jclass>(klass));
+ }
+ return true;
+ }
+
+ art::Thread* self_;
+ std::vector<jclass> classes_;
+ };
+
+ art::Thread* self = art::Thread::Current();
+ ReportClassVisitor rcv(self);
+ {
+ art::ScopedObjectAccess soa(self);
+ art::Runtime::Current()->GetClassLinker()->VisitClasses(&rcv);
+ }
+
+ size_t size = rcv.classes_.size();
+ jclass* classes = nullptr;
+ jvmtiError alloc_ret = env->Allocate(static_cast<jlong>(size * sizeof(jclass)),
+ reinterpret_cast<unsigned char**>(&classes));
+ if (alloc_ret != ERR(NONE)) {
+ return alloc_ret;
+ }
+
+ for (size_t i = 0; i < size; ++i) {
+ classes[i] = rcv.classes_[i];
+ }
+ *classes_ptr = classes;
+ *class_count_ptr = static_cast<jint>(size);
+
+ return ERR(NONE);
+}
+
+jvmtiError HeapUtil::ForceGarbageCollection(jvmtiEnv* env ATTRIBUTE_UNUSED) {
+ art::Runtime::Current()->GetHeap()->CollectGarbage(false);
+
+ return ERR(NONE);
+}
+
+static constexpr jint kHeapIdDefault = 0;
+static constexpr jint kHeapIdImage = 1;
+static constexpr jint kHeapIdZygote = 2;
+static constexpr jint kHeapIdApp = 3;
+
+static jint GetHeapId(art::ObjPtr<art::mirror::Object> obj)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (obj == nullptr) {
+ return -1;
+ }
+
+ art::gc::Heap* const heap = art::Runtime::Current()->GetHeap();
+ const art::gc::space::ContinuousSpace* const space =
+ heap->FindContinuousSpaceFromObject(obj, true);
+ jint heap_type = kHeapIdApp;
+ if (space != nullptr) {
+ if (space->IsZygoteSpace()) {
+ heap_type = kHeapIdZygote;
+ } else if (space->IsImageSpace() && heap->ObjectIsInBootImageSpace(obj)) {
+ // Only count objects in the boot image as HPROF_HEAP_IMAGE, this leaves app image objects
+ // as HPROF_HEAP_APP. b/35762934
+ heap_type = kHeapIdImage;
+ }
+ } else {
+ const auto* los = heap->GetLargeObjectsSpace();
+ if (los->Contains(obj.Ptr()) && los->IsZygoteLargeObject(art::Thread::Current(), obj.Ptr())) {
+ heap_type = kHeapIdZygote;
+ }
+ }
+ return heap_type;
+};
+
+jvmtiError HeapExtensions::GetObjectHeapId(jvmtiEnv* env, jlong tag, jint* heap_id, ...) {
+ if (heap_id == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Thread* self = art::Thread::Current();
+
+ auto work = [&]() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ ObjectTagTable* tag_table = ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get();
+ art::ObjPtr<art::mirror::Object> obj = tag_table->Find(tag);
+ jint heap_type = GetHeapId(obj);
+ if (heap_type == -1) {
+ return ERR(NOT_FOUND);
+ }
+ *heap_id = heap_type;
+ return ERR(NONE);
+ };
+
+ if (!art::Locks::mutator_lock_->IsSharedHeld(self)) {
+ if (!self->IsThreadSuspensionAllowable()) {
+ return ERR(INTERNAL);
+ }
+ art::ScopedObjectAccess soa(self);
+ return work();
+ } else {
+ // We cannot use SOA in this case. We might be holding the lock, but may not be in the
+ // runnable state (e.g., during GC).
+ art::Locks::mutator_lock_->AssertSharedHeld(self);
+ // TODO: Investigate why ASSERT_SHARED_CAPABILITY doesn't work.
+ auto annotalysis_workaround = [&]() NO_THREAD_SAFETY_ANALYSIS {
+ return work();
+ };
+ return annotalysis_workaround();
+ }
+}
+
+static jvmtiError CopyStringAndReturn(jvmtiEnv* env, const char* in, char** out) {
+ jvmtiError error;
+ JvmtiUniquePtr<char[]> param_name = CopyString(env, in, &error);
+ if (param_name == nullptr) {
+ return error;
+ }
+ *out = param_name.release();
+ return ERR(NONE);
+}
+
+static constexpr const char* kHeapIdDefaultName = "default";
+static constexpr const char* kHeapIdImageName = "image";
+static constexpr const char* kHeapIdZygoteName = "zygote";
+static constexpr const char* kHeapIdAppName = "app";
+
+jvmtiError HeapExtensions::GetHeapName(jvmtiEnv* env, jint heap_id, char** heap_name, ...) {
+ switch (heap_id) {
+ case kHeapIdDefault:
+ return CopyStringAndReturn(env, kHeapIdDefaultName, heap_name);
+ case kHeapIdImage:
+ return CopyStringAndReturn(env, kHeapIdImageName, heap_name);
+ case kHeapIdZygote:
+ return CopyStringAndReturn(env, kHeapIdZygoteName, heap_name);
+ case kHeapIdApp:
+ return CopyStringAndReturn(env, kHeapIdAppName, heap_name);
+
+ default:
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+}
+
+jvmtiError HeapExtensions::IterateThroughHeapExt(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data) {
+ if (ArtJvmTiEnv::AsArtJvmTiEnv(env)->capabilities.can_tag_objects != 1) { \
+ return ERR(MUST_POSSESS_CAPABILITY); \
+ }
+
+ // ART extension API: Also pass the heap id.
+ auto ArtIterateHeap = [](art::mirror::Object* obj,
+ const jvmtiHeapCallbacks* cb_callbacks,
+ jlong class_tag,
+ jlong size,
+ jlong* tag,
+ jint length,
+ void* cb_user_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ jint heap_id = GetHeapId(obj);
+ using ArtExtensionAPI = jint (*)(jlong, jlong, jlong*, jint length, void*, jint);
+ return reinterpret_cast<ArtExtensionAPI>(cb_callbacks->heap_iteration_callback)(
+ class_tag, size, tag, length, cb_user_data, heap_id);
+ };
+ return DoIterateThroughHeap(ArtIterateHeap,
+ env,
+ ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get(),
+ heap_filter,
+ klass,
+ callbacks,
+ user_data);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h
new file mode 100644
index 0000000..62761b5
--- /dev/null
+++ b/openjdkjvmti/ti_heap.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_HEAP_H_
+#define ART_OPENJDKJVMTI_TI_HEAP_H_
+
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class ObjectTagTable;
+
+class HeapUtil {
+ public:
+ explicit HeapUtil(ObjectTagTable* tags) : tags_(tags) {
+ }
+
+ jvmtiError GetLoadedClasses(jvmtiEnv* env, jint* class_count_ptr, jclass** classes_ptr);
+
+ jvmtiError IterateThroughHeap(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data);
+
+ jvmtiError FollowReferences(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ jobject initial_object,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data);
+
+ static jvmtiError ForceGarbageCollection(jvmtiEnv* env);
+
+ ObjectTagTable* GetTags() {
+ return tags_;
+ }
+
+ static void Register();
+ static void Unregister();
+
+ private:
+ ObjectTagTable* tags_;
+};
+
+class HeapExtensions {
+ public:
+ static jvmtiError JNICALL GetObjectHeapId(jvmtiEnv* env, jlong tag, jint* heap_id, ...);
+ static jvmtiError JNICALL GetHeapName(jvmtiEnv* env, jint heap_id, char** heap_name, ...);
+
+ static jvmtiError JNICALL IterateThroughHeapExt(jvmtiEnv* env,
+ jint heap_filter,
+ jclass klass,
+ const jvmtiHeapCallbacks* callbacks,
+ const void* user_data);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_HEAP_H_
diff --git a/openjdkjvmti/ti_jni.cc b/openjdkjvmti/ti_jni.cc
new file mode 100644
index 0000000..dd2dda1
--- /dev/null
+++ b/openjdkjvmti/ti_jni.cc
@@ -0,0 +1,91 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_jni.h"
+
+#include "jni.h"
+
+#include "art_jvmti.h"
+#include "base/mutex.h"
+#include "java_vm_ext.h"
+#include "jni_env_ext.h"
+#include "runtime.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError JNIUtil::SetJNIFunctionTable(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ const jniNativeInterface* function_table) {
+ // While we supporting setting null (which will reset the table), the spec says no.
+ if (function_table == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::JNIEnvExt::SetTableOverride(function_table);
+ return ERR(NONE);
+}
+
+jvmtiError JNIUtil::GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table) {
+ if (function_table == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ // We use the generic JNIEnvExt::GetFunctionTable instead of querying a specific JNIEnv, as
+ // this has to work in the start phase.
+
+ // Figure out which table is current. Conservatively assume check-jni is off.
+ bool check_jni = false;
+ art::Runtime* runtime = art::Runtime::Current();
+ if (runtime != nullptr && runtime->GetJavaVM() != nullptr) {
+ check_jni = runtime->GetJavaVM()->IsCheckJniEnabled();
+ }
+
+ // Get that table.
+ const JNINativeInterface* current_table;
+ {
+ art::MutexLock mu(art::Thread::Current(), *art::Locks::jni_function_table_lock_);
+ current_table = art::JNIEnvExt::GetFunctionTable(check_jni);
+ }
+
+ // Allocate memory and copy the table.
+ unsigned char* data;
+ jvmtiError data_result = env->Allocate(sizeof(JNINativeInterface), &data);
+ if (data_result != ERR(NONE)) {
+ return data_result;
+ }
+ memcpy(data, current_table, sizeof(JNINativeInterface));
+
+ *function_table = reinterpret_cast<JNINativeInterface*>(data);
+
+ return ERR(NONE);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_jni.h b/openjdkjvmti/ti_jni.h
new file mode 100644
index 0000000..590fd54
--- /dev/null
+++ b/openjdkjvmti/ti_jni.h
@@ -0,0 +1,58 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_JNI_H_
+#define ART_OPENJDKJVMTI_TI_JNI_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+// Note: Currently, JNI function table changes are sensitive to the order of operations wrt/
+// CheckJNI. If an agent sets the function table, and a program than late-enables CheckJNI,
+// CheckJNI will not be working (as the agent will forward to the non-CheckJNI table).
+//
+// This behavior results from our usage of the function table to avoid a check of the
+// CheckJNI flag. A future implementation may install on loading of this plugin an
+// intermediate function table that explicitly checks the flag, so that switching CheckJNI
+// is transparently handled.
+
+class JNIUtil {
+ public:
+ static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table);
+
+ static jvmtiError GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_JNI_H_
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
new file mode 100644
index 0000000..8f72714
--- /dev/null
+++ b/openjdkjvmti/ti_method.cc
@@ -0,0 +1,1071 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_method.h"
+
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/enums.h"
+#include "base/mutex-inl.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_loader.h"
+#include "mirror/object-inl.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_thread.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+struct TiMethodCallback : public art::MethodCallback {
+ void RegisterNativeMethod(art::ArtMethod* method,
+ const void* cur_method,
+ /*out*/void** new_method)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kNativeMethodBind)) {
+ art::Thread* thread = art::Thread::Current();
+ art::JNIEnvExt* jnienv = thread->GetJniEnv();
+ ScopedLocalRef<jthread> thread_jni(
+ jnienv, PhaseUtil::IsLivePhase() ? jnienv->AddLocalReference<jthread>(thread->GetPeer())
+ : nullptr);
+ art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(
+ thread,
+ static_cast<JNIEnv*>(jnienv),
+ thread_jni.get(),
+ art::jni::EncodeArtMethod(method),
+ const_cast<void*>(cur_method),
+ new_method);
+ }
+ }
+
+ EventHandler* event_handler = nullptr;
+};
+
+TiMethodCallback gMethodCallback;
+
+void MethodUtil::Register(EventHandler* handler) {
+ gMethodCallback.event_handler = handler;
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add method callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddMethodCallback(&gMethodCallback);
+}
+
+void MethodUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove method callback");
+ art::Runtime* runtime = art::Runtime::Current();
+ runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback);
+}
+
+jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env,
+ jmethodID method,
+ jint* size_ptr,
+ unsigned char** bytecode_ptr) {
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+ if (art_method->IsNative()) {
+ return ERR(NATIVE_METHOD);
+ }
+
+ if (size_ptr == nullptr || bytecode_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
+ if (code_item == nullptr) {
+ *size_ptr = 0;
+ *bytecode_ptr = nullptr;
+ return OK;
+ }
+ // 2 bytes per instruction for dex code.
+ *size_ptr = code_item->insns_size_in_code_units_ * 2;
+ jvmtiError err = env->Allocate(*size_ptr, bytecode_ptr);
+ if (err != OK) {
+ return err;
+ }
+ memcpy(*bytecode_ptr, code_item->insns_, *size_ptr);
+ return OK;
+}
+
+jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jint* size_ptr) {
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+ if (art_method->IsNative()) {
+ return ERR(NATIVE_METHOD);
+ }
+
+ if (size_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+ // Use the shorty.
+ art::ArtMethod* base_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+ size_t arg_count = art::ArtMethod::NumArgRegisters(base_method->GetShorty());
+ if (!base_method->IsStatic()) {
+ arg_count++;
+ }
+ *size_ptr = static_cast<jint>(arg_count);
+ return ERR(NONE);
+ }
+
+ DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+ *size_ptr = art_method->GetCodeItem()->ins_size_;
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetLocalVariableTable(jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLocalVariableEntry** table_ptr) {
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+ if (art_method->IsNative()) {
+ return ERR(NATIVE_METHOD);
+ }
+
+ if (entry_count_ptr == nullptr || table_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ const art::DexFile* dex_file = art_method->GetDexFile();
+ const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
+ // TODO code_item == nullptr means that the method is abstract (or native, but we check that
+ // earlier). We should check what is returned by the RI in this situation since it's not clear
+ // what the appropriate return value is from the spec.
+ if (dex_file == nullptr || code_item == nullptr) {
+ return ERR(ABSENT_INFORMATION);
+ }
+
+ struct LocalVariableContext {
+ explicit LocalVariableContext(jvmtiEnv* jenv) : env_(jenv), variables_(), err_(OK) {}
+
+ static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) {
+ reinterpret_cast<LocalVariableContext*>(raw_ctx)->Insert(entry);
+ }
+
+ void Insert(const art::DexFile::LocalInfo& entry) {
+ if (err_ != OK) {
+ return;
+ }
+ JvmtiUniquePtr<char[]> name_str = CopyString(env_, entry.name_, &err_);
+ if (err_ != OK) {
+ return;
+ }
+ JvmtiUniquePtr<char[]> sig_str = CopyString(env_, entry.descriptor_, &err_);
+ if (err_ != OK) {
+ return;
+ }
+ JvmtiUniquePtr<char[]> generic_sig_str = CopyString(env_, entry.signature_, &err_);
+ if (err_ != OK) {
+ return;
+ }
+ variables_.push_back({
+ .start_location = static_cast<jlocation>(entry.start_address_),
+ .length = static_cast<jint>(entry.end_address_ - entry.start_address_),
+ .name = name_str.release(),
+ .signature = sig_str.release(),
+ .generic_signature = generic_sig_str.release(),
+ .slot = entry.reg_,
+ });
+ }
+
+ jvmtiError Release(jint* out_entry_count_ptr, jvmtiLocalVariableEntry** out_table_ptr) {
+ jlong table_size = sizeof(jvmtiLocalVariableEntry) * variables_.size();
+ if (err_ != OK ||
+ (err_ = env_->Allocate(table_size,
+ reinterpret_cast<unsigned char**>(out_table_ptr))) != OK) {
+ Cleanup();
+ return err_;
+ } else {
+ *out_entry_count_ptr = variables_.size();
+ memcpy(*out_table_ptr, variables_.data(), table_size);
+ return OK;
+ }
+ }
+
+ void Cleanup() {
+ for (jvmtiLocalVariableEntry& e : variables_) {
+ env_->Deallocate(reinterpret_cast<unsigned char*>(e.name));
+ env_->Deallocate(reinterpret_cast<unsigned char*>(e.signature));
+ env_->Deallocate(reinterpret_cast<unsigned char*>(e.generic_signature));
+ }
+ }
+
+ jvmtiEnv* env_;
+ std::vector<jvmtiLocalVariableEntry> variables_;
+ jvmtiError err_;
+ };
+
+ LocalVariableContext context(env);
+ if (!dex_file->DecodeDebugLocalInfo(code_item,
+ art_method->IsStatic(),
+ art_method->GetDexMethodIndex(),
+ LocalVariableContext::Callback,
+ &context)) {
+ // Something went wrong with decoding the debug information. It might as well not be there.
+ return ERR(ABSENT_INFORMATION);
+ } else {
+ return context.Release(entry_count_ptr, table_ptr);
+ }
+}
+
+jvmtiError MethodUtil::GetMaxLocals(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jint* max_ptr) {
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+ if (art_method->IsNative()) {
+ return ERR(NATIVE_METHOD);
+ }
+
+ if (max_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+ // This isn't specified as an error case, so return 0.
+ *max_ptr = 0;
+ return ERR(NONE);
+ }
+
+ DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+ *max_ptr = art_method->GetCodeItem()->registers_size_;
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodName(jvmtiEnv* env,
+ jmethodID method,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr) {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ art_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+
+ JvmtiUniquePtr<char[]> name_copy;
+ if (name_ptr != nullptr) {
+ const char* method_name = art_method->GetName();
+ if (method_name == nullptr) {
+ method_name = "<error>";
+ }
+ jvmtiError ret;
+ name_copy = CopyString(env, method_name, &ret);
+ if (name_copy == nullptr) {
+ return ret;
+ }
+ *name_ptr = name_copy.get();
+ }
+
+ JvmtiUniquePtr<char[]> signature_copy;
+ if (signature_ptr != nullptr) {
+ const art::Signature sig = art_method->GetSignature();
+ std::string str = sig.ToString();
+ jvmtiError ret;
+ signature_copy = CopyString(env, str.c_str(), &ret);
+ if (signature_copy == nullptr) {
+ return ret;
+ }
+ *signature_ptr = signature_copy.get();
+ }
+
+ if (generic_ptr != nullptr) {
+ *generic_ptr = nullptr;
+ if (!art_method->GetDeclaringClass()->IsProxyClass()) {
+ art::mirror::ObjectArray<art::mirror::String>* str_array =
+ art::annotations::GetSignatureAnnotationForMethod(art_method);
+ if (str_array != nullptr) {
+ std::ostringstream oss;
+ for (int32_t i = 0; i != str_array->GetLength(); ++i) {
+ oss << str_array->Get(i)->ToModifiedUtf8();
+ }
+ std::string output_string = oss.str();
+ jvmtiError ret;
+ JvmtiUniquePtr<char[]> generic_copy = CopyString(env, output_string.c_str(), &ret);
+ if (generic_copy == nullptr) {
+ return ret;
+ }
+ *generic_ptr = generic_copy.release();
+ } else if (soa.Self()->IsExceptionPending()) {
+ // TODO: Should we report an error here?
+ soa.Self()->ClearException();
+ }
+ }
+ }
+
+ // Everything is fine, release the buffers.
+ name_copy.release();
+ signature_copy.release();
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jclass* declaring_class_ptr) {
+ if (declaring_class_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ // Note: No GetInterfaceMethodIfProxy, we want to actual class.
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::mirror::Class* klass = art_method->GetDeclaringClass();
+ *declaring_class_ptr = soa.AddLocalReference<jclass>(klass);
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jlocation* start_location_ptr,
+ jlocation* end_location_ptr) {
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+ if (art_method->IsNative()) {
+ return ERR(NATIVE_METHOD);
+ }
+
+ if (start_location_ptr == nullptr || end_location_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+ // This isn't specified as an error case, so return -1/-1 as the RI does.
+ *start_location_ptr = -1;
+ *end_location_ptr = -1;
+ return ERR(NONE);
+ }
+
+ DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+ *start_location_ptr = 0;
+ *end_location_ptr = art_method->GetCodeItem()->insns_size_in_code_units_ - 1;
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jint* modifiers_ptr) {
+ if (modifiers_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ uint32_t modifiers = art_method->GetAccessFlags();
+
+ // Note: Keep this code in sync with Executable.fixMethodFlags.
+ if ((modifiers & art::kAccAbstract) != 0) {
+ modifiers &= ~art::kAccNative;
+ }
+ modifiers &= ~art::kAccSynchronized;
+ if ((modifiers & art::kAccDeclaredSynchronized) != 0) {
+ modifiers |= art::kAccSynchronized;
+ }
+ modifiers &= art::kAccJavaFlagsMask;
+
+ *modifiers_ptr = modifiers;
+ return ERR(NONE);
+}
+
+using LineNumberContext = std::vector<jvmtiLineNumberEntry>;
+
+static bool CollectLineNumbers(void* void_context, const art::DexFile::PositionInfo& entry) {
+ LineNumberContext* context = reinterpret_cast<LineNumberContext*>(void_context);
+ jvmtiLineNumberEntry jvmti_entry = { static_cast<jlocation>(entry.address_),
+ static_cast<jint>(entry.line_) };
+ context->push_back(jvmti_entry);
+ return false; // Collect all, no early exit.
+}
+
+jvmtiError MethodUtil::GetLineNumberTable(jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLineNumberEntry** table_ptr) {
+ if (method == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ DCHECK(!art_method->IsRuntimeMethod());
+
+ const art::DexFile::CodeItem* code_item;
+ const art::DexFile* dex_file;
+ {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+
+ if (art_method->IsProxyMethod()) {
+ return ERR(ABSENT_INFORMATION);
+ }
+ if (art_method->IsNative()) {
+ return ERR(NATIVE_METHOD);
+ }
+ if (entry_count_ptr == nullptr || table_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ code_item = art_method->GetCodeItem();
+ dex_file = art_method->GetDexFile();
+ DCHECK(code_item != nullptr) << art_method->PrettyMethod() << " " << dex_file->GetLocation();
+ }
+
+ LineNumberContext context;
+ bool success = dex_file->DecodeDebugPositionInfo(code_item, CollectLineNumbers, &context);
+ if (!success) {
+ return ERR(ABSENT_INFORMATION);
+ }
+
+ unsigned char* data;
+ jlong mem_size = context.size() * sizeof(jvmtiLineNumberEntry);
+ jvmtiError alloc_error = env->Allocate(mem_size, &data);
+ if (alloc_error != ERR(NONE)) {
+ return alloc_error;
+ }
+ *table_ptr = reinterpret_cast<jvmtiLineNumberEntry*>(data);
+ memcpy(*table_ptr, context.data(), mem_size);
+ *entry_count_ptr = static_cast<jint>(context.size());
+
+ return ERR(NONE);
+}
+
+template <typename T>
+static jvmtiError IsMethodT(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jmethodID method,
+ T test,
+ jboolean* is_t_ptr) {
+ if (method == nullptr) {
+ return ERR(INVALID_METHODID);
+ }
+ if (is_t_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+ *is_t_ptr = test(art_method) ? JNI_TRUE : JNI_FALSE;
+
+ return ERR(NONE);
+}
+
+jvmtiError MethodUtil::IsMethodNative(jvmtiEnv* env, jmethodID m, jboolean* is_native_ptr) {
+ auto test = [](art::ArtMethod* method) {
+ return method->IsNative();
+ };
+ return IsMethodT(env, m, test, is_native_ptr);
+}
+
+jvmtiError MethodUtil::IsMethodObsolete(jvmtiEnv* env, jmethodID m, jboolean* is_obsolete_ptr) {
+ auto test = [](art::ArtMethod* method) {
+ return method->IsObsolete();
+ };
+ return IsMethodT(env, m, test, is_obsolete_ptr);
+}
+
+jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* is_synthetic_ptr) {
+ auto test = [](art::ArtMethod* method) {
+ return method->IsSynthetic();
+ };
+ return IsMethodT(env, m, test, is_synthetic_ptr);
+}
+
+struct FindFrameAtDepthVisitor : art::StackVisitor {
+ public:
+ FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ found_frame_(false),
+ cnt_(0),
+ depth_(static_cast<size_t>(depth)) { }
+
+ bool FoundFrame() {
+ return found_frame_;
+ }
+
+ bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
+ if (GetMethod()->IsRuntimeMethod()) {
+ return true;
+ }
+ if (cnt_ == depth_) {
+ // We found our frame, exit.
+ found_frame_ = true;
+ return false;
+ } else {
+ cnt_++;
+ return true;
+ }
+ }
+
+ private:
+ bool found_frame_;
+ size_t cnt_;
+ size_t depth_;
+};
+
+class CommonLocalVariableClosure : public art::Closure {
+ public:
+ CommonLocalVariableClosure(art::Thread* caller,
+ jint depth,
+ jint slot)
+ : result_(ERR(INTERNAL)), caller_(caller), depth_(depth), slot_(slot) {}
+
+ void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
+ std::unique_ptr<art::Context> context(art::Context::Create());
+ FindFrameAtDepthVisitor visitor(self, context.get(), depth_);
+ visitor.WalkStack();
+ if (!visitor.FoundFrame()) {
+ // Must have been a bad depth.
+ result_ = ERR(NO_MORE_FRAMES);
+ return;
+ }
+ art::ArtMethod* method = visitor.GetMethod();
+ if (method->IsNative() || !visitor.IsShadowFrame()) {
+ // TODO We really should support get/set for non-shadow frames.
+ result_ = ERR(OPAQUE_FRAME);
+ return;
+ } else if (method->GetCodeItem()->registers_size_ <= slot_) {
+ result_ = ERR(INVALID_SLOT);
+ return;
+ }
+ uint32_t pc = visitor.GetDexPc(/*abort_on_failure*/ false);
+ if (pc == art::DexFile::kDexNoIndex) {
+ // Cannot figure out current PC.
+ result_ = ERR(OPAQUE_FRAME);
+ return;
+ }
+ std::string descriptor;
+ art::Primitive::Type slot_type = art::Primitive::kPrimVoid;
+ jvmtiError err = GetSlotType(method, pc, &descriptor, &slot_type);
+ if (err != OK) {
+ result_ = err;
+ return;
+ }
+
+ err = GetTypeError(method, slot_type, descriptor);
+ if (err != OK) {
+ result_ = err;
+ return;
+ }
+ result_ = Execute(method, visitor);
+ }
+
+ jvmtiError GetResult() const {
+ return result_;
+ }
+
+ protected:
+ virtual jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+ REQUIRES(art::Locks::mutator_lock_) = 0;
+ virtual jvmtiError GetTypeError(art::ArtMethod* method,
+ art::Primitive::Type type,
+ const std::string& descriptor)
+ REQUIRES(art::Locks::mutator_lock_) = 0;
+
+ jvmtiError GetSlotType(art::ArtMethod* method,
+ uint32_t dex_pc,
+ /*out*/std::string* descriptor,
+ /*out*/art::Primitive::Type* type)
+ REQUIRES(art::Locks::mutator_lock_) {
+ const art::DexFile* dex_file = method->GetDexFile();
+ const art::DexFile::CodeItem* code_item = method->GetCodeItem();
+ if (dex_file == nullptr || code_item == nullptr) {
+ return ERR(OPAQUE_FRAME);
+ }
+
+ struct GetLocalVariableInfoContext {
+ explicit GetLocalVariableInfoContext(jint slot,
+ uint32_t pc,
+ std::string* out_descriptor,
+ art::Primitive::Type* out_type)
+ : found_(false), jslot_(slot), pc_(pc), descriptor_(out_descriptor), type_(out_type) {
+ *descriptor_ = "";
+ *type_ = art::Primitive::kPrimVoid;
+ }
+
+ static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) {
+ reinterpret_cast<GetLocalVariableInfoContext*>(raw_ctx)->Handle(entry);
+ }
+
+ void Handle(const art::DexFile::LocalInfo& entry) {
+ if (found_) {
+ return;
+ } else if (entry.start_address_ <= pc_ &&
+ entry.end_address_ > pc_ &&
+ entry.reg_ == jslot_) {
+ found_ = true;
+ *type_ = art::Primitive::GetType(entry.descriptor_[0]);
+ *descriptor_ = entry.descriptor_;
+ }
+ return;
+ }
+
+ bool found_;
+ jint jslot_;
+ uint32_t pc_;
+ std::string* descriptor_;
+ art::Primitive::Type* type_;
+ };
+
+ GetLocalVariableInfoContext context(slot_, dex_pc, descriptor, type);
+ if (!dex_file->DecodeDebugLocalInfo(code_item,
+ method->IsStatic(),
+ method->GetDexMethodIndex(),
+ GetLocalVariableInfoContext::Callback,
+ &context) || !context.found_) {
+ // Something went wrong with decoding the debug information. It might as well not be there.
+ return ERR(INVALID_SLOT);
+ } else {
+ return OK;
+ }
+ }
+
+ jvmtiError result_;
+ art::Thread* caller_;
+ jint depth_;
+ jint slot_;
+};
+
+class GetLocalVariableClosure : public CommonLocalVariableClosure {
+ public:
+ GetLocalVariableClosure(art::Thread* caller,
+ jint depth,
+ jint slot,
+ art::Primitive::Type type,
+ jvalue* val)
+ : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {}
+
+ protected:
+ jvmtiError GetTypeError(art::ArtMethod* method ATTRIBUTE_UNUSED,
+ art::Primitive::Type slot_type,
+ const std::string& descriptor ATTRIBUTE_UNUSED)
+ OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ switch (slot_type) {
+ case art::Primitive::kPrimByte:
+ case art::Primitive::kPrimChar:
+ case art::Primitive::kPrimInt:
+ case art::Primitive::kPrimShort:
+ case art::Primitive::kPrimBoolean:
+ return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH);
+ case art::Primitive::kPrimLong:
+ case art::Primitive::kPrimFloat:
+ case art::Primitive::kPrimDouble:
+ case art::Primitive::kPrimNot:
+ return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+ case art::Primitive::kPrimVoid:
+ LOG(FATAL) << "Unexpected primitive type " << slot_type;
+ UNREACHABLE();
+ }
+ }
+
+ jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+ OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ switch (type_) {
+ case art::Primitive::kPrimNot: {
+ uint32_t ptr_val;
+ if (!visitor.GetVReg(method,
+ static_cast<uint16_t>(slot_),
+ art::kReferenceVReg,
+ &ptr_val)) {
+ return ERR(OPAQUE_FRAME);
+ }
+ art::ObjPtr<art::mirror::Object> obj(reinterpret_cast<art::mirror::Object*>(ptr_val));
+ val_->l = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj);
+ break;
+ }
+ case art::Primitive::kPrimInt:
+ case art::Primitive::kPrimFloat: {
+ if (!visitor.GetVReg(method,
+ static_cast<uint16_t>(slot_),
+ type_ == art::Primitive::kPrimFloat ? art::kFloatVReg : art::kIntVReg,
+ reinterpret_cast<uint32_t*>(&val_->i))) {
+ return ERR(OPAQUE_FRAME);
+ }
+ break;
+ }
+ case art::Primitive::kPrimDouble:
+ case art::Primitive::kPrimLong: {
+ auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg;
+ auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg;
+ if (!visitor.GetVRegPair(method,
+ static_cast<uint16_t>(slot_),
+ lo_type,
+ high_type,
+ reinterpret_cast<uint64_t*>(&val_->j))) {
+ return ERR(OPAQUE_FRAME);
+ }
+ break;
+ }
+ default: {
+ LOG(FATAL) << "unexpected register type " << type_;
+ UNREACHABLE();
+ }
+ }
+ return OK;
+ }
+
+ private:
+ art::Primitive::Type type_;
+ jvalue* val_;
+};
+
+jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread thread,
+ jint depth,
+ jint slot,
+ art::Primitive::Type type,
+ jvalue* val) {
+ if (depth < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+ if (target == nullptr && thread == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+ if (target == nullptr) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ GetLocalVariableClosure c(self, depth, slot, type, val);
+ if (!target->RequestSynchronousCheckpoint(&c)) {
+ return ERR(THREAD_NOT_ALIVE);
+ } else {
+ return c.GetResult();
+ }
+}
+
+class SetLocalVariableClosure : public CommonLocalVariableClosure {
+ public:
+ SetLocalVariableClosure(art::Thread* caller,
+ jint depth,
+ jint slot,
+ art::Primitive::Type type,
+ jvalue val)
+ : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {}
+
+ protected:
+ jvmtiError GetTypeError(art::ArtMethod* method,
+ art::Primitive::Type slot_type,
+ const std::string& descriptor)
+ OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ switch (slot_type) {
+ case art::Primitive::kPrimNot: {
+ if (type_ != art::Primitive::kPrimNot) {
+ return ERR(TYPE_MISMATCH);
+ } else if (val_.l == nullptr) {
+ return OK;
+ } else {
+ art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker();
+ art::ObjPtr<art::mirror::Class> set_class =
+ caller_->DecodeJObject(val_.l)->GetClass();
+ art::ObjPtr<art::mirror::ClassLoader> loader =
+ method->GetDeclaringClass()->GetClassLoader();
+ art::ObjPtr<art::mirror::Class> slot_class =
+ cl->LookupClass(caller_, descriptor.c_str(), loader);
+ DCHECK(!slot_class.IsNull());
+ return slot_class->IsAssignableFrom(set_class) ? OK : ERR(TYPE_MISMATCH);
+ }
+ }
+ case art::Primitive::kPrimByte:
+ case art::Primitive::kPrimChar:
+ case art::Primitive::kPrimInt:
+ case art::Primitive::kPrimShort:
+ case art::Primitive::kPrimBoolean:
+ return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH);
+ case art::Primitive::kPrimLong:
+ case art::Primitive::kPrimFloat:
+ case art::Primitive::kPrimDouble:
+ return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+ case art::Primitive::kPrimVoid:
+ LOG(FATAL) << "Unexpected primitive type " << slot_type;
+ UNREACHABLE();
+ }
+ }
+
+ jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+ OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ switch (type_) {
+ case art::Primitive::kPrimNot: {
+ uint32_t ptr_val;
+ art::ObjPtr<art::mirror::Object> obj(caller_->DecodeJObject(val_.l));
+ ptr_val = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(obj.Ptr()));
+ if (!visitor.SetVReg(method,
+ static_cast<uint16_t>(slot_),
+ ptr_val,
+ art::kReferenceVReg)) {
+ return ERR(OPAQUE_FRAME);
+ }
+ break;
+ }
+ case art::Primitive::kPrimInt:
+ case art::Primitive::kPrimFloat: {
+ if (!visitor.SetVReg(method,
+ static_cast<uint16_t>(slot_),
+ static_cast<uint32_t>(val_.i),
+ type_ == art::Primitive::kPrimFloat ? art::kFloatVReg
+ : art::kIntVReg)) {
+ return ERR(OPAQUE_FRAME);
+ }
+ break;
+ }
+ case art::Primitive::kPrimDouble:
+ case art::Primitive::kPrimLong: {
+ auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg;
+ auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg;
+ if (!visitor.SetVRegPair(method,
+ static_cast<uint16_t>(slot_),
+ static_cast<uint64_t>(val_.j),
+ lo_type,
+ high_type)) {
+ return ERR(OPAQUE_FRAME);
+ }
+ break;
+ }
+ default: {
+ LOG(FATAL) << "unexpected register type " << type_;
+ UNREACHABLE();
+ }
+ }
+ return OK;
+ }
+
+ private:
+ art::Primitive::Type type_;
+ jvalue val_;
+};
+
+jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread thread,
+ jint depth,
+ jint slot,
+ art::Primitive::Type type,
+ jvalue val) {
+ if (depth < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+ if (target == nullptr && thread == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+ if (target == nullptr) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ SetLocalVariableClosure c(self, depth, slot, type, val);
+ if (!target->RequestSynchronousCheckpoint(&c)) {
+ return ERR(THREAD_NOT_ALIVE);
+ } else {
+ return c.GetResult();
+ }
+}
+
+class GetLocalInstanceClosure : public art::Closure {
+ public:
+ GetLocalInstanceClosure(art::Thread* caller, jint depth, jobject* val)
+ : result_(ERR(INTERNAL)),
+ caller_(caller),
+ depth_(depth),
+ val_(val) {}
+
+ void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
+ std::unique_ptr<art::Context> context(art::Context::Create());
+ FindFrameAtDepthVisitor visitor(self, context.get(), depth_);
+ visitor.WalkStack();
+ if (!visitor.FoundFrame()) {
+ // Must have been a bad depth.
+ result_ = ERR(NO_MORE_FRAMES);
+ return;
+ }
+ art::ArtMethod* method = visitor.GetMethod();
+ if (!visitor.IsShadowFrame() && !method->IsNative() && !method->IsProxyMethod()) {
+ // TODO We really should support get/set for non-shadow frames.
+ result_ = ERR(OPAQUE_FRAME);
+ return;
+ }
+ result_ = OK;
+ art::ObjPtr<art::mirror::Object> obj = visitor.GetThisObject();
+ *val_ = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj);
+ }
+
+ jvmtiError GetResult() const {
+ return result_;
+ }
+
+ private:
+ jvmtiError result_;
+ art::Thread* caller_;
+ jint depth_;
+ jobject* val_;
+};
+
+jvmtiError MethodUtil::GetLocalInstance(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread thread,
+ jint depth,
+ jobject* data) {
+ if (depth < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+ if (target == nullptr && thread == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+ if (target == nullptr) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ GetLocalInstanceClosure c(self, depth, data);
+ if (!target->RequestSynchronousCheckpoint(&c)) {
+ return ERR(THREAD_NOT_ALIVE);
+ } else {
+ return c.GetResult();
+ }
+}
+
+#define FOR_JVMTI_JVALUE_TYPES(fn) \
+ fn(jint, art::Primitive::kPrimInt, i) \
+ fn(jlong, art::Primitive::kPrimLong, j) \
+ fn(jfloat, art::Primitive::kPrimFloat, f) \
+ fn(jdouble, art::Primitive::kPrimDouble, d) \
+ fn(jobject, art::Primitive::kPrimNot, l)
+
+namespace impl {
+
+template<typename T> void WriteJvalue(T, jvalue*);
+template<typename T> void ReadJvalue(jvalue, T*);
+template<typename T> art::Primitive::Type GetJNIType();
+
+#define JNI_TYPE_CHAR(type, prim, id) \
+template<> art::Primitive::Type GetJNIType<type>() { \
+ return prim; \
+}
+
+FOR_JVMTI_JVALUE_TYPES(JNI_TYPE_CHAR);
+
+#undef JNI_TYPE_CHAR
+
+#define RW_JVALUE(type, prim, id) \
+ template<> void ReadJvalue<type>(jvalue in, type* out) { \
+ *out = in.id; \
+ } \
+ template<> void WriteJvalue<type>(type in, jvalue* out) { \
+ out->id = in; \
+ }
+
+FOR_JVMTI_JVALUE_TYPES(RW_JVALUE);
+
+#undef RW_JVALUE
+
+} // namespace impl
+
+template<typename T>
+jvmtiError MethodUtil::SetLocalVariable(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ T data) {
+ jvalue v = {.j = 0};
+ art::Primitive::Type type = impl::GetJNIType<T>();
+ impl::WriteJvalue(data, &v);
+ return SetLocalVariableGeneric(env, thread, depth, slot, type, v);
+}
+
+template<typename T>
+jvmtiError MethodUtil::GetLocalVariable(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ T* data) {
+ if (data == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ jvalue v = {.j = 0};
+ art::Primitive::Type type = impl::GetJNIType<T>();
+ jvmtiError err = GetLocalVariableGeneric(env, thread, depth, slot, type, &v);
+ if (err != OK) {
+ return err;
+ } else {
+ impl::ReadJvalue(v, data);
+ return OK;
+ }
+}
+
+#define GET_SET_LV(type, prim, id) \
+ template jvmtiError MethodUtil::GetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type*); \
+ template jvmtiError MethodUtil::SetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type);
+
+FOR_JVMTI_JVALUE_TYPES(GET_SET_LV);
+
+#undef GET_SET_LV
+
+#undef FOR_JVMTI_JVALUE_TYPES
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_method.h b/openjdkjvmti/ti_method.h
new file mode 100644
index 0000000..e3578a4
--- /dev/null
+++ b/openjdkjvmti/ti_method.h
@@ -0,0 +1,114 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_METHOD_H_
+#define ART_OPENJDKJVMTI_TI_METHOD_H_
+
+#include "jni.h"
+#include "jvmti.h"
+#include "primitive.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class MethodUtil {
+ public:
+ static void Register(EventHandler* event_handler);
+ static void Unregister();
+
+ static jvmtiError GetBytecodes(jvmtiEnv* env,
+ jmethodID method,
+ jint* count_ptr,
+ unsigned char** bytecodes);
+
+ static jvmtiError GetArgumentsSize(jvmtiEnv* env, jmethodID method, jint* size_ptr);
+
+ static jvmtiError GetMaxLocals(jvmtiEnv* env, jmethodID method, jint* max_ptr);
+
+ static jvmtiError GetMethodName(jvmtiEnv* env,
+ jmethodID method,
+ char** name_ptr,
+ char** signature_ptr,
+ char** generic_ptr);
+
+ static jvmtiError GetMethodDeclaringClass(jvmtiEnv* env,
+ jmethodID method,
+ jclass* declaring_class_ptr);
+
+ static jvmtiError GetMethodLocation(jvmtiEnv* env,
+ jmethodID method,
+ jlocation* start_location_ptr,
+ jlocation* end_location_ptr);
+
+ static jvmtiError GetMethodModifiers(jvmtiEnv* env,
+ jmethodID method,
+ jint* modifiers_ptr);
+
+ static jvmtiError GetLineNumberTable(jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLineNumberEntry** table_ptr);
+
+ static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr);
+ static jvmtiError IsMethodObsolete(jvmtiEnv* env, jmethodID method, jboolean* is_obsolete_ptr);
+ static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr);
+ static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
+ jmethodID method,
+ jint* entry_count_ptr,
+ jvmtiLocalVariableEntry** table_ptr);
+
+ template<typename T>
+ static jvmtiError SetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T data);
+
+ template<typename T>
+ static jvmtiError GetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T* data);
+
+ static jvmtiError GetLocalInstance(jvmtiEnv* env, jthread thread, jint depth, jobject* data);
+
+ private:
+ static jvmtiError SetLocalVariableGeneric(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ art::Primitive::Type type,
+ jvalue value);
+ static jvmtiError GetLocalVariableGeneric(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jint slot,
+ art::Primitive::Type type,
+ jvalue* value);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_METHOD_H_
diff --git a/openjdkjvmti/ti_monitor.cc b/openjdkjvmti/ti_monitor.cc
new file mode 100644
index 0000000..61bf533
--- /dev/null
+++ b/openjdkjvmti/ti_monitor.cc
@@ -0,0 +1,302 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_monitor.h"
+
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+
+#include "art_jvmti.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+// We cannot use ART monitors, as they require the mutator lock for contention locking. We
+// also cannot use pthread mutexes and condition variables (or C++11 abstractions) directly,
+// as the do not have the right semantics for recursive mutexes and waiting (wait only unlocks
+// the mutex once).
+// So go ahead and use a wrapper that does the counting explicitly.
+
+class JvmtiMonitor {
+ public:
+ JvmtiMonitor() : owner_(nullptr), count_(0) {
+ }
+
+ static bool Destroy(art::Thread* self, JvmtiMonitor* monitor) NO_THREAD_SAFETY_ANALYSIS {
+ // Check whether this thread holds the monitor, or nobody does.
+ art::Thread* owner_thread = monitor->owner_.load(std::memory_order_relaxed);
+ if (owner_thread != nullptr && self != owner_thread) {
+ return false;
+ }
+
+ if (monitor->count_ > 0) {
+ monitor->count_ = 0;
+ monitor->owner_.store(nullptr, std::memory_order_relaxed);
+ monitor->mutex_.unlock();
+ }
+
+ delete monitor;
+ return true;
+ }
+
+ void MonitorEnter(art::Thread* self) NO_THREAD_SAFETY_ANALYSIS {
+ // Check for recursive enter.
+ if (IsOwner(self)) {
+ count_++;
+ return;
+ }
+
+ mutex_.lock();
+
+ DCHECK(owner_.load(std::memory_order_relaxed) == nullptr);
+ owner_.store(self, std::memory_order_relaxed);
+ DCHECK_EQ(0u, count_);
+ count_ = 1;
+ }
+
+ bool MonitorExit(art::Thread* self) NO_THREAD_SAFETY_ANALYSIS {
+ if (!IsOwner(self)) {
+ return false;
+ }
+
+ --count_;
+ if (count_ == 0u) {
+ owner_.store(nullptr, std::memory_order_relaxed);
+ mutex_.unlock();
+ }
+
+ return true;
+ }
+
+ bool Wait(art::Thread* self) {
+ auto wait_without_timeout = [&](std::unique_lock<std::mutex>& lk) {
+ cond_.wait(lk);
+ };
+ return Wait(self, wait_without_timeout);
+ }
+
+ bool Wait(art::Thread* self, uint64_t timeout_in_ms) {
+ auto wait_with_timeout = [&](std::unique_lock<std::mutex>& lk) {
+ cond_.wait_for(lk, std::chrono::milliseconds(timeout_in_ms));
+ };
+ return Wait(self, wait_with_timeout);
+ }
+
+ bool Notify(art::Thread* self) {
+ return Notify(self, [&]() { cond_.notify_one(); });
+ }
+
+ bool NotifyAll(art::Thread* self) {
+ return Notify(self, [&]() { cond_.notify_all(); });
+ }
+
+ private:
+ bool IsOwner(art::Thread* self) {
+ // There's a subtle correctness argument here for a relaxed load outside the critical section.
+ // A thread is guaranteed to see either its own latest store or another thread's store. If a
+ // thread sees another thread's store than it cannot be holding the lock.
+ art::Thread* owner_thread = owner_.load(std::memory_order_relaxed);
+ return self == owner_thread;
+ }
+
+ template <typename T>
+ bool Wait(art::Thread* self, T how_to_wait) {
+ if (!IsOwner(self)) {
+ return false;
+ }
+
+ size_t old_count = count_;
+
+ count_ = 0;
+ owner_.store(nullptr, std::memory_order_relaxed);
+
+ {
+ std::unique_lock<std::mutex> lk(mutex_, std::adopt_lock);
+ how_to_wait(lk);
+ lk.release(); // Do not unlock the mutex.
+ }
+
+ DCHECK(owner_.load(std::memory_order_relaxed) == nullptr);
+ owner_.store(self, std::memory_order_relaxed);
+ DCHECK_EQ(0u, count_);
+ count_ = old_count;
+
+ return true;
+ }
+
+ template <typename T>
+ bool Notify(art::Thread* self, T how_to_notify) {
+ if (!IsOwner(self)) {
+ return false;
+ }
+
+ how_to_notify();
+
+ return true;
+ }
+
+ std::mutex mutex_;
+ std::condition_variable cond_;
+ std::atomic<art::Thread*> owner_;
+ size_t count_;
+};
+
+static jrawMonitorID EncodeMonitor(JvmtiMonitor* monitor) {
+ return reinterpret_cast<jrawMonitorID>(monitor);
+}
+
+static JvmtiMonitor* DecodeMonitor(jrawMonitorID id) {
+ return reinterpret_cast<JvmtiMonitor*>(id);
+}
+
+jvmtiError MonitorUtil::CreateRawMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ const char* name,
+ jrawMonitorID* monitor_ptr) {
+ if (name == nullptr || monitor_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ JvmtiMonitor* monitor = new JvmtiMonitor();
+ *monitor_ptr = EncodeMonitor(monitor);
+
+ return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::DestroyRawMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+ if (id == nullptr) {
+ return ERR(INVALID_MONITOR);
+ }
+
+ JvmtiMonitor* monitor = DecodeMonitor(id);
+ art::Thread* self = art::Thread::Current();
+
+ if (!JvmtiMonitor::Destroy(self, monitor)) {
+ return ERR(NOT_MONITOR_OWNER);
+ }
+
+ return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorEnter(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+ if (id == nullptr) {
+ return ERR(INVALID_MONITOR);
+ }
+
+ JvmtiMonitor* monitor = DecodeMonitor(id);
+ art::Thread* self = art::Thread::Current();
+
+ monitor->MonitorEnter(self);
+
+ return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorExit(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+ if (id == nullptr) {
+ return ERR(INVALID_MONITOR);
+ }
+
+ JvmtiMonitor* monitor = DecodeMonitor(id);
+ art::Thread* self = art::Thread::Current();
+
+ if (!monitor->MonitorExit(self)) {
+ return ERR(NOT_MONITOR_OWNER);
+ }
+
+ return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorWait(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jrawMonitorID id,
+ jlong millis) {
+ if (id == nullptr) {
+ return ERR(INVALID_MONITOR);
+ }
+
+ JvmtiMonitor* monitor = DecodeMonitor(id);
+ art::Thread* self = art::Thread::Current();
+
+ // This is not in the spec, but it's the only thing that makes sense (and agrees with
+ // Object.wait).
+ if (millis < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+
+ bool result = (millis > 0)
+ ? monitor->Wait(self, static_cast<uint64_t>(millis))
+ : monitor->Wait(self);
+
+ if (!result) {
+ return ERR(NOT_MONITOR_OWNER);
+ }
+
+ // TODO: Make sure that is really what we should be checking here.
+ if (self->IsInterrupted()) {
+ return ERR(INTERRUPT);
+ }
+
+ return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorNotify(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+ if (id == nullptr) {
+ return ERR(INVALID_MONITOR);
+ }
+
+ JvmtiMonitor* monitor = DecodeMonitor(id);
+ art::Thread* self = art::Thread::Current();
+
+ if (!monitor->Notify(self)) {
+ return ERR(NOT_MONITOR_OWNER);
+ }
+
+ return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorNotifyAll(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+ if (id == nullptr) {
+ return ERR(INVALID_MONITOR);
+ }
+
+ JvmtiMonitor* monitor = DecodeMonitor(id);
+ art::Thread* self = art::Thread::Current();
+
+ if (!monitor->NotifyAll(self)) {
+ return ERR(NOT_MONITOR_OWNER);
+ }
+
+ return ERR(NONE);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_monitor.h b/openjdkjvmti/ti_monitor.h
new file mode 100644
index 0000000..add089c
--- /dev/null
+++ b/openjdkjvmti/ti_monitor.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_MONITOR_H_
+#define ART_OPENJDKJVMTI_TI_MONITOR_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class MonitorUtil {
+ public:
+ static jvmtiError CreateRawMonitor(jvmtiEnv* env, const char* name, jrawMonitorID* monitor_ptr);
+
+ static jvmtiError DestroyRawMonitor(jvmtiEnv* env, jrawMonitorID monitor);
+
+ static jvmtiError RawMonitorEnter(jvmtiEnv* env, jrawMonitorID monitor);
+
+ static jvmtiError RawMonitorExit(jvmtiEnv* env, jrawMonitorID monitor);
+
+ static jvmtiError RawMonitorWait(jvmtiEnv* env, jrawMonitorID monitor, jlong millis);
+
+ static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor);
+
+ static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_MONITOR_H_
diff --git a/openjdkjvmti/ti_object.cc b/openjdkjvmti/ti_object.cc
new file mode 100644
index 0000000..2506aca
--- /dev/null
+++ b/openjdkjvmti/ti_object.cc
@@ -0,0 +1,76 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_object.h"
+
+#include "art_jvmti.h"
+#include "mirror/object-inl.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError ObjectUtil::GetObjectSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jobject jobject,
+ jlong* size_ptr) {
+ if (jobject == nullptr) {
+ return ERR(INVALID_OBJECT);
+ }
+ if (size_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Object> object = soa.Decode<art::mirror::Object>(jobject);
+
+ *size_ptr = object->SizeOf();
+ return ERR(NONE);
+}
+
+jvmtiError ObjectUtil::GetObjectHashCode(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jobject jobject,
+ jint* hash_code_ptr) {
+ if (jobject == nullptr) {
+ return ERR(INVALID_OBJECT);
+ }
+ if (hash_code_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Object> object = soa.Decode<art::mirror::Object>(jobject);
+
+ *hash_code_ptr = object->IdentityHashCode();
+
+ return ERR(NONE);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_object.h b/openjdkjvmti/ti_object.h
new file mode 100644
index 0000000..fa3bd0f
--- /dev/null
+++ b/openjdkjvmti/ti_object.h
@@ -0,0 +1,49 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_OBJECT_H_
+#define ART_OPENJDKJVMTI_TI_OBJECT_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class ObjectUtil {
+ public:
+ static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr);
+
+ static jvmtiError GetObjectHashCode(jvmtiEnv* env, jobject object, jint* hash_code_ptr);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_OBJECT_H_
diff --git a/openjdkjvmti/ti_phase.cc b/openjdkjvmti/ti_phase.cc
new file mode 100644
index 0000000..8893c9b
--- /dev/null
+++ b/openjdkjvmti/ti_phase.cc
@@ -0,0 +1,155 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_phase.h"
+
+#include "art_jvmti.h"
+#include "base/macros.h"
+#include "events-inl.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_thread.h"
+
+namespace openjdkjvmti {
+
+jvmtiPhase PhaseUtil::current_phase_ = static_cast<jvmtiPhase>(0);
+
+struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
+ inline static JNIEnv* GetJniEnv() {
+ return reinterpret_cast<JNIEnv*>(art::Thread::Current()->GetJniEnv());
+ }
+
+ inline static jthread GetCurrentJThread() {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ return soa.AddLocalReference<jthread>(soa.Self()->GetPeer());
+ }
+
+ void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+ switch (phase) {
+ case RuntimePhase::kInitialAgents:
+ PhaseUtil::current_phase_ = JVMTI_PHASE_PRIMORDIAL;
+ break;
+ case RuntimePhase::kStart:
+ {
+ art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kVmStart>(nullptr, GetJniEnv());
+ PhaseUtil::current_phase_ = JVMTI_PHASE_START;
+ }
+ break;
+ case RuntimePhase::kInit:
+ {
+ ThreadUtil::CacheData();
+ ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread());
+ art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(nullptr, GetJniEnv(), thread.get());
+ PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
+ }
+ break;
+ case RuntimePhase::kDeath:
+ {
+ art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kVmDeath>(nullptr, GetJniEnv());
+ PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
+ }
+ // TODO: Block events now.
+ break;
+ }
+ }
+
+ EventHandler* event_handler = nullptr;
+};
+
+PhaseUtil::PhaseCallback gPhaseCallback;
+
+jvmtiError PhaseUtil::GetPhase(jvmtiEnv* env ATTRIBUTE_UNUSED, jvmtiPhase* phase_ptr) {
+ if (phase_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ jvmtiPhase now = PhaseUtil::current_phase_;
+ DCHECK(now == JVMTI_PHASE_ONLOAD ||
+ now == JVMTI_PHASE_PRIMORDIAL ||
+ now == JVMTI_PHASE_START ||
+ now == JVMTI_PHASE_LIVE ||
+ now == JVMTI_PHASE_DEAD);
+ *phase_ptr = now;
+ return ERR(NONE);
+}
+
+bool PhaseUtil::IsLivePhase() {
+ jvmtiPhase now = PhaseUtil::current_phase_;
+ DCHECK(now == JVMTI_PHASE_ONLOAD ||
+ now == JVMTI_PHASE_PRIMORDIAL ||
+ now == JVMTI_PHASE_START ||
+ now == JVMTI_PHASE_LIVE ||
+ now == JVMTI_PHASE_DEAD);
+ return now == JVMTI_PHASE_LIVE;
+}
+
+void PhaseUtil::SetToOnLoad() {
+ DCHECK_EQ(0u, static_cast<size_t>(PhaseUtil::current_phase_));
+ PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD;
+}
+
+void PhaseUtil::SetToPrimordial() {
+ DCHECK_EQ(static_cast<size_t>(JVMTI_PHASE_ONLOAD), static_cast<size_t>(PhaseUtil::current_phase_));
+ PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD;
+}
+
+void PhaseUtil::SetToLive() {
+ DCHECK_EQ(static_cast<size_t>(0), static_cast<size_t>(PhaseUtil::current_phase_));
+ ThreadUtil::CacheData();
+ PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
+}
+
+void PhaseUtil::Register(EventHandler* handler) {
+ gPhaseCallback.event_handler = handler;
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add phase callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gPhaseCallback);
+}
+
+void PhaseUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove phase callback");
+ art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gPhaseCallback);
+}
+
+jvmtiPhase PhaseUtil::GetPhaseUnchecked() {
+ return PhaseUtil::current_phase_;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_phase.h b/openjdkjvmti/ti_phase.h
new file mode 100644
index 0000000..d4ed86b
--- /dev/null
+++ b/openjdkjvmti/ti_phase.h
@@ -0,0 +1,69 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_PHASE_H_
+#define ART_OPENJDKJVMTI_TI_PHASE_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class PhaseUtil {
+ public:
+ static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr);
+ static bool IsLivePhase();
+
+ static void Register(EventHandler* event_handler);
+ static void Unregister();
+
+ // Move the phase from unitialized to LOAD.
+ static void SetToOnLoad();
+
+ // Move the phase from LOAD to PRIMORDIAL.
+ static void SetToPrimordial();
+
+ // Move the phase from unitialized to LIVE.
+ static void SetToLive();
+
+ struct PhaseCallback;
+
+ static jvmtiPhase GetPhaseUnchecked();
+
+ private:
+ static jvmtiPhase current_phase_;
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_PHASE_H_
diff --git a/openjdkjvmti/ti_properties.cc b/openjdkjvmti/ti_properties.cc
new file mode 100644
index 0000000..c412814
--- /dev/null
+++ b/openjdkjvmti/ti_properties.cc
@@ -0,0 +1,236 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_properties.h"
+
+#include <string.h>
+#include <vector>
+
+#include "jni.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "nativehelper/ScopedUtfChars.h"
+
+#include "art_jvmti.h"
+#include "runtime.h"
+#include "thread-current-inl.h"
+#include "ti_phase.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+// Hardcoded properties. Tests ensure that these are consistent with libcore's view, as seen
+// in System.java and AndroidHardcodedSystemProperties.java.
+static constexpr const char* kProperties[][2] = {
+ // Recommended by the spec.
+ { "java.vm.vendor", "The Android Project" },
+ { "java.vm.version", "2.1.0" }, // This is Runtime::GetVersion().
+ { "java.vm.name", "Dalvik" },
+ // Android does not provide java.vm.info.
+ //
+ // These are other values provided by AndroidHardcodedSystemProperties.
+ { "java.class.version", "50.0" },
+ { "java.version", "0" },
+ { "java.compiler", "" },
+ { "java.ext.dirs", "" },
+
+ { "java.specification.name", "Dalvik Core Library" },
+ { "java.specification.vendor", "The Android Project" },
+ { "java.specification.version", "0.9" },
+
+ { "java.vendor", "The Android Project" },
+ { "java.vendor.url", "http://www.android.com/" },
+ { "java.vm.name", "Dalvik" },
+ { "java.vm.specification.name", "Dalvik Virtual Machine Specification" },
+ { "java.vm.specification.vendor", "The Android Project" },
+ { "java.vm.specification.version", "0.9" },
+ { "java.vm.vendor", "The Android Project" },
+
+ { "java.vm.vendor.url", "http://www.android.com/" },
+
+ { "java.net.preferIPv6Addresses", "false" },
+
+ { "file.encoding", "UTF-8" },
+
+ { "file.separator", "/" },
+ { "line.separator", "\n" },
+ { "path.separator", ":" },
+
+ { "os.name", "Linux" },
+};
+static constexpr size_t kPropertiesSize = arraysize(kProperties);
+static constexpr const char* kPropertyLibraryPath = "java.library.path";
+static constexpr const char* kPropertyClassPath = "java.class.path";
+
+jvmtiError PropertiesUtil::GetSystemProperties(jvmtiEnv* env,
+ jint* count_ptr,
+ char*** property_ptr) {
+ if (count_ptr == nullptr || property_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ jvmtiError array_alloc_result;
+ JvmtiUniquePtr<char*[]> array_data_ptr = AllocJvmtiUniquePtr<char*[]>(env,
+ kPropertiesSize + 2,
+ &array_alloc_result);
+ if (array_data_ptr == nullptr) {
+ return array_alloc_result;
+ }
+
+ std::vector<JvmtiUniquePtr<char[]>> property_copies;
+
+ {
+ jvmtiError libpath_result;
+ JvmtiUniquePtr<char[]> libpath_data = CopyString(env, kPropertyLibraryPath, &libpath_result);
+ if (libpath_data == nullptr) {
+ return libpath_result;
+ }
+ array_data_ptr.get()[0] = libpath_data.get();
+ property_copies.push_back(std::move(libpath_data));
+ }
+
+ {
+ jvmtiError classpath_result;
+ JvmtiUniquePtr<char[]> classpath_data = CopyString(env, kPropertyClassPath, &classpath_result);
+ if (classpath_data == nullptr) {
+ return classpath_result;
+ }
+ array_data_ptr.get()[1] = classpath_data.get();
+ property_copies.push_back(std::move(classpath_data));
+ }
+
+ for (size_t i = 0; i != kPropertiesSize; ++i) {
+ jvmtiError data_result;
+ JvmtiUniquePtr<char[]> data = CopyString(env, kProperties[i][0], &data_result);
+ if (data == nullptr) {
+ return data_result;
+ }
+ array_data_ptr.get()[i + 2] = data.get();
+ property_copies.push_back(std::move(data));
+ }
+
+ // Everything is OK, release the data.
+ *count_ptr = kPropertiesSize + 2;
+ *property_ptr = array_data_ptr.release();
+ for (auto& uptr : property_copies) {
+ uptr.release();
+ }
+
+ return ERR(NONE);
+}
+
+static jvmtiError Copy(jvmtiEnv* env, const char* in, char** out) {
+ jvmtiError result;
+ JvmtiUniquePtr<char[]> data = CopyString(env, in, &result);
+ *out = data.release();
+ return result;
+}
+
+// See dalvik_system_VMRuntime.cpp.
+static const char* DefaultToDot(const std::string& class_path) {
+ return class_path.empty() ? "." : class_path.c_str();
+}
+
+// Handle kPropertyLibraryPath.
+static jvmtiError GetLibraryPath(jvmtiEnv* env, char** value_ptr) {
+ const std::vector<std::string>& runtime_props = art::Runtime::Current()->GetProperties();
+ for (const std::string& prop_assignment : runtime_props) {
+ size_t assign_pos = prop_assignment.find('=');
+ if (assign_pos != std::string::npos && assign_pos > 0) {
+ if (prop_assignment.substr(0, assign_pos) == kPropertyLibraryPath) {
+ return Copy(env, prop_assignment.substr(assign_pos + 1).c_str(), value_ptr);
+ }
+ }
+ }
+ if (!PhaseUtil::IsLivePhase()) {
+ return ERR(NOT_AVAILABLE);
+ }
+ // We expect this call to be rare. So don't optimize.
+ DCHECK(art::Thread::Current() != nullptr);
+ JNIEnv* jni_env = art::Thread::Current()->GetJniEnv();
+ jmethodID get_prop = jni_env->GetStaticMethodID(art::WellKnownClasses::java_lang_System,
+ "getProperty",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+ CHECK(get_prop != nullptr);
+
+ ScopedLocalRef<jobject> input_str(jni_env, jni_env->NewStringUTF(kPropertyLibraryPath));
+ if (input_str.get() == nullptr) {
+ jni_env->ExceptionClear();
+ return ERR(OUT_OF_MEMORY);
+ }
+
+ ScopedLocalRef<jobject> prop_res(
+ jni_env, jni_env->CallStaticObjectMethod(art::WellKnownClasses::java_lang_System,
+ get_prop,
+ input_str.get()));
+ if (jni_env->ExceptionCheck() == JNI_TRUE) {
+ jni_env->ExceptionClear();
+ return ERR(INTERNAL);
+ }
+ if (prop_res.get() == nullptr) {
+ *value_ptr = nullptr;
+ return ERR(NONE);
+ }
+
+ ScopedUtfChars chars(jni_env, reinterpret_cast<jstring>(prop_res.get()));
+ return Copy(env, chars.c_str(), value_ptr);
+}
+
+jvmtiError PropertiesUtil::GetSystemProperty(jvmtiEnv* env,
+ const char* property,
+ char** value_ptr) {
+ if (property == nullptr || value_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ if (strcmp(property, kPropertyLibraryPath) == 0) {
+ return GetLibraryPath(env, value_ptr);
+ }
+
+ if (strcmp(property, kPropertyClassPath) == 0) {
+ return Copy(env, DefaultToDot(art::Runtime::Current()->GetClassPathString()), value_ptr);
+ }
+
+ for (size_t i = 0; i != kPropertiesSize; ++i) {
+ if (strcmp(property, kProperties[i][0]) == 0) {
+ return Copy(env, kProperties[i][1], value_ptr);
+ }
+ }
+
+ return ERR(NOT_AVAILABLE);
+}
+
+jvmtiError PropertiesUtil::SetSystemProperty(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ const char* property ATTRIBUTE_UNUSED,
+ const char* value ATTRIBUTE_UNUSED) {
+ // We do not allow manipulation of any property here.
+ return ERR(NOT_AVAILABLE);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_properties.h b/openjdkjvmti/ti_properties.h
new file mode 100644
index 0000000..187b85d
--- /dev/null
+++ b/openjdkjvmti/ti_properties.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_PROPERTIES_H_
+#define ART_OPENJDKJVMTI_TI_PROPERTIES_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class PropertiesUtil {
+ public:
+ static jvmtiError GetSystemProperties(jvmtiEnv* env, jint* count_ptr, char*** property_ptr);
+
+ static jvmtiError GetSystemProperty(jvmtiEnv* env, const char* property, char** value_ptr);
+
+ static jvmtiError SetSystemProperty(jvmtiEnv* env, const char* property, const char* value);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_PROPERTIES_H_
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
new file mode 100644
index 0000000..c679d73
--- /dev/null
+++ b/openjdkjvmti/ti_redefine.cc
@@ -0,0 +1,1517 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_redefine.h"
+
+#include <limits>
+
+#include "android-base/stringprintf.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/array_ref.h"
+#include "base/logging.h"
+#include "base/stringpiece.h"
+#include "class_linker-inl.h"
+#include "debugger.h"
+#include "dex_file.h"
+#include "dex_file_types.h"
+#include "events-inl.h"
+#include "gc/allocation_listener.h"
+#include "gc/heap.h"
+#include "instrumentation.h"
+#include "intern_table.h"
+#include "jdwp/jdwp.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/object_registry.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti_allocator.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
+#include "mirror/object.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "non_debuggable_classes.h"
+#include "object_lock.h"
+#include "runtime.h"
+#include "ti_breakpoint.h"
+#include "ti_class_loader.h"
+#include "transform.h"
+#include "verifier/method_verifier.h"
+#include "verifier/verifier_enums.h"
+
+namespace openjdkjvmti {
+
+using android::base::StringPrintf;
+
+// A helper that fills in a classes obsolete_methods_ and obsolete_dex_caches_ classExt fields as
+// they are created. This ensures that we can always call any method of an obsolete ArtMethod object
+// almost as soon as they are created since the GetObsoleteDexCache method will succeed.
+class ObsoleteMap {
+ public:
+ art::ArtMethod* FindObsoleteVersion(art::ArtMethod* original)
+ REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
+ auto method_pair = id_map_.find(original);
+ if (method_pair != id_map_.end()) {
+ art::ArtMethod* res = obsolete_methods_->GetElementPtrSize<art::ArtMethod*>(
+ method_pair->second, art::kRuntimePointerSize);
+ DCHECK(res != nullptr);
+ DCHECK_EQ(original, res->GetNonObsoleteMethod());
+ return res;
+ } else {
+ return nullptr;
+ }
+ }
+
+ void RecordObsolete(art::ArtMethod* original, art::ArtMethod* obsolete)
+ REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
+ DCHECK(original != nullptr);
+ DCHECK(obsolete != nullptr);
+ int32_t slot = next_free_slot_++;
+ DCHECK_LT(slot, obsolete_methods_->GetLength());
+ DCHECK(nullptr ==
+ obsolete_methods_->GetElementPtrSize<art::ArtMethod*>(slot, art::kRuntimePointerSize));
+ DCHECK(nullptr == obsolete_dex_caches_->Get(slot));
+ obsolete_methods_->SetElementPtrSize(slot, obsolete, art::kRuntimePointerSize);
+ obsolete_dex_caches_->Set(slot, original_dex_cache_);
+ id_map_.insert({original, slot});
+ }
+
+ ObsoleteMap(art::ObjPtr<art::mirror::PointerArray> obsolete_methods,
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::DexCache>> obsolete_dex_caches,
+ art::ObjPtr<art::mirror::DexCache> original_dex_cache)
+ : next_free_slot_(0),
+ obsolete_methods_(obsolete_methods),
+ obsolete_dex_caches_(obsolete_dex_caches),
+ original_dex_cache_(original_dex_cache) {
+ // Figure out where the first unused slot in the obsolete_methods_ array is.
+ while (obsolete_methods_->GetElementPtrSize<art::ArtMethod*>(
+ next_free_slot_, art::kRuntimePointerSize) != nullptr) {
+ DCHECK(obsolete_dex_caches_->Get(next_free_slot_) != nullptr);
+ next_free_slot_++;
+ }
+ // Sanity check that the same slot in obsolete_dex_caches_ is free.
+ DCHECK(obsolete_dex_caches_->Get(next_free_slot_) == nullptr);
+ }
+
+ private:
+ int32_t next_free_slot_;
+ std::unordered_map<art::ArtMethod*, int32_t> id_map_;
+ // Pointers to the fields in mirror::ClassExt. These can be held as ObjPtr since this is only used
+ // when we have an exclusive mutator_lock_ (i.e. all threads are suspended).
+ art::ObjPtr<art::mirror::PointerArray> obsolete_methods_;
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::DexCache>> obsolete_dex_caches_;
+ art::ObjPtr<art::mirror::DexCache> original_dex_cache_;
+};
+
+// This visitor walks thread stacks and allocates and sets up the obsolete methods. It also does
+// some basic sanity checks that the obsolete method is sane.
+class ObsoleteMethodStackVisitor : public art::StackVisitor {
+ protected:
+ ObsoleteMethodStackVisitor(
+ art::Thread* thread,
+ art::LinearAlloc* allocator,
+ const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
+ ObsoleteMap* obsolete_maps)
+ : StackVisitor(thread,
+ /*context*/nullptr,
+ StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ allocator_(allocator),
+ obsoleted_methods_(obsoleted_methods),
+ obsolete_maps_(obsolete_maps) { }
+
+ ~ObsoleteMethodStackVisitor() OVERRIDE {}
+
+ public:
+ // Returns true if we successfully installed obsolete methods on this thread, filling
+ // obsolete_maps_ with the translations if needed. Returns false and fills error_msg if we fail.
+ // The stack is cleaned up when we fail.
+ static void UpdateObsoleteFrames(
+ art::Thread* thread,
+ art::LinearAlloc* allocator,
+ const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
+ ObsoleteMap* obsolete_maps)
+ REQUIRES(art::Locks::mutator_lock_) {
+ ObsoleteMethodStackVisitor visitor(thread,
+ allocator,
+ obsoleted_methods,
+ obsolete_maps);
+ visitor.WalkStack();
+ }
+
+ bool VisitFrame() OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ art::ScopedAssertNoThreadSuspension snts("Fixing up the stack for obsolete methods.");
+ art::ArtMethod* old_method = GetMethod();
+ if (obsoleted_methods_.find(old_method) != obsoleted_methods_.end()) {
+ // We cannot ensure that the right dex file is used in inlined frames so we don't support
+ // redefining them.
+ DCHECK(!IsInInlinedFrame()) << "Inlined frames are not supported when using redefinition";
+ art::ArtMethod* new_obsolete_method = obsolete_maps_->FindObsoleteVersion(old_method);
+ if (new_obsolete_method == nullptr) {
+ // Create a new Obsolete Method and put it in the list.
+ art::Runtime* runtime = art::Runtime::Current();
+ art::ClassLinker* cl = runtime->GetClassLinker();
+ auto ptr_size = cl->GetImagePointerSize();
+ const size_t method_size = art::ArtMethod::Size(ptr_size);
+ auto* method_storage = allocator_->Alloc(art::Thread::Current(), method_size);
+ CHECK(method_storage != nullptr) << "Unable to allocate storage for obsolete version of '"
+ << old_method->PrettyMethod() << "'";
+ new_obsolete_method = new (method_storage) art::ArtMethod();
+ new_obsolete_method->CopyFrom(old_method, ptr_size);
+ DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass());
+ new_obsolete_method->SetIsObsolete();
+ new_obsolete_method->SetDontCompile();
+ cl->SetEntryPointsForObsoleteMethod(new_obsolete_method);
+ obsolete_maps_->RecordObsolete(old_method, new_obsolete_method);
+ // Update JIT Data structures to point to the new method.
+ art::jit::Jit* jit = art::Runtime::Current()->GetJit();
+ if (jit != nullptr) {
+ // Notify the JIT we are making this obsolete method. It will update the jit's internal
+ // structures to keep track of the new obsolete method.
+ jit->GetCodeCache()->MoveObsoleteMethod(old_method, new_obsolete_method);
+ }
+ }
+ DCHECK(new_obsolete_method != nullptr);
+ SetMethod(new_obsolete_method);
+ }
+ return true;
+ }
+
+ private:
+ // The linear allocator we should use to make new methods.
+ art::LinearAlloc* allocator_;
+ // The set of all methods which could be obsoleted.
+ const std::unordered_set<art::ArtMethod*>& obsoleted_methods_;
+ // A map from the original to the newly allocated obsolete method for frames on this thread. The
+ // values in this map are added to the obsolete_methods_ (and obsolete_dex_caches_) fields of
+ // the redefined classes ClassExt as it is filled.
+ ObsoleteMap* obsolete_maps_;
+};
+
+jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jclass klass,
+ jboolean* is_redefinable) {
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::StackHandleScope<1> hs(self);
+ art::ObjPtr<art::mirror::Object> obj(self->DecodeJObject(klass));
+ if (obj.IsNull()) {
+ return ERR(INVALID_CLASS);
+ }
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
+ std::string err_unused;
+ *is_redefinable =
+ Redefiner::GetClassRedefinitionError(h_klass, &err_unused) == OK ? JNI_TRUE : JNI_FALSE;
+ return OK;
+}
+
+jvmtiError Redefiner::GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
+ /*out*/std::string* error_msg) {
+ if (klass->IsPrimitive()) {
+ *error_msg = "Modification of primitive classes is not supported";
+ return ERR(UNMODIFIABLE_CLASS);
+ } else if (klass->IsInterface()) {
+ *error_msg = "Modification of Interface classes is currently not supported";
+ return ERR(UNMODIFIABLE_CLASS);
+ } else if (klass->IsStringClass()) {
+ *error_msg = "Modification of String class is not supported";
+ return ERR(UNMODIFIABLE_CLASS);
+ } else if (klass->IsArrayClass()) {
+ *error_msg = "Modification of Array classes is not supported";
+ return ERR(UNMODIFIABLE_CLASS);
+ } else if (klass->IsProxyClass()) {
+ *error_msg = "Modification of proxy classes is not supported";
+ return ERR(UNMODIFIABLE_CLASS);
+ }
+
+ for (jclass c : art::NonDebuggableClasses::GetNonDebuggableClasses()) {
+ if (klass.Get() == art::Thread::Current()->DecodeJObject(c)->AsClass()) {
+ *error_msg = "Class might have stack frames that cannot be made obsolete";
+ return ERR(UNMODIFIABLE_CLASS);
+ }
+ }
+
+ return OK;
+}
+
+// Moves dex data to an anonymous, read-only mmap'd region.
+std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& original_location,
+ art::ArrayRef<const unsigned char> data,
+ std::string* error_msg) {
+ std::unique_ptr<art::MemMap> map(art::MemMap::MapAnonymous(
+ StringPrintf("%s-transformed", original_location.c_str()).c_str(),
+ nullptr,
+ data.size(),
+ PROT_READ|PROT_WRITE,
+ /*low_4gb*/false,
+ /*reuse*/false,
+ error_msg));
+ if (map == nullptr) {
+ return map;
+ }
+ memcpy(map->Begin(), data.data(), data.size());
+ // Make the dex files mmap read only. This matches how other DexFiles are mmaped and prevents
+ // programs from corrupting it.
+ map->Protect(PROT_READ);
+ return map;
+}
+
+Redefiner::ClassRedefinition::ClassRedefinition(
+ Redefiner* driver,
+ jclass klass,
+ const art::DexFile* redefined_dex_file,
+ const char* class_sig,
+ art::ArrayRef<const unsigned char> orig_dex_file) :
+ driver_(driver),
+ klass_(klass),
+ dex_file_(redefined_dex_file),
+ class_sig_(class_sig),
+ original_dex_file_(orig_dex_file) {
+ GetMirrorClass()->MonitorEnter(driver_->self_);
+}
+
+Redefiner::ClassRedefinition::~ClassRedefinition() {
+ if (driver_ != nullptr) {
+ GetMirrorClass()->MonitorExit(driver_->self_);
+ }
+}
+
+jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env,
+ EventHandler* event_handler,
+ art::Runtime* runtime,
+ art::Thread* self,
+ jint class_count,
+ const jvmtiClassDefinition* definitions,
+ /*out*/std::string* error_msg) {
+ if (env == nullptr) {
+ *error_msg = "env was null!";
+ return ERR(INVALID_ENVIRONMENT);
+ } else if (class_count < 0) {
+ *error_msg = "class_count was less then 0";
+ return ERR(ILLEGAL_ARGUMENT);
+ } else if (class_count == 0) {
+ // We don't actually need to do anything. Just return OK.
+ return OK;
+ } else if (definitions == nullptr) {
+ *error_msg = "null definitions!";
+ return ERR(NULL_POINTER);
+ }
+ std::vector<ArtClassDefinition> def_vector;
+ def_vector.reserve(class_count);
+ for (jint i = 0; i < class_count; i++) {
+ jboolean is_modifiable = JNI_FALSE;
+ jvmtiError res = env->IsModifiableClass(definitions[i].klass, &is_modifiable);
+ if (res != OK) {
+ return res;
+ } else if (!is_modifiable) {
+ return ERR(UNMODIFIABLE_CLASS);
+ }
+ // We make a copy of the class_bytes to pass into the retransformation.
+ // This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we
+ // will need to keep the original bytes around unaltered for subsequent RetransformClasses calls
+ // to get the passed in bytes.
+ unsigned char* class_bytes_copy = nullptr;
+ res = env->Allocate(definitions[i].class_byte_count, &class_bytes_copy);
+ if (res != OK) {
+ return res;
+ }
+ memcpy(class_bytes_copy, definitions[i].class_bytes, definitions[i].class_byte_count);
+
+ ArtClassDefinition def;
+ res = def.Init(env, definitions[i]);
+ if (res != OK) {
+ return res;
+ }
+ def_vector.push_back(std::move(def));
+ }
+ // Call all the transformation events.
+ jvmtiError res = Transformer::RetransformClassesDirect(env,
+ event_handler,
+ self,
+ &def_vector);
+ if (res != OK) {
+ // Something went wrong with transformation!
+ return res;
+ }
+ return RedefineClassesDirect(env, runtime, self, def_vector, error_msg);
+}
+
+jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env,
+ art::Runtime* runtime,
+ art::Thread* self,
+ const std::vector<ArtClassDefinition>& definitions,
+ std::string* error_msg) {
+ DCHECK(env != nullptr);
+ if (definitions.size() == 0) {
+ // We don't actually need to do anything. Just return OK.
+ return OK;
+ }
+ // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
+ // are going to redefine.
+ art::jit::ScopedJitSuspend suspend_jit;
+ // Get shared mutator lock so we can lock all the classes.
+ art::ScopedObjectAccess soa(self);
+ Redefiner r(env, runtime, self, error_msg);
+ for (const ArtClassDefinition& def : definitions) {
+ // Only try to transform classes that have been modified.
+ if (def.IsModified()) {
+ jvmtiError res = r.AddRedefinition(env, def);
+ if (res != OK) {
+ return res;
+ }
+ }
+ }
+ return r.Run();
+}
+
+jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def) {
+ std::string original_dex_location;
+ jvmtiError ret = OK;
+ if ((ret = GetClassLocation(env, def.GetClass(), &original_dex_location))) {
+ *error_msg_ = "Unable to get original dex file location!";
+ return ret;
+ }
+ char* generic_ptr_unused = nullptr;
+ char* signature_ptr = nullptr;
+ if ((ret = env->GetClassSignature(def.GetClass(), &signature_ptr, &generic_ptr_unused)) != OK) {
+ *error_msg_ = "Unable to get class signature!";
+ return ret;
+ }
+ JvmtiUniquePtr<char> generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
+ JvmtiUniquePtr<char> signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
+ std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location,
+ def.GetDexData(),
+ error_msg_));
+ std::ostringstream os;
+ if (map.get() == nullptr) {
+ os << "Failed to create anonymous mmap for modified dex file of class " << def.GetName()
+ << "in dex file " << original_dex_location << " because: " << *error_msg_;
+ *error_msg_ = os.str();
+ return ERR(OUT_OF_MEMORY);
+ }
+ if (map->Size() < sizeof(art::DexFile::Header)) {
+ *error_msg_ = "Could not read dex file header because dex_data was too short";
+ return ERR(INVALID_CLASS_FORMAT);
+ }
+ uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_;
+ std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map->GetName(),
+ checksum,
+ std::move(map),
+ /*verify*/true,
+ /*verify_checksum*/true,
+ error_msg_));
+ if (dex_file.get() == nullptr) {
+ os << "Unable to load modified dex file for " << def.GetName() << ": " << *error_msg_;
+ *error_msg_ = os.str();
+ return ERR(INVALID_CLASS_FORMAT);
+ }
+ redefinitions_.push_back(
+ Redefiner::ClassRedefinition(this,
+ def.GetClass(),
+ dex_file.release(),
+ signature_ptr,
+ def.GetNewOriginalDexFile()));
+ return OK;
+}
+
+art::mirror::Class* Redefiner::ClassRedefinition::GetMirrorClass() {
+ return driver_->self_->DecodeJObject(klass_)->AsClass();
+}
+
+art::mirror::ClassLoader* Redefiner::ClassRedefinition::GetClassLoader() {
+ return GetMirrorClass()->GetClassLoader();
+}
+
+art::mirror::DexCache* Redefiner::ClassRedefinition::CreateNewDexCache(
+ art::Handle<art::mirror::ClassLoader> loader) {
+ art::StackHandleScope<2> hs(driver_->self_);
+ art::ClassLinker* cl = driver_->runtime_->GetClassLinker();
+ art::Handle<art::mirror::DexCache> cache(hs.NewHandle(
+ art::ObjPtr<art::mirror::DexCache>::DownCast(
+ cl->GetClassRoot(art::ClassLinker::kJavaLangDexCache)->AllocObject(driver_->self_))));
+ if (cache.IsNull()) {
+ driver_->self_->AssertPendingOOMException();
+ return nullptr;
+ }
+ art::Handle<art::mirror::String> location(hs.NewHandle(
+ cl->GetInternTable()->InternStrong(dex_file_->GetLocation().c_str())));
+ if (location.IsNull()) {
+ driver_->self_->AssertPendingOOMException();
+ return nullptr;
+ }
+ art::WriterMutexLock mu(driver_->self_, *art::Locks::dex_lock_);
+ art::mirror::DexCache::InitializeDexCache(driver_->self_,
+ cache.Get(),
+ location.Get(),
+ dex_file_.get(),
+ loader.IsNull() ? driver_->runtime_->GetLinearAlloc()
+ : loader->GetAllocator(),
+ art::kRuntimePointerSize);
+ return cache.Get();
+}
+
+void Redefiner::RecordFailure(jvmtiError result,
+ const std::string& class_sig,
+ const std::string& error_msg) {
+ *error_msg_ = StringPrintf("Unable to perform redefinition of '%s': %s",
+ class_sig.c_str(),
+ error_msg.c_str());
+ result_ = result;
+}
+
+art::mirror::Object* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFile() {
+ // If we have been specifically given a new set of bytes use that
+ if (original_dex_file_.size() != 0) {
+ return art::mirror::ByteArray::AllocateAndFill(
+ driver_->self_,
+ reinterpret_cast<const signed char*>(original_dex_file_.data()),
+ original_dex_file_.size());
+ }
+
+ // See if we already have one set.
+ art::ObjPtr<art::mirror::ClassExt> ext(GetMirrorClass()->GetExtData());
+ if (!ext.IsNull()) {
+ art::ObjPtr<art::mirror::Object> old_original_dex_file(ext->GetOriginalDexFile());
+ if (!old_original_dex_file.IsNull()) {
+ // We do. Use it.
+ return old_original_dex_file.Ptr();
+ }
+ }
+
+ // return the current dex_cache which has the dex file in it.
+ art::ObjPtr<art::mirror::DexCache> current_dex_cache(GetMirrorClass()->GetDexCache());
+ // TODO Handle this or make it so it cannot happen.
+ if (current_dex_cache->GetDexFile()->NumClassDefs() != 1) {
+ LOG(WARNING) << "Current dex file has more than one class in it. Calling RetransformClasses "
+ << "on this class might fail if no transformations are applied to it!";
+ }
+ return current_dex_cache.Ptr();
+}
+
+struct CallbackCtx {
+ ObsoleteMap* obsolete_map;
+ art::LinearAlloc* allocator;
+ std::unordered_set<art::ArtMethod*> obsolete_methods;
+
+ explicit CallbackCtx(ObsoleteMap* map, art::LinearAlloc* alloc)
+ : obsolete_map(map), allocator(alloc) {}
+};
+
+void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS {
+ CallbackCtx* data = reinterpret_cast<CallbackCtx*>(vdata);
+ ObsoleteMethodStackVisitor::UpdateObsoleteFrames(t,
+ data->allocator,
+ data->obsolete_methods,
+ data->obsolete_map);
+}
+
+// This creates any ArtMethod* structures needed for obsolete methods and ensures that the stack is
+// updated so they will be run.
+// TODO Rewrite so we can do this only once regardless of how many redefinitions there are.
+void Redefiner::ClassRedefinition::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) {
+ art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking");
+ art::mirror::ClassExt* ext = art_klass->GetExtData();
+ CHECK(ext->GetObsoleteMethods() != nullptr);
+ art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
+ // This holds pointers to the obsolete methods map fields which are updated as needed.
+ ObsoleteMap map(ext->GetObsoleteMethods(), ext->GetObsoleteDexCaches(), art_klass->GetDexCache());
+ CallbackCtx ctx(&map, linker->GetAllocatorForClassLoader(art_klass->GetClassLoader()));
+ // Add all the declared methods to the map
+ for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+ if (m.IsIntrinsic()) {
+ LOG(WARNING) << "Redefining intrinsic method " << m.PrettyMethod() << ". This may cause the "
+ << "unexpected use of the original definition of " << m.PrettyMethod() << "in "
+ << "methods that have already been compiled.";
+ }
+ // It is possible to simply filter out some methods where they cannot really become obsolete,
+ // such as native methods and keep their original (possibly optimized) implementations. We don't
+ // do this, however, since we would need to mark these functions (still in the classes
+ // declared_methods array) as obsolete so we will find the correct dex file to get meta-data
+ // from (for example about stack-frame size). Furthermore we would be unable to get some useful
+ // error checking from the interpreter which ensure we don't try to start executing obsolete
+ // methods.
+ ctx.obsolete_methods.insert(&m);
+ }
+ {
+ art::MutexLock mu(driver_->self_, *art::Locks::thread_list_lock_);
+ art::ThreadList* list = art::Runtime::Current()->GetThreadList();
+ list->ForEach(DoAllocateObsoleteMethodsCallback, static_cast<void*>(&ctx));
+ }
+}
+
+// Try and get the declared method. First try to get a virtual method then a direct method if that's
+// not found.
+static art::ArtMethod* FindMethod(art::Handle<art::mirror::Class> klass,
+ art::StringPiece name,
+ art::Signature sig) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK(!klass->IsProxyClass());
+ for (art::ArtMethod& m : klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize)) {
+ if (m.GetName() == name && m.GetSignature() == sig) {
+ return &m;
+ }
+ }
+ return nullptr;
+}
+
+bool Redefiner::ClassRedefinition::CheckSameMethods() {
+ art::StackHandleScope<1> hs(driver_->self_);
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
+ DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+
+ art::ClassDataItemIterator new_iter(*dex_file_,
+ dex_file_->GetClassData(dex_file_->GetClassDef(0)));
+
+ // Make sure we have the same number of methods.
+ uint32_t num_new_method = new_iter.NumVirtualMethods() + new_iter.NumDirectMethods();
+ uint32_t num_old_method = h_klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size();
+ if (num_new_method != num_old_method) {
+ bool bigger = num_new_method > num_old_method;
+ RecordFailure(bigger ? ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED)
+ : ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
+ StringPrintf("Total number of declared methods changed from %d to %d",
+ num_old_method, num_new_method));
+ return false;
+ }
+
+ // Skip all of the fields. We should have already checked this.
+ new_iter.SkipAllFields();
+ // Check each of the methods. NB we don't need to specifically check for removals since the 2 dex
+ // files have the same number of methods, which means there must be an equal amount of additions
+ // and removals.
+ for (; new_iter.HasNextVirtualMethod() || new_iter.HasNextDirectMethod(); new_iter.Next()) {
+ // Get the data on the method we are searching for
+ const art::DexFile::MethodId& new_method_id = dex_file_->GetMethodId(new_iter.GetMemberIndex());
+ const char* new_method_name = dex_file_->GetMethodName(new_method_id);
+ art::Signature new_method_signature = dex_file_->GetMethodSignature(new_method_id);
+ art::ArtMethod* old_method = FindMethod(h_klass, new_method_name, new_method_signature);
+ // If we got past the check for the same number of methods above that means there must be at
+ // least one added and one removed method. We will return the ADDED failure message since it is
+ // easier to get a useful error report for it.
+ if (old_method == nullptr) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
+ StringPrintf("Unknown method '%s' (sig: %s) was added!",
+ new_method_name,
+ new_method_signature.ToString().c_str()));
+ return false;
+ }
+ // Since direct methods have different flags than virtual ones (specifically direct methods must
+ // have kAccPrivate or kAccStatic or kAccConstructor flags) we can tell if a method changes from
+ // virtual to direct.
+ uint32_t new_flags = new_iter.GetMethodAccessFlags() & ~art::kAccPreviouslyWarm;
+ if (new_flags != (old_method->GetAccessFlags() & (art::kAccValidMethodFlags ^ art::kAccPreviouslyWarm))) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED),
+ StringPrintf("method '%s' (sig: %s) had different access flags",
+ new_method_name,
+ new_method_signature.ToString().c_str()));
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Redefiner::ClassRedefinition::CheckSameFields() {
+ art::StackHandleScope<1> hs(driver_->self_);
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
+ DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+ art::ClassDataItemIterator new_iter(*dex_file_,
+ dex_file_->GetClassData(dex_file_->GetClassDef(0)));
+ const art::DexFile& old_dex_file = h_klass->GetDexFile();
+ art::ClassDataItemIterator old_iter(old_dex_file,
+ old_dex_file.GetClassData(*h_klass->GetClassDef()));
+ // Instance and static fields can be differentiated by their flags so no need to check them
+ // separately.
+ while (new_iter.HasNextInstanceField() || new_iter.HasNextStaticField()) {
+ // Get the data on the method we are searching for
+ const art::DexFile::FieldId& new_field_id = dex_file_->GetFieldId(new_iter.GetMemberIndex());
+ const char* new_field_name = dex_file_->GetFieldName(new_field_id);
+ const char* new_field_type = dex_file_->GetFieldTypeDescriptor(new_field_id);
+
+ if (!(old_iter.HasNextInstanceField() || old_iter.HasNextStaticField())) {
+ // We are missing the old version of this method!
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+ StringPrintf("Unknown field '%s' (type: %s) added!",
+ new_field_name,
+ new_field_type));
+ return false;
+ }
+
+ const art::DexFile::FieldId& old_field_id = old_dex_file.GetFieldId(old_iter.GetMemberIndex());
+ const char* old_field_name = old_dex_file.GetFieldName(old_field_id);
+ const char* old_field_type = old_dex_file.GetFieldTypeDescriptor(old_field_id);
+
+ // Check name and type.
+ if (strcmp(old_field_name, new_field_name) != 0 ||
+ strcmp(old_field_type, new_field_type) != 0) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+ StringPrintf("Field changed from '%s' (sig: %s) to '%s' (sig: %s)!",
+ old_field_name,
+ old_field_type,
+ new_field_name,
+ new_field_type));
+ return false;
+ }
+
+ // Since static fields have different flags than instance ones (specifically static fields must
+ // have the kAccStatic flag) we can tell if a field changes from static to instance.
+ if (new_iter.GetFieldAccessFlags() != old_iter.GetFieldAccessFlags()) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+ StringPrintf("Field '%s' (sig: %s) had different access flags",
+ new_field_name,
+ new_field_type));
+ return false;
+ }
+
+ new_iter.Next();
+ old_iter.Next();
+ }
+ if (old_iter.HasNextInstanceField() || old_iter.HasNextStaticField()) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+ StringPrintf("field '%s' (sig: %s) is missing!",
+ old_dex_file.GetFieldName(old_dex_file.GetFieldId(
+ old_iter.GetMemberIndex())),
+ old_dex_file.GetFieldTypeDescriptor(old_dex_file.GetFieldId(
+ old_iter.GetMemberIndex()))));
+ return false;
+ }
+ return true;
+}
+
+bool Redefiner::ClassRedefinition::CheckClass() {
+ art::StackHandleScope<1> hs(driver_->self_);
+ // Easy check that only 1 class def is present.
+ if (dex_file_->NumClassDefs() != 1) {
+ RecordFailure(ERR(ILLEGAL_ARGUMENT),
+ StringPrintf("Expected 1 class def in dex file but found %d",
+ dex_file_->NumClassDefs()));
+ return false;
+ }
+ // Get the ClassDef from the new DexFile.
+ // Since the dex file has only a single class def the index is always 0.
+ const art::DexFile::ClassDef& def = dex_file_->GetClassDef(0);
+ // Get the class as it is now.
+ art::Handle<art::mirror::Class> current_class(hs.NewHandle(GetMirrorClass()));
+
+ // Check the access flags didn't change.
+ if (def.GetJavaAccessFlags() != (current_class->GetAccessFlags() & art::kAccValidClassFlags)) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED),
+ "Cannot change modifiers of class by redefinition");
+ return false;
+ }
+
+ // Check class name.
+ // These should have been checked by the dexfile verifier on load.
+ DCHECK_NE(def.class_idx_, art::dex::TypeIndex::Invalid()) << "Invalid type index";
+ const char* descriptor = dex_file_->StringByTypeIdx(def.class_idx_);
+ DCHECK(descriptor != nullptr) << "Invalid dex file structure!";
+ if (!current_class->DescriptorEquals(descriptor)) {
+ std::string storage;
+ RecordFailure(ERR(NAMES_DONT_MATCH),
+ StringPrintf("expected file to contain class called '%s' but found '%s'!",
+ current_class->GetDescriptor(&storage),
+ descriptor));
+ return false;
+ }
+ if (current_class->IsObjectClass()) {
+ if (def.superclass_idx_ != art::dex::TypeIndex::Invalid()) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Superclass added!");
+ return false;
+ }
+ } else {
+ const char* super_descriptor = dex_file_->StringByTypeIdx(def.superclass_idx_);
+ DCHECK(descriptor != nullptr) << "Invalid dex file structure!";
+ if (!current_class->GetSuperClass()->DescriptorEquals(super_descriptor)) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Superclass changed");
+ return false;
+ }
+ }
+ const art::DexFile::TypeList* interfaces = dex_file_->GetInterfacesList(def);
+ if (interfaces == nullptr) {
+ if (current_class->NumDirectInterfaces() != 0) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Interfaces added");
+ return false;
+ }
+ } else {
+ DCHECK(!current_class->IsProxyClass());
+ const art::DexFile::TypeList* current_interfaces = current_class->GetInterfaceTypeList();
+ if (current_interfaces == nullptr || current_interfaces->Size() != interfaces->Size()) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Interfaces added or removed");
+ return false;
+ }
+ // The order of interfaces is (barely) meaningful so we error if it changes.
+ const art::DexFile& orig_dex_file = current_class->GetDexFile();
+ for (uint32_t i = 0; i < interfaces->Size(); i++) {
+ if (strcmp(
+ dex_file_->StringByTypeIdx(interfaces->GetTypeItem(i).type_idx_),
+ orig_dex_file.StringByTypeIdx(current_interfaces->GetTypeItem(i).type_idx_)) != 0) {
+ RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
+ "Interfaces changed or re-ordered");
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool Redefiner::ClassRedefinition::CheckRedefinable() {
+ std::string err;
+ art::StackHandleScope<1> hs(driver_->self_);
+
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
+ jvmtiError res = Redefiner::GetClassRedefinitionError(h_klass, &err);
+ if (res != OK) {
+ RecordFailure(res, err);
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() {
+ return CheckRedefinable() &&
+ CheckClass() &&
+ CheckSameFields() &&
+ CheckSameMethods();
+}
+
+class RedefinitionDataIter;
+
+// A wrapper that lets us hold onto the arbitrary sized data needed for redefinitions in a
+// reasonably sane way. This adds no fields to the normal ObjectArray. By doing this we can avoid
+// having to deal with the fact that we need to hold an arbitrary number of references live.
+class RedefinitionDataHolder {
+ public:
+ enum DataSlot : int32_t {
+ kSlotSourceClassLoader = 0,
+ kSlotJavaDexFile = 1,
+ kSlotNewDexFileCookie = 2,
+ kSlotNewDexCache = 3,
+ kSlotMirrorClass = 4,
+ kSlotOrigDexFile = 5,
+ kSlotOldObsoleteMethods = 6,
+ kSlotOldDexCaches = 7,
+
+ // Must be last one.
+ kNumSlots = 8,
+ };
+
+ // This needs to have a HandleScope passed in that is capable of creating a new Handle without
+ // overflowing. Only one handle will be created. This object has a lifetime identical to that of
+ // the passed in handle-scope.
+ RedefinitionDataHolder(art::StackHandleScope<1>* hs,
+ art::Runtime* runtime,
+ art::Thread* self,
+ std::vector<Redefiner::ClassRedefinition>* redefinitions)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) :
+ arr_(
+ hs->NewHandle(
+ art::mirror::ObjectArray<art::mirror::Object>::Alloc(
+ self,
+ runtime->GetClassLinker()->GetClassRoot(art::ClassLinker::kObjectArrayClass),
+ redefinitions->size() * kNumSlots))),
+ redefinitions_(redefinitions) {}
+
+ bool IsNull() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return arr_.IsNull();
+ }
+
+ art::mirror::ClassLoader* GetSourceClassLoader(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::down_cast<art::mirror::ClassLoader*>(GetSlot(klass_index, kSlotSourceClassLoader));
+ }
+ art::mirror::Object* GetJavaDexFile(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return GetSlot(klass_index, kSlotJavaDexFile);
+ }
+ art::mirror::LongArray* GetNewDexFileCookie(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::down_cast<art::mirror::LongArray*>(GetSlot(klass_index, kSlotNewDexFileCookie));
+ }
+ art::mirror::DexCache* GetNewDexCache(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::down_cast<art::mirror::DexCache*>(GetSlot(klass_index, kSlotNewDexCache));
+ }
+ art::mirror::Class* GetMirrorClass(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass));
+ }
+
+ art::mirror::Object* GetOriginalDexFile(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::down_cast<art::mirror::Object*>(GetSlot(klass_index, kSlotOrigDexFile));
+ }
+
+ art::mirror::PointerArray* GetOldObsoleteMethods(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::down_cast<art::mirror::PointerArray*>(
+ GetSlot(klass_index, kSlotOldObsoleteMethods));
+ }
+
+ art::mirror::ObjectArray<art::mirror::DexCache>* GetOldDexCaches(jint klass_index) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return art::down_cast<art::mirror::ObjectArray<art::mirror::DexCache>*>(
+ GetSlot(klass_index, kSlotOldDexCaches));
+ }
+
+ void SetSourceClassLoader(jint klass_index, art::mirror::ClassLoader* loader)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotSourceClassLoader, loader);
+ }
+ void SetJavaDexFile(jint klass_index, art::mirror::Object* dexfile)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotJavaDexFile, dexfile);
+ }
+ void SetNewDexFileCookie(jint klass_index, art::mirror::LongArray* cookie)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotNewDexFileCookie, cookie);
+ }
+ void SetNewDexCache(jint klass_index, art::mirror::DexCache* cache)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotNewDexCache, cache);
+ }
+ void SetMirrorClass(jint klass_index, art::mirror::Class* klass)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotMirrorClass, klass);
+ }
+ void SetOriginalDexFile(jint klass_index, art::mirror::Object* bytes)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotOrigDexFile, bytes);
+ }
+ void SetOldObsoleteMethods(jint klass_index, art::mirror::PointerArray* methods)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotOldObsoleteMethods, methods);
+ }
+ void SetOldDexCaches(jint klass_index, art::mirror::ObjectArray<art::mirror::DexCache>* caches)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ SetSlot(klass_index, kSlotOldDexCaches, caches);
+ }
+
+ int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return arr_->GetLength() / kNumSlots;
+ }
+
+ std::vector<Redefiner::ClassRedefinition>* GetRedefinitions()
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return redefinitions_;
+ }
+
+ bool operator==(const RedefinitionDataHolder& other) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return arr_.Get() == other.arr_.Get();
+ }
+
+ bool operator!=(const RedefinitionDataHolder& other) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return !(*this == other);
+ }
+
+ RedefinitionDataIter begin() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ RedefinitionDataIter end() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+ mutable art::Handle<art::mirror::ObjectArray<art::mirror::Object>> arr_;
+ std::vector<Redefiner::ClassRedefinition>* redefinitions_;
+
+ art::mirror::Object* GetSlot(jint klass_index,
+ DataSlot slot) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK_LT(klass_index, Length());
+ return arr_->Get((kNumSlots * klass_index) + slot);
+ }
+
+ void SetSlot(jint klass_index,
+ DataSlot slot,
+ art::ObjPtr<art::mirror::Object> obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK(!art::Runtime::Current()->IsActiveTransaction());
+ DCHECK_LT(klass_index, Length());
+ arr_->Set<false>((kNumSlots * klass_index) + slot, obj);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder);
+};
+
+class RedefinitionDataIter {
+ public:
+ RedefinitionDataIter(int32_t idx, RedefinitionDataHolder& holder) : idx_(idx), holder_(holder) {}
+
+ RedefinitionDataIter(const RedefinitionDataIter&) = default;
+ RedefinitionDataIter(RedefinitionDataIter&&) = default;
+ RedefinitionDataIter& operator=(const RedefinitionDataIter&) = default;
+ RedefinitionDataIter& operator=(RedefinitionDataIter&&) = default;
+
+ bool operator==(const RedefinitionDataIter& other) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return idx_ == other.idx_ && holder_ == other.holder_;
+ }
+
+ bool operator!=(const RedefinitionDataIter& other) const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return !(*this == other);
+ }
+
+ RedefinitionDataIter operator++() { // Value after modification.
+ idx_++;
+ return *this;
+ }
+
+ RedefinitionDataIter operator++(int) {
+ RedefinitionDataIter temp = *this;
+ idx_++;
+ return temp;
+ }
+
+ RedefinitionDataIter operator+(ssize_t delta) const {
+ RedefinitionDataIter temp = *this;
+ temp += delta;
+ return temp;
+ }
+
+ RedefinitionDataIter& operator+=(ssize_t delta) {
+ idx_ += delta;
+ return *this;
+ }
+
+ Redefiner::ClassRedefinition& GetRedefinition() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return (*holder_.GetRedefinitions())[idx_];
+ }
+
+ RedefinitionDataHolder& GetHolder() {
+ return holder_;
+ }
+
+ art::mirror::ClassLoader* GetSourceClassLoader() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetSourceClassLoader(idx_);
+ }
+ art::mirror::Object* GetJavaDexFile() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetJavaDexFile(idx_);
+ }
+ art::mirror::LongArray* GetNewDexFileCookie() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetNewDexFileCookie(idx_);
+ }
+ art::mirror::DexCache* GetNewDexCache() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetNewDexCache(idx_);
+ }
+ art::mirror::Class* GetMirrorClass() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetMirrorClass(idx_);
+ }
+ art::mirror::Object* GetOriginalDexFile() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetOriginalDexFile(idx_);
+ }
+ art::mirror::PointerArray* GetOldObsoleteMethods() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetOldObsoleteMethods(idx_);
+ }
+ art::mirror::ObjectArray<art::mirror::DexCache>* GetOldDexCaches() const
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return holder_.GetOldDexCaches(idx_);
+ }
+
+ int32_t GetIndex() const {
+ return idx_;
+ }
+
+ void SetSourceClassLoader(art::mirror::ClassLoader* loader)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetSourceClassLoader(idx_, loader);
+ }
+ void SetJavaDexFile(art::mirror::Object* dexfile) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetJavaDexFile(idx_, dexfile);
+ }
+ void SetNewDexFileCookie(art::mirror::LongArray* cookie)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetNewDexFileCookie(idx_, cookie);
+ }
+ void SetNewDexCache(art::mirror::DexCache* cache) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetNewDexCache(idx_, cache);
+ }
+ void SetMirrorClass(art::mirror::Class* klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetMirrorClass(idx_, klass);
+ }
+ void SetOriginalDexFile(art::mirror::Object* bytes)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetOriginalDexFile(idx_, bytes);
+ }
+ void SetOldObsoleteMethods(art::mirror::PointerArray* methods)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetOldObsoleteMethods(idx_, methods);
+ }
+ void SetOldDexCaches(art::mirror::ObjectArray<art::mirror::DexCache>* caches)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ holder_.SetOldDexCaches(idx_, caches);
+ }
+
+ private:
+ int32_t idx_;
+ RedefinitionDataHolder& holder_;
+};
+
+RedefinitionDataIter RedefinitionDataHolder::begin() {
+ return RedefinitionDataIter(0, *this);
+}
+
+RedefinitionDataIter RedefinitionDataHolder::end() {
+ return RedefinitionDataIter(Length(), *this);
+}
+
+bool Redefiner::ClassRedefinition::CheckVerification(const RedefinitionDataIter& iter) {
+ DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+ art::StackHandleScope<2> hs(driver_->self_);
+ std::string error;
+ // TODO Make verification log level lower
+ art::verifier::FailureKind failure =
+ art::verifier::MethodVerifier::VerifyClass(driver_->self_,
+ dex_file_.get(),
+ hs.NewHandle(iter.GetNewDexCache()),
+ hs.NewHandle(GetClassLoader()),
+ dex_file_->GetClassDef(0), /*class_def*/
+ nullptr, /*compiler_callbacks*/
+ false, /*allow_soft_failures*/
+ /*log_level*/
+ art::verifier::HardFailLogMode::kLogWarning,
+ &error);
+ bool passes = failure == art::verifier::FailureKind::kNoFailure;
+ if (!passes) {
+ RecordFailure(ERR(FAILS_VERIFICATION), "Failed to verify class. Error was: " + error);
+ }
+ return passes;
+}
+
+// Looks through the previously allocated cookies to see if we need to update them with another new
+// dexfile. This is so that even if multiple classes with the same classloader are redefined at
+// once they are all added to the classloader.
+bool Redefiner::ClassRedefinition::AllocateAndRememberNewDexFileCookie(
+ art::Handle<art::mirror::ClassLoader> source_class_loader,
+ art::Handle<art::mirror::Object> dex_file_obj,
+ /*out*/RedefinitionDataIter* cur_data) {
+ art::StackHandleScope<2> hs(driver_->self_);
+ art::MutableHandle<art::mirror::LongArray> old_cookie(
+ hs.NewHandle<art::mirror::LongArray>(nullptr));
+ bool has_older_cookie = false;
+ // See if we already have a cookie that a previous redefinition got from the same classloader.
+ for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
+ if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
+ // Since every instance of this classloader should have the same cookie associated with it we
+ // can stop looking here.
+ has_older_cookie = true;
+ old_cookie.Assign(old_data.GetNewDexFileCookie());
+ break;
+ }
+ }
+ if (old_cookie.IsNull()) {
+ // No older cookie. Get it directly from the dex_file_obj
+ // We should not have seen this classloader elsewhere.
+ CHECK(!has_older_cookie);
+ old_cookie.Assign(ClassLoaderHelper::GetDexFileCookie(dex_file_obj));
+ }
+ // Use the old cookie to generate the new one with the new DexFile* added in.
+ art::Handle<art::mirror::LongArray>
+ new_cookie(hs.NewHandle(ClassLoaderHelper::AllocateNewDexFileCookie(driver_->self_,
+ old_cookie,
+ dex_file_.get())));
+ // Make sure the allocation worked.
+ if (new_cookie.IsNull()) {
+ return false;
+ }
+
+ // Save the cookie.
+ cur_data->SetNewDexFileCookie(new_cookie.Get());
+ // If there are other copies of this same classloader we need to make sure that we all have the
+ // same cookie.
+ if (has_older_cookie) {
+ for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
+ // We will let the GC take care of the cookie we allocated for this one.
+ if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
+ old_data.SetNewDexFileCookie(new_cookie.Get());
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
+ /*out*/RedefinitionDataIter* cur_data) {
+ art::ScopedObjectAccessUnchecked soa(driver_->self_);
+ art::StackHandleScope<2> hs(driver_->self_);
+ cur_data->SetMirrorClass(GetMirrorClass());
+ // This shouldn't allocate
+ art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
+ // The bootclasspath is handled specially so it doesn't have a j.l.DexFile.
+ if (!art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
+ cur_data->SetSourceClassLoader(loader.Get());
+ art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(
+ ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader)));
+ cur_data->SetJavaDexFile(dex_file_obj.Get());
+ if (dex_file_obj == nullptr) {
+ RecordFailure(ERR(INTERNAL), "Unable to find dex file!");
+ return false;
+ }
+ // Allocate the new dex file cookie.
+ if (!AllocateAndRememberNewDexFileCookie(loader, dex_file_obj, cur_data)) {
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader");
+ return false;
+ }
+ }
+ cur_data->SetNewDexCache(CreateNewDexCache(loader));
+ if (cur_data->GetNewDexCache() == nullptr) {
+ driver_->self_->AssertPendingException();
+ driver_->self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache");
+ return false;
+ }
+
+ // We won't always need to set this field.
+ cur_data->SetOriginalDexFile(AllocateOrGetOriginalDexFile());
+ if (cur_data->GetOriginalDexFile() == nullptr) {
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file");
+ return false;
+ }
+ return true;
+}
+
+void Redefiner::ClassRedefinition::UnregisterJvmtiBreakpoints() {
+ BreakpointUtil::RemoveBreakpointsInClass(driver_->env_, GetMirrorClass());
+}
+
+void Redefiner::ClassRedefinition::UnregisterBreakpoints() {
+ DCHECK(art::Dbg::IsDebuggerActive());
+ art::JDWP::JdwpState* state = art::Dbg::GetJdwpState();
+ if (state != nullptr) {
+ state->UnregisterLocationEventsOnClass(GetMirrorClass());
+ }
+}
+
+void Redefiner::UnregisterAllBreakpoints() {
+ if (LIKELY(!art::Dbg::IsDebuggerActive())) {
+ return;
+ }
+ for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+ redef.UnregisterBreakpoints();
+ }
+}
+
+bool Redefiner::CheckAllRedefinitionAreValid() {
+ for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+ if (!redef.CheckRedefinitionIsValid()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void Redefiner::RestoreObsoleteMethodMapsIfUnneeded(RedefinitionDataHolder& holder) {
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ data.GetRedefinition().RestoreObsoleteMethodMapsIfUnneeded(&data);
+ }
+}
+
+bool Redefiner::EnsureAllClassAllocationsFinished(RedefinitionDataHolder& holder) {
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ if (!data.GetRedefinition().EnsureClassAllocationsFinished(&data)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) {
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ // Allocate the data this redefinition requires.
+ if (!data.GetRedefinition().FinishRemainingAllocations(&data)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void Redefiner::ClassRedefinition::ReleaseDexFile() {
+ dex_file_.release();
+}
+
+void Redefiner::ReleaseAllDexFiles() {
+ for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+ redef.ReleaseDexFile();
+ }
+}
+
+bool Redefiner::CheckAllClassesAreVerified(RedefinitionDataHolder& holder) {
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ if (!data.GetRedefinition().CheckVerification(data)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+class ScopedDisableConcurrentAndMovingGc {
+ public:
+ ScopedDisableConcurrentAndMovingGc(art::gc::Heap* heap, art::Thread* self)
+ : heap_(heap), self_(self) {
+ if (heap_->IsGcConcurrentAndMoving()) {
+ heap_->IncrementDisableMovingGC(self_);
+ }
+ }
+
+ ~ScopedDisableConcurrentAndMovingGc() {
+ if (heap_->IsGcConcurrentAndMoving()) {
+ heap_->DecrementDisableMovingGC(self_);
+ }
+ }
+ private:
+ art::gc::Heap* heap_;
+ art::Thread* self_;
+};
+
+jvmtiError Redefiner::Run() {
+ art::StackHandleScope<1> hs(self_);
+ // Allocate an array to hold onto all java temporary objects associated with this redefinition.
+ // We will let this be collected after the end of this function.
+ RedefinitionDataHolder holder(&hs, runtime_, self_, &redefinitions_);
+ if (holder.IsNull()) {
+ self_->AssertPendingOOMException();
+ self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate storage for temporaries");
+ return result_;
+ }
+
+ // First we just allocate the ClassExt and its fields that we need. These can be updated
+ // atomically without any issues (since we allocate the map arrays as empty) so we don't bother
+ // doing a try loop. The other allocations we need to ensure that nothing has changed in the time
+ // between allocating them and pausing all threads before we can update them so we need to do a
+ // try loop.
+ if (!CheckAllRedefinitionAreValid() ||
+ !EnsureAllClassAllocationsFinished(holder) ||
+ !FinishAllRemainingAllocations(holder) ||
+ !CheckAllClassesAreVerified(holder)) {
+ return result_;
+ }
+
+ // At this point we can no longer fail without corrupting the runtime state.
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ art::ClassLinker* cl = runtime_->GetClassLinker();
+ cl->RegisterExistingDexCache(data.GetNewDexCache(), data.GetSourceClassLoader());
+ if (data.GetSourceClassLoader() == nullptr) {
+ cl->AppendToBootClassPath(self_, data.GetRedefinition().GetDexFile());
+ }
+ }
+ UnregisterAllBreakpoints();
+
+ // Disable GC and wait for it to be done if we are a moving GC. This is fine since we are done
+ // allocating so no deadlocks.
+ ScopedDisableConcurrentAndMovingGc sdcamgc(runtime_->GetHeap(), self_);
+
+ // Do transition to final suspension
+ // TODO We might want to give this its own suspended state!
+ // TODO This isn't right. We need to change state without any chance of suspend ideally!
+ art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative);
+ art::ScopedSuspendAll ssa("Final installation of redefined Classes!", /*long_suspend*/true);
+ for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+ art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition");
+ ClassRedefinition& redef = data.GetRedefinition();
+ if (data.GetSourceClassLoader() != nullptr) {
+ ClassLoaderHelper::UpdateJavaDexFile(data.GetJavaDexFile(), data.GetNewDexFileCookie());
+ }
+ art::mirror::Class* klass = data.GetMirrorClass();
+ // TODO Rewrite so we don't do a stack walk for each and every class.
+ redef.FindAndAllocateObsoleteMethods(klass);
+ redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFile());
+ redef.UnregisterJvmtiBreakpoints();
+ }
+ RestoreObsoleteMethodMapsIfUnneeded(holder);
+ // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any
+ // are, force a full-world deoptimization before finishing redefinition. If we don't do this then
+ // methods that have been jitted prior to the current redefinition being applied might continue
+ // to use the old versions of the intrinsics!
+ // TODO Do the dex_file release at a more reasonable place. This works but it muddles who really
+ // owns the DexFile and when ownership is transferred.
+ ReleaseAllDexFiles();
+ return OK;
+}
+
+void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
+ art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+ const art::DexFile::ClassDef& class_def) {
+ art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
+ art::PointerSize image_pointer_size = linker->GetImagePointerSize();
+ const art::DexFile::TypeId& declaring_class_id = dex_file_->GetTypeId(class_def.class_idx_);
+ const art::DexFile& old_dex_file = mclass->GetDexFile();
+ // Update methods.
+ for (art::ArtMethod& method : mclass->GetDeclaredMethods(image_pointer_size)) {
+ const art::DexFile::StringId* new_name_id = dex_file_->FindStringId(method.GetName());
+ art::dex::TypeIndex method_return_idx =
+ dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(method.GetReturnTypeDescriptor()));
+ const auto* old_type_list = method.GetParameterTypeList();
+ std::vector<art::dex::TypeIndex> new_type_list;
+ for (uint32_t i = 0; old_type_list != nullptr && i < old_type_list->Size(); i++) {
+ new_type_list.push_back(
+ dex_file_->GetIndexForTypeId(
+ *dex_file_->FindTypeId(
+ old_dex_file.GetTypeDescriptor(
+ old_dex_file.GetTypeId(
+ old_type_list->GetTypeItem(i).type_idx_)))));
+ }
+ const art::DexFile::ProtoId* proto_id = dex_file_->FindProtoId(method_return_idx,
+ new_type_list);
+ CHECK(proto_id != nullptr || old_type_list == nullptr);
+ const art::DexFile::MethodId* method_id = dex_file_->FindMethodId(declaring_class_id,
+ *new_name_id,
+ *proto_id);
+ CHECK(method_id != nullptr);
+ uint32_t dex_method_idx = dex_file_->GetIndexForMethodId(*method_id);
+ method.SetDexMethodIndex(dex_method_idx);
+ linker->SetEntryPointsToInterpreter(&method);
+ method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx));
+ method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size);
+ // Clear all the intrinsics related flags.
+ method.ClearAccessFlags(art::kAccIntrinsic | (~art::kAccFlagsNotUsedByIntrinsic));
+ // Notify the jit that this method is redefined.
+ art::jit::Jit* jit = driver_->runtime_->GetJit();
+ if (jit != nullptr) {
+ jit->GetCodeCache()->NotifyMethodRedefined(&method);
+ }
+ }
+}
+
+void Redefiner::ClassRedefinition::UpdateFields(art::ObjPtr<art::mirror::Class> mclass) {
+ // TODO The IFields & SFields pointers should be combined like the methods_ arrays were.
+ for (auto fields_iter : {mclass->GetIFields(), mclass->GetSFields()}) {
+ for (art::ArtField& field : fields_iter) {
+ std::string declaring_class_name;
+ const art::DexFile::TypeId* new_declaring_id =
+ dex_file_->FindTypeId(field.GetDeclaringClass()->GetDescriptor(&declaring_class_name));
+ const art::DexFile::StringId* new_name_id = dex_file_->FindStringId(field.GetName());
+ const art::DexFile::TypeId* new_type_id = dex_file_->FindTypeId(field.GetTypeDescriptor());
+ CHECK(new_name_id != nullptr && new_type_id != nullptr && new_declaring_id != nullptr);
+ const art::DexFile::FieldId* new_field_id =
+ dex_file_->FindFieldId(*new_declaring_id, *new_name_id, *new_type_id);
+ CHECK(new_field_id != nullptr);
+ // We only need to update the index since the other data in the ArtField cannot be updated.
+ field.SetDexFieldIndex(dex_file_->GetIndexForFieldId(*new_field_id));
+ }
+ }
+}
+
+// Performs updates to class that will allow us to verify it.
+void Redefiner::ClassRedefinition::UpdateClass(
+ art::ObjPtr<art::mirror::Class> mclass,
+ art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+ art::ObjPtr<art::mirror::Object> original_dex_file) {
+ DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+ const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0);
+ UpdateMethods(mclass, new_dex_cache, class_def);
+ UpdateFields(mclass);
+
+ // Update the class fields.
+ // Need to update class last since the ArtMethod gets its DexFile from the class (which is needed
+ // to call GetReturnTypeDescriptor and GetParameterTypeList above).
+ mclass->SetDexCache(new_dex_cache.Ptr());
+ mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(class_def));
+ mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str())));
+ art::ObjPtr<art::mirror::ClassExt> ext(mclass->GetExtData());
+ CHECK(!ext.IsNull());
+ ext->SetOriginalDexFile(original_dex_file);
+}
+
+// Restores the old obsolete methods maps if it turns out they weren't needed (ie there were no new
+// obsolete methods).
+void Redefiner::ClassRedefinition::RestoreObsoleteMethodMapsIfUnneeded(
+ const RedefinitionDataIter* cur_data) {
+ art::mirror::Class* klass = GetMirrorClass();
+ art::mirror::ClassExt* ext = klass->GetExtData();
+ art::mirror::PointerArray* methods = ext->GetObsoleteMethods();
+ art::mirror::PointerArray* old_methods = cur_data->GetOldObsoleteMethods();
+ int32_t old_length = old_methods == nullptr ? 0 : old_methods->GetLength();
+ int32_t expected_length =
+ old_length + klass->NumDirectMethods() + klass->NumDeclaredVirtualMethods();
+ // Check to make sure we are only undoing this one.
+ if (expected_length == methods->GetLength()) {
+ for (int32_t i = 0; i < expected_length; i++) {
+ art::ArtMethod* expected = nullptr;
+ if (i < old_length) {
+ expected = old_methods->GetElementPtrSize<art::ArtMethod*>(i, art::kRuntimePointerSize);
+ }
+ if (methods->GetElementPtrSize<art::ArtMethod*>(i, art::kRuntimePointerSize) != expected) {
+ // We actually have some new obsolete methods. Just abort since we cannot safely shrink the
+ // obsolete methods array.
+ return;
+ }
+ }
+ // No new obsolete methods! We can get rid of the maps.
+ ext->SetObsoleteArrays(cur_data->GetOldObsoleteMethods(), cur_data->GetOldDexCaches());
+ }
+}
+
+// This function does all (java) allocations we need to do for the Class being redefined.
+// TODO Change this name maybe?
+bool Redefiner::ClassRedefinition::EnsureClassAllocationsFinished(
+ /*out*/RedefinitionDataIter* cur_data) {
+ art::StackHandleScope<2> hs(driver_->self_);
+ art::Handle<art::mirror::Class> klass(hs.NewHandle(
+ driver_->self_->DecodeJObject(klass_)->AsClass()));
+ if (klass == nullptr) {
+ RecordFailure(ERR(INVALID_CLASS), "Unable to decode class argument!");
+ return false;
+ }
+ // Allocate the classExt
+ art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(driver_->self_)));
+ if (ext == nullptr) {
+ // No memory. Clear exception (it's not useful) and return error.
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate ClassExt");
+ return false;
+ }
+ // First save the old values of the 2 arrays that make up the obsolete methods maps. Then
+ // allocate the 2 arrays that make up the obsolete methods map. Since the contents of the arrays
+ // are only modified when all threads (other than the modifying one) are suspended we don't need
+ // to worry about missing the unsyncronized writes to the array. We do synchronize when setting it
+ // however, since that can happen at any time.
+ cur_data->SetOldObsoleteMethods(ext->GetObsoleteMethods());
+ cur_data->SetOldDexCaches(ext->GetObsoleteDexCaches());
+ if (!ext->ExtendObsoleteArrays(
+ driver_->self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) {
+ // OOM. Clear exception and return error.
+ driver_->self_->AssertPendingOOMException();
+ driver_->self_->ClearException();
+ RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate/extend obsolete methods map");
+ return false;
+ }
+ return true;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
new file mode 100644
index 0000000..984f922
--- /dev/null
+++ b/openjdkjvmti/ti_redefine.h
@@ -0,0 +1,268 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_REDEFINE_H_
+#define ART_OPENJDKJVMTI_TI_REDEFINE_H_
+
+#include <string>
+
+#include <jni.h>
+
+#include "art_jvmti.h"
+#include "art_method.h"
+#include "base/array_ref.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "gc_root-inl.h"
+#include "globals.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti.h"
+#include "linear_alloc.h"
+#include "mem_map.h"
+#include "mirror/array-inl.h"
+#include "mirror/array.h"
+#include "mirror/class-inl.h"
+#include "mirror/class.h"
+#include "mirror/class_loader-inl.h"
+#include "mirror/string-inl.h"
+#include "oat_file.h"
+#include "obj_ptr.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread_list.h"
+#include "ti_class_definition.h"
+#include "transform.h"
+#include "utf.h"
+#include "utils/dex_cache_arrays_layout-inl.h"
+
+namespace openjdkjvmti {
+
+class RedefinitionDataHolder;
+class RedefinitionDataIter;
+
+// Class that can redefine a single class's methods.
+class Redefiner {
+ public:
+ // Redefine the given classes with the given dex data. Note this function does not take ownership
+ // of the dex_data pointers. It is not used after this call however and may be freed if desired.
+ // The caller is responsible for freeing it. The runtime makes its own copy of the data. This
+ // function does not call the transformation events.
+ static jvmtiError RedefineClassesDirect(ArtJvmTiEnv* env,
+ art::Runtime* runtime,
+ art::Thread* self,
+ const std::vector<ArtClassDefinition>& definitions,
+ /*out*/std::string* error_msg);
+
+ // Redefine the given classes with the given dex data. Note this function does not take ownership
+ // of the dex_data pointers. It is not used after this call however and may be freed if desired.
+ // The caller is responsible for freeing it. The runtime makes its own copy of the data.
+ static jvmtiError RedefineClasses(ArtJvmTiEnv* env,
+ EventHandler* event_handler,
+ art::Runtime* runtime,
+ art::Thread* self,
+ jint class_count,
+ const jvmtiClassDefinition* definitions,
+ /*out*/std::string* error_msg);
+
+ static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
+
+ static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location,
+ art::ArrayRef<const unsigned char> data,
+ std::string* error_msg);
+
+ private:
+ class ClassRedefinition {
+ public:
+ ClassRedefinition(Redefiner* driver,
+ jclass klass,
+ const art::DexFile* redefined_dex_file,
+ const char* class_sig,
+ art::ArrayRef<const unsigned char> orig_dex_file)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // NO_THREAD_SAFETY_ANALYSIS so we can unlock the class in the destructor.
+ ~ClassRedefinition() NO_THREAD_SAFETY_ANALYSIS;
+
+ // Move constructor so we can put these into a vector.
+ ClassRedefinition(ClassRedefinition&& other)
+ : driver_(other.driver_),
+ klass_(other.klass_),
+ dex_file_(std::move(other.dex_file_)),
+ class_sig_(std::move(other.class_sig_)),
+ original_dex_file_(other.original_dex_file_) {
+ other.driver_ = nullptr;
+ }
+
+ art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ const art::DexFile& GetDexFile() {
+ return *dex_file_;
+ }
+
+ art::mirror::DexCache* CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // This may return nullptr with a OOME pending if allocation fails.
+ art::mirror::Object* AllocateOrGetOriginalDexFile()
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ void RecordFailure(jvmtiError e, const std::string& err) {
+ driver_->RecordFailure(e, class_sig_, err);
+ }
+
+ bool FinishRemainingAllocations(/*out*/RedefinitionDataIter* cur_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ bool AllocateAndRememberNewDexFileCookie(
+ art::Handle<art::mirror::ClassLoader> source_class_loader,
+ art::Handle<art::mirror::Object> dex_file_obj,
+ /*out*/RedefinitionDataIter* cur_data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
+ REQUIRES(art::Locks::mutator_lock_);
+
+ // Checks that the dex file contains only the single expected class and that the top-level class
+ // data has not been modified in an incompatible manner.
+ bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // Checks that the contained class can be successfully verified.
+ bool CheckVerification(const RedefinitionDataIter& holder)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // Preallocates all needed allocations in klass so that we can pause execution safely.
+ bool EnsureClassAllocationsFinished(/*out*/RedefinitionDataIter* data)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // This will check that no constraints are violated (more than 1 class in dex file, any changes
+ // in number/declaration of methods & fields, changes in access flags, etc.)
+ bool CheckRedefinitionIsValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // Checks that the class can even be redefined.
+ bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // Checks that the dex file does not add/remove methods, or change their modifiers or types.
+ bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ // Checks that the dex file does not modify fields types or modifiers.
+ bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+ art::ObjPtr<art::mirror::LongArray> new_cookie)
+ REQUIRES(art::Locks::mutator_lock_);
+
+ void UpdateFields(art::ObjPtr<art::mirror::Class> mclass)
+ REQUIRES(art::Locks::mutator_lock_);
+
+ void UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
+ art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+ const art::DexFile::ClassDef& class_def)
+ REQUIRES(art::Locks::mutator_lock_);
+
+ void UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
+ art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+ art::ObjPtr<art::mirror::Object> original_dex_file)
+ REQUIRES(art::Locks::mutator_lock_);
+
+ void RestoreObsoleteMethodMapsIfUnneeded(const RedefinitionDataIter* cur_data)
+ REQUIRES(art::Locks::mutator_lock_);
+
+ void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ void UnregisterBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ // This should be done with all threads suspended.
+ void UnregisterJvmtiBreakpoints() REQUIRES(art::Locks::mutator_lock_);
+
+ private:
+ Redefiner* driver_;
+ jclass klass_;
+ std::unique_ptr<const art::DexFile> dex_file_;
+ std::string class_sig_;
+ art::ArrayRef<const unsigned char> original_dex_file_;
+ };
+
+ ArtJvmTiEnv* env_;
+ jvmtiError result_;
+ art::Runtime* runtime_;
+ art::Thread* self_;
+ std::vector<ClassRedefinition> redefinitions_;
+ // Kept as a jclass since we have weird run-state changes that make keeping it around as a
+ // mirror::Class difficult and confusing.
+ std::string* error_msg_;
+
+ Redefiner(ArtJvmTiEnv* env,
+ art::Runtime* runtime,
+ art::Thread* self,
+ std::string* error_msg)
+ : env_(env),
+ result_(ERR(INTERNAL)),
+ runtime_(runtime),
+ self_(self),
+ redefinitions_(),
+ error_msg_(error_msg) { }
+
+ jvmtiError AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ static jvmtiError GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
+ /*out*/std::string* error_msg)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ bool CheckAllRedefinitionAreValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ bool CheckAllClassesAreVerified(RedefinitionDataHolder& holder)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+ bool EnsureAllClassAllocationsFinished(RedefinitionDataHolder& holder)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+ bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void UnregisterAllBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ // Restores the old obsolete methods maps if it turns out they weren't needed (ie there were no
+ // new obsolete methods).
+ void RestoreObsoleteMethodMapsIfUnneeded(RedefinitionDataHolder& holder)
+ REQUIRES(art::Locks::mutator_lock_);
+
+ void RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg);
+ void RecordFailure(jvmtiError result, const std::string& error_msg) {
+ RecordFailure(result, "NO CLASS", error_msg);
+ }
+
+ friend struct CallbackCtx;
+ friend class RedefinitionDataHolder;
+ friend class RedefinitionDataIter;
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_REDEFINE_H_
diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
new file mode 100644
index 0000000..25bc5d6
--- /dev/null
+++ b/openjdkjvmti/ti_search.cc
@@ -0,0 +1,296 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_search.h"
+
+#include "jni.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/enums.h"
+#include "base/macros.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/object.h"
+#include "mirror/string.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "obj_ptr-inl.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+static std::vector<std::string> gSystemOnloadSegments;
+
+static art::ObjPtr<art::mirror::Object> GetSystemProperties(art::Thread* self,
+ art::ClassLinker* class_linker)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::ObjPtr<art::mirror::Class> system_class =
+ class_linker->LookupClass(self, "Ljava/lang/System;", nullptr);
+ DCHECK(system_class != nullptr);
+ DCHECK(system_class->IsInitialized());
+
+ art::ArtField* props_field =
+ system_class->FindDeclaredStaticField("props", "Ljava/util/Properties;");
+ DCHECK(props_field != nullptr);
+
+ art::ObjPtr<art::mirror::Object> props_obj = props_field->GetObject(system_class);
+ DCHECK(props_obj != nullptr);
+
+ return props_obj;
+}
+
+static void Update() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (gSystemOnloadSegments.empty()) {
+ return;
+ }
+
+ // In the on-load phase we have to modify java.class.path to influence the system classloader.
+ // As this is an unmodifiable system property, we have to access the "defaults" field.
+ art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
+ DCHECK(class_linker != nullptr);
+ art::Thread* self = art::Thread::Current();
+
+ // Prepare: collect classes, fields and methods.
+ art::ObjPtr<art::mirror::Class> properties_class =
+ class_linker->LookupClass(self, "Ljava/util/Properties;", nullptr);
+ DCHECK(properties_class != nullptr);
+
+ ScopedLocalRef<jobject> defaults_jobj(self->GetJniEnv(), nullptr);
+ {
+ art::ObjPtr<art::mirror::Object> props_obj = GetSystemProperties(self, class_linker);
+
+ art::ArtField* defaults_field =
+ properties_class->FindDeclaredInstanceField("defaults", "Ljava/util/Properties;");
+ DCHECK(defaults_field != nullptr);
+
+ art::ObjPtr<art::mirror::Object> defaults_obj = defaults_field->GetObject(props_obj);
+ DCHECK(defaults_obj != nullptr);
+ defaults_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(defaults_obj));
+ }
+
+ art::ArtMethod* get_property =
+ properties_class->FindClassMethod(
+ "getProperty",
+ "(Ljava/lang/String;)Ljava/lang/String;",
+ art::kRuntimePointerSize);
+ DCHECK(get_property != nullptr);
+ DCHECK(!get_property->IsDirect());
+ DCHECK(get_property->GetDeclaringClass() == properties_class);
+ art::ArtMethod* set_property =
+ properties_class->FindClassMethod(
+ "setProperty",
+ "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;",
+ art::kRuntimePointerSize);
+ DCHECK(set_property != nullptr);
+ DCHECK(!set_property->IsDirect());
+ DCHECK(set_property->GetDeclaringClass() == properties_class);
+
+ // This is an allocation. Do this late to avoid the need for handles.
+ ScopedLocalRef<jobject> cp_jobj(self->GetJniEnv(), nullptr);
+ {
+ art::ObjPtr<art::mirror::Object> cp_key =
+ art::mirror::String::AllocFromModifiedUtf8(self, "java.class.path");
+ if (cp_key == nullptr) {
+ self->AssertPendingOOMException();
+ self->ClearException();
+ return;
+ }
+ cp_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(cp_key));
+ }
+
+ // OK, now get the current value.
+ std::string str_value;
+ {
+ ScopedLocalRef<jobject> old_value(self->GetJniEnv(),
+ self->GetJniEnv()->CallObjectMethod(
+ defaults_jobj.get(),
+ art::jni::EncodeArtMethod(get_property),
+ cp_jobj.get()));
+ DCHECK(old_value.get() != nullptr);
+
+ str_value = self->DecodeJObject(old_value.get())->AsString()->ToModifiedUtf8();
+ self->GetJniEnv()->DeleteLocalRef(old_value.release());
+ }
+
+ // Update the value by appending the new segments.
+ for (const std::string& segment : gSystemOnloadSegments) {
+ if (!str_value.empty()) {
+ str_value += ":";
+ }
+ str_value += segment;
+ }
+ gSystemOnloadSegments.clear();
+
+ // Create the new value object.
+ ScopedLocalRef<jobject> new_val_jobj(self->GetJniEnv(), nullptr);
+ {
+ art::ObjPtr<art::mirror::Object> new_value =
+ art::mirror::String::AllocFromModifiedUtf8(self, str_value.c_str());
+ if (new_value == nullptr) {
+ self->AssertPendingOOMException();
+ self->ClearException();
+ return;
+ }
+
+ new_val_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(new_value));
+ }
+
+ // Write to the defaults.
+ ScopedLocalRef<jobject> res_obj(self->GetJniEnv(),
+ self->GetJniEnv()->CallObjectMethod(defaults_jobj.get(),
+ art::jni::EncodeArtMethod(set_property),
+ cp_jobj.get(),
+ new_val_jobj.get()));
+ if (self->IsExceptionPending()) {
+ self->ClearException();
+ return;
+ }
+}
+
+struct SearchCallback : public art::RuntimePhaseCallback {
+ void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (phase == RuntimePhase::kStart) {
+ // It's time to update the system properties.
+ Update();
+ }
+ }
+};
+
+static SearchCallback gSearchCallback;
+
+void SearchUtil::Register() {
+ art::Runtime* runtime = art::Runtime::Current();
+
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add search callback");
+ runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gSearchCallback);
+}
+
+void SearchUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove search callback");
+ art::Runtime* runtime = art::Runtime::Current();
+ runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gSearchCallback);
+}
+
+jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ const char* segment) {
+ art::Runtime* current = art::Runtime::Current();
+ if (current == nullptr) {
+ return ERR(WRONG_PHASE);
+ }
+ if (current->GetClassLinker() == nullptr) {
+ return ERR(WRONG_PHASE);
+ }
+ if (segment == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ std::string error_msg;
+ std::vector<std::unique_ptr<const art::DexFile>> dex_files;
+ if (!art::DexFile::Open(segment, segment, true, &error_msg, &dex_files)) {
+ LOG(WARNING) << "Could not open " << segment << " for boot classpath extension: " << error_msg;
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ for (std::unique_ptr<const art::DexFile>& dex_file : dex_files) {
+ current->GetClassLinker()->AppendToBootClassPath(art::Thread::Current(), *dex_file.release());
+ }
+
+ return ERR(NONE);
+}
+
+jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED,
+ const char* segment) {
+ if (segment == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked();
+
+ if (phase == jvmtiPhase::JVMTI_PHASE_ONLOAD) {
+ // We could try and see whether it is a valid path. We could also try to allocate Java
+ // objects to avoid later OOME.
+ gSystemOnloadSegments.push_back(segment);
+ return ERR(NONE);
+ } else if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) {
+ return ERR(WRONG_PHASE);
+ }
+
+ jobject sys_class_loader = art::Runtime::Current()->GetSystemClassLoader();
+ if (sys_class_loader == nullptr) {
+ // This is unexpected.
+ return ERR(INTERNAL);
+ }
+
+ // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside,
+ // exceptions are swallowed.
+
+ art::Thread* self = art::Thread::Current();
+ JNIEnv* env = self->GetJniEnv();
+ if (!env->IsInstanceOf(sys_class_loader,
+ art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
+ return ERR(INTERNAL);
+ }
+
+ jmethodID add_dex_path_id = env->GetMethodID(
+ art::WellKnownClasses::dalvik_system_BaseDexClassLoader,
+ "addDexPath",
+ "(Ljava/lang/String;)V");
+ if (add_dex_path_id == nullptr) {
+ return ERR(INTERNAL);
+ }
+
+ ScopedLocalRef<jstring> dex_path(env, env->NewStringUTF(segment));
+ if (dex_path.get() == nullptr) {
+ return ERR(INTERNAL);
+ }
+ env->CallVoidMethod(sys_class_loader, add_dex_path_id, dex_path.get());
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ return ERR(NONE);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_search.h b/openjdkjvmti/ti_search.h
new file mode 100644
index 0000000..81a28cc
--- /dev/null
+++ b/openjdkjvmti/ti_search.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_SEARCH_H_
+#define ART_OPENJDKJVMTI_TI_SEARCH_H_
+
+#include <vector>
+
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class SearchUtil {
+ public:
+ static void Register();
+ static void Unregister();
+
+ static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment);
+
+ static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_SEARCH_H_
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
new file mode 100644
index 0000000..ff2de8d
--- /dev/null
+++ b/openjdkjvmti/ti_stack.cc
@@ -0,0 +1,827 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_stack.h"
+
+#include <algorithm>
+#include <list>
+#include <unordered_map>
+#include <vector>
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "barrier.h"
+#include "base/bit_utils.h"
+#include "base/enums.h"
+#include "base/mutex.h"
+#include "dex_file.h"
+#include "dex_file_annotations.h"
+#include "handle_scope-inl.h"
+#include "jni_env_ext.h"
+#include "jni_internal.h"
+#include "mirror/class.h"
+#include "mirror/dex_cache.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "thread_pool.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+template <typename FrameFn>
+struct GetStackTraceVisitor : public art::StackVisitor {
+ GetStackTraceVisitor(art::Thread* thread_in,
+ size_t start_,
+ size_t stop_,
+ FrameFn fn_)
+ : StackVisitor(thread_in, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ fn(fn_),
+ start(start_),
+ stop(stop_) {}
+ GetStackTraceVisitor(const GetStackTraceVisitor&) = default;
+ GetStackTraceVisitor(GetStackTraceVisitor&&) = default;
+
+ bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::ArtMethod* m = GetMethod();
+ if (m->IsRuntimeMethod()) {
+ return true;
+ }
+
+ if (start == 0) {
+ m = m->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+ jmethodID id = art::jni::EncodeArtMethod(m);
+
+ uint32_t dex_pc = GetDexPc(false);
+ jlong dex_location = (dex_pc == art::DexFile::kDexNoIndex) ? -1 : static_cast<jlong>(dex_pc);
+
+ jvmtiFrameInfo info = { id, dex_location };
+ fn(info);
+
+ if (stop == 1) {
+ return false; // We're done.
+ } else if (stop > 0) {
+ stop--;
+ }
+ } else {
+ start--;
+ }
+
+ return true;
+ }
+
+ FrameFn fn;
+ size_t start;
+ size_t stop;
+};
+
+template <typename FrameFn>
+GetStackTraceVisitor<FrameFn> MakeStackTraceVisitor(art::Thread* thread_in,
+ size_t start,
+ size_t stop,
+ FrameFn fn) {
+ return GetStackTraceVisitor<FrameFn>(thread_in, start, stop, fn);
+}
+
+struct GetStackTraceVectorClosure : public art::Closure {
+ public:
+ GetStackTraceVectorClosure(size_t start, size_t stop)
+ : start_input(start),
+ stop_input(stop),
+ start_result(0),
+ stop_result(0) {}
+
+ void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ auto frames_fn = [&](jvmtiFrameInfo info) {
+ frames.push_back(info);
+ };
+ auto visitor = MakeStackTraceVisitor(self, start_input, stop_input, frames_fn);
+ visitor.WalkStack(/* include_transitions */ false);
+
+ start_result = visitor.start;
+ stop_result = visitor.stop;
+ }
+
+ const size_t start_input;
+ const size_t stop_input;
+
+ std::vector<jvmtiFrameInfo> frames;
+ size_t start_result;
+ size_t stop_result;
+};
+
+static jvmtiError TranslateFrameVector(const std::vector<jvmtiFrameInfo>& frames,
+ jint start_depth,
+ size_t start_result,
+ jint max_frame_count,
+ jvmtiFrameInfo* frame_buffer,
+ jint* count_ptr) {
+ size_t collected_frames = frames.size();
+
+ // Assume we're here having collected something.
+ DCHECK_GT(max_frame_count, 0);
+
+ // Frames from the top.
+ if (start_depth >= 0) {
+ if (start_result != 0) {
+ // Not enough frames.
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count));
+ if (frames.size() > 0) {
+ memcpy(frame_buffer, frames.data(), collected_frames * sizeof(jvmtiFrameInfo));
+ }
+ *count_ptr = static_cast<jint>(frames.size());
+ return ERR(NONE);
+ }
+
+ // Frames from the bottom.
+ if (collected_frames < static_cast<size_t>(-start_depth)) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+
+ size_t count = std::min(static_cast<size_t>(-start_depth), static_cast<size_t>(max_frame_count));
+ memcpy(frame_buffer,
+ &frames.data()[collected_frames + start_depth],
+ count * sizeof(jvmtiFrameInfo));
+ *count_ptr = static_cast<jint>(count);
+ return ERR(NONE);
+}
+
+struct GetStackTraceDirectClosure : public art::Closure {
+ public:
+ GetStackTraceDirectClosure(jvmtiFrameInfo* frame_buffer_, size_t start, size_t stop)
+ : frame_buffer(frame_buffer_),
+ start_input(start),
+ stop_input(stop),
+ index(0) {
+ DCHECK_GE(start_input, 0u);
+ }
+
+ void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ auto frames_fn = [&](jvmtiFrameInfo info) {
+ frame_buffer[index] = info;
+ ++index;
+ };
+ auto visitor = MakeStackTraceVisitor(self, start_input, stop_input, frames_fn);
+ visitor.WalkStack(/* include_transitions */ false);
+ }
+
+ jvmtiFrameInfo* frame_buffer;
+
+ const size_t start_input;
+ const size_t stop_input;
+
+ size_t index = 0;
+};
+
+static jvmtiError GetThread(JNIEnv* env,
+ art::ScopedObjectAccessAlreadyRunnable& soa,
+ jthread java_thread,
+ art::Thread** thread)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) // Needed for FromManagedThread.
+ REQUIRES(art::Locks::thread_list_lock_) { // Needed for FromManagedThread.
+ if (java_thread == nullptr) {
+ *thread = art::Thread::Current();
+ if (*thread == nullptr) {
+ // GetStackTrace can only be run during the live phase, so the current thread should be
+ // attached and thus available. Getting a null for current means we're starting up or
+ // dying.
+ return ERR(WRONG_PHASE);
+ }
+ } else {
+ if (!env->IsInstanceOf(java_thread, art::WellKnownClasses::java_lang_Thread)) {
+ return ERR(INVALID_THREAD);
+ }
+
+ // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD.
+ *thread = art::Thread::FromManagedThread(soa, java_thread);
+ if (*thread == nullptr) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ }
+ return ERR(NONE);
+}
+
+jvmtiError StackUtil::GetStackTrace(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED,
+ jthread java_thread,
+ jint start_depth,
+ jint max_frame_count,
+ jvmtiFrameInfo* frame_buffer,
+ jint* count_ptr) {
+ // It is not great that we have to hold these locks for so long, but it is necessary to ensure
+ // that the thread isn't dying on us.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+
+ art::Thread* thread;
+ jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(),
+ soa,
+ java_thread,
+ &thread);
+ if (thread_error != ERR(NONE)) {
+ return thread_error;
+ }
+ DCHECK(thread != nullptr);
+
+ art::ThreadState state = thread->GetState();
+ if (state == art::ThreadState::kStarting ||
+ state == art::ThreadState::kTerminated ||
+ thread->IsStillStarting()) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+
+ if (max_frame_count < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ if (frame_buffer == nullptr || count_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ if (max_frame_count == 0) {
+ *count_ptr = 0;
+ return ERR(NONE);
+ }
+
+ if (start_depth >= 0) {
+ // Fast path: Regular order of stack trace. Fill into the frame_buffer directly.
+ GetStackTraceDirectClosure closure(frame_buffer,
+ static_cast<size_t>(start_depth),
+ static_cast<size_t>(max_frame_count));
+ thread->RequestSynchronousCheckpoint(&closure);
+ *count_ptr = static_cast<jint>(closure.index);
+ if (closure.index < static_cast<size_t>(start_depth)) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ return ERR(NONE);
+ }
+
+ GetStackTraceVectorClosure closure(0, 0);
+ thread->RequestSynchronousCheckpoint(&closure);
+
+ return TranslateFrameVector(closure.frames,
+ start_depth,
+ closure.start_result,
+ max_frame_count,
+ frame_buffer,
+ count_ptr);
+}
+
+template <typename Data>
+struct GetAllStackTracesVectorClosure : public art::Closure {
+ GetAllStackTracesVectorClosure(size_t stop, Data* data_)
+ : barrier(0), stop_input(stop), data(data_) {}
+
+ void Run(art::Thread* thread) OVERRIDE
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!data->mutex) {
+ art::Thread* self = art::Thread::Current();
+ Work(thread, self);
+ barrier.Pass(self);
+ }
+
+ void Work(art::Thread* thread, art::Thread* self)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!data->mutex) {
+ // Skip threads that are still starting.
+ if (thread->IsStillStarting()) {
+ return;
+ }
+
+ std::vector<jvmtiFrameInfo>* thread_frames = data->GetFrameStorageFor(self, thread);
+ if (thread_frames == nullptr) {
+ return;
+ }
+
+ // Now collect the data.
+ auto frames_fn = [&](jvmtiFrameInfo info) {
+ thread_frames->push_back(info);
+ };
+ auto visitor = MakeStackTraceVisitor(thread, 0u, stop_input, frames_fn);
+ visitor.WalkStack(/* include_transitions */ false);
+ }
+
+ art::Barrier barrier;
+ const size_t stop_input;
+ Data* data;
+};
+
+template <typename Data>
+static void RunCheckpointAndWait(Data* data, size_t max_frame_count) {
+ GetAllStackTracesVectorClosure<Data> closure(max_frame_count, data);
+ size_t barrier_count = art::Runtime::Current()->GetThreadList()->RunCheckpoint(&closure, nullptr);
+ if (barrier_count == 0) {
+ return;
+ }
+ art::Thread* self = art::Thread::Current();
+ art::ScopedThreadStateChange tsc(self, art::ThreadState::kWaitingForCheckPointsToRun);
+ closure.barrier.Increment(self, barrier_count);
+}
+
+jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr,
+ jint* thread_count_ptr) {
+ if (max_frame_count < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ if (stack_info_ptr == nullptr || thread_count_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ struct AllStackTracesData {
+ AllStackTracesData() : mutex("GetAllStackTraces", art::LockLevel::kAbortLock) {}
+ ~AllStackTracesData() {
+ JNIEnv* jni_env = art::Thread::Current()->GetJniEnv();
+ for (jthread global_thread_ref : thread_peers) {
+ jni_env->DeleteGlobalRef(global_thread_ref);
+ }
+ }
+
+ std::vector<jvmtiFrameInfo>* GetFrameStorageFor(art::Thread* self, art::Thread* thread)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!mutex) {
+ art::MutexLock mu(self, mutex);
+
+ threads.push_back(thread);
+
+ jthread peer = art::Runtime::Current()->GetJavaVM()->AddGlobalRef(
+ self, thread->GetPeerFromOtherThread());
+ thread_peers.push_back(peer);
+
+ frames.emplace_back(new std::vector<jvmtiFrameInfo>());
+ return frames.back().get();
+ }
+
+ art::Mutex mutex;
+
+ // Storage. Only access directly after completion.
+
+ std::vector<art::Thread*> threads;
+ // "thread_peers" contains global references to their peers.
+ std::vector<jthread> thread_peers;
+
+ std::vector<std::unique_ptr<std::vector<jvmtiFrameInfo>>> frames;
+ };
+
+ AllStackTracesData data;
+ RunCheckpointAndWait(&data, static_cast<size_t>(max_frame_count));
+
+ art::Thread* current = art::Thread::Current();
+
+ // Convert the data into our output format.
+
+ // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to
+ // allocate one big chunk for this and the actual frames, which means we need
+ // to either be conservative or rearrange things later (the latter is implemented).
+ std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[data.frames.size()]);
+ std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos;
+ frame_infos.reserve(data.frames.size());
+
+ // Now run through and add data for each thread.
+ size_t sum_frames = 0;
+ for (size_t index = 0; index < data.frames.size(); ++index) {
+ jvmtiStackInfo& stack_info = stack_info_array.get()[index];
+ memset(&stack_info, 0, sizeof(jvmtiStackInfo));
+
+ const std::vector<jvmtiFrameInfo>& thread_frames = *data.frames[index].get();
+
+ // For the time being, set the thread to null. We'll fix it up in the second stage.
+ stack_info.thread = nullptr;
+ stack_info.state = JVMTI_THREAD_STATE_SUSPENDED;
+
+ size_t collected_frames = thread_frames.size();
+ if (max_frame_count == 0 || collected_frames == 0) {
+ stack_info.frame_count = 0;
+ stack_info.frame_buffer = nullptr;
+ continue;
+ }
+ DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count));
+
+ jvmtiFrameInfo* frame_info = new jvmtiFrameInfo[collected_frames];
+ frame_infos.emplace_back(frame_info);
+
+ jint count;
+ jvmtiError translate_result = TranslateFrameVector(thread_frames,
+ 0,
+ 0,
+ static_cast<jint>(collected_frames),
+ frame_info,
+ &count);
+ DCHECK(translate_result == JVMTI_ERROR_NONE);
+ stack_info.frame_count = static_cast<jint>(collected_frames);
+ stack_info.frame_buffer = frame_info;
+ sum_frames += static_cast<size_t>(count);
+ }
+
+ // No errors, yet. Now put it all into an output buffer.
+ size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * data.frames.size(),
+ alignof(jvmtiFrameInfo));
+ size_t chunk_size = rounded_stack_info_size + sum_frames * sizeof(jvmtiFrameInfo);
+ unsigned char* chunk_data;
+ jvmtiError alloc_result = env->Allocate(chunk_size, &chunk_data);
+ if (alloc_result != ERR(NONE)) {
+ return alloc_result;
+ }
+
+ jvmtiStackInfo* stack_info = reinterpret_cast<jvmtiStackInfo*>(chunk_data);
+ // First copy in all the basic data.
+ memcpy(stack_info, stack_info_array.get(), sizeof(jvmtiStackInfo) * data.frames.size());
+
+ // Now copy the frames and fix up the pointers.
+ jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>(
+ chunk_data + rounded_stack_info_size);
+ for (size_t i = 0; i < data.frames.size(); ++i) {
+ jvmtiStackInfo& old_stack_info = stack_info_array.get()[i];
+ jvmtiStackInfo& new_stack_info = stack_info[i];
+
+ // Translate the global ref into a local ref.
+ new_stack_info.thread =
+ static_cast<JNIEnv*>(current->GetJniEnv())->NewLocalRef(data.thread_peers[i]);
+
+ if (old_stack_info.frame_count > 0) {
+ // Only copy when there's data - leave the nullptr alone.
+ size_t frames_size = static_cast<size_t>(old_stack_info.frame_count) * sizeof(jvmtiFrameInfo);
+ memcpy(frame_info, old_stack_info.frame_buffer, frames_size);
+ new_stack_info.frame_buffer = frame_info;
+ frame_info += old_stack_info.frame_count;
+ }
+ }
+
+ *stack_info_ptr = stack_info;
+ *thread_count_ptr = static_cast<jint>(data.frames.size());
+
+ return ERR(NONE);
+}
+
+jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env,
+ jint thread_count,
+ const jthread* thread_list,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr) {
+ if (max_frame_count < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ if (thread_count < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ if (thread_count == 0) {
+ *stack_info_ptr = nullptr;
+ return ERR(NONE);
+ }
+ if (stack_info_ptr == nullptr || stack_info_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Thread* current = art::Thread::Current();
+ art::ScopedObjectAccess soa(current); // Now we know we have the shared lock.
+
+ struct SelectStackTracesData {
+ SelectStackTracesData() : mutex("GetSelectStackTraces", art::LockLevel::kAbortLock) {}
+
+ std::vector<jvmtiFrameInfo>* GetFrameStorageFor(art::Thread* self, art::Thread* thread)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(!mutex) {
+ art::ObjPtr<art::mirror::Object> peer = thread->GetPeerFromOtherThread();
+ for (size_t index = 0; index != handles.size(); ++index) {
+ if (peer == handles[index].Get()) {
+ // Found the thread.
+ art::MutexLock mu(self, mutex);
+
+ threads.push_back(thread);
+ thread_list_indices.push_back(index);
+
+ frames.emplace_back(new std::vector<jvmtiFrameInfo>());
+ return frames.back().get();
+ }
+ }
+ return nullptr;
+ }
+
+ art::Mutex mutex;
+
+ // Selection data.
+
+ std::vector<art::Handle<art::mirror::Object>> handles;
+
+ // Storage. Only access directly after completion.
+
+ std::vector<art::Thread*> threads;
+ std::vector<size_t> thread_list_indices;
+
+ std::vector<std::unique_ptr<std::vector<jvmtiFrameInfo>>> frames;
+ };
+
+ SelectStackTracesData data;
+
+ // Decode all threads to raw pointers. Put them into a handle scope to avoid any moving GC bugs.
+ art::VariableSizedHandleScope hs(current);
+ for (jint i = 0; i != thread_count; ++i) {
+ if (thread_list[i] == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+ if (!soa.Env()->IsInstanceOf(thread_list[i], art::WellKnownClasses::java_lang_Thread)) {
+ return ERR(INVALID_THREAD);
+ }
+ data.handles.push_back(hs.NewHandle(soa.Decode<art::mirror::Object>(thread_list[i])));
+ }
+
+ RunCheckpointAndWait(&data, static_cast<size_t>(max_frame_count));
+
+ // Convert the data into our output format.
+
+ // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to
+ // allocate one big chunk for this and the actual frames, which means we need
+ // to either be conservative or rearrange things later (the latter is implemented).
+ std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[data.frames.size()]);
+ std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos;
+ frame_infos.reserve(data.frames.size());
+
+ // Now run through and add data for each thread.
+ size_t sum_frames = 0;
+ for (size_t index = 0; index < data.frames.size(); ++index) {
+ jvmtiStackInfo& stack_info = stack_info_array.get()[index];
+ memset(&stack_info, 0, sizeof(jvmtiStackInfo));
+
+ art::Thread* self = data.threads[index];
+ const std::vector<jvmtiFrameInfo>& thread_frames = *data.frames[index].get();
+
+ // For the time being, set the thread to null. We don't have good ScopedLocalRef
+ // infrastructure.
+ DCHECK(self->GetPeerFromOtherThread() != nullptr);
+ stack_info.thread = nullptr;
+ stack_info.state = JVMTI_THREAD_STATE_SUSPENDED;
+
+ size_t collected_frames = thread_frames.size();
+ if (max_frame_count == 0 || collected_frames == 0) {
+ stack_info.frame_count = 0;
+ stack_info.frame_buffer = nullptr;
+ continue;
+ }
+ DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count));
+
+ jvmtiFrameInfo* frame_info = new jvmtiFrameInfo[collected_frames];
+ frame_infos.emplace_back(frame_info);
+
+ jint count;
+ jvmtiError translate_result = TranslateFrameVector(thread_frames,
+ 0,
+ 0,
+ static_cast<jint>(collected_frames),
+ frame_info,
+ &count);
+ DCHECK(translate_result == JVMTI_ERROR_NONE);
+ stack_info.frame_count = static_cast<jint>(collected_frames);
+ stack_info.frame_buffer = frame_info;
+ sum_frames += static_cast<size_t>(count);
+ }
+
+ // No errors, yet. Now put it all into an output buffer. Note that this is not frames.size(),
+ // potentially.
+ size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * thread_count,
+ alignof(jvmtiFrameInfo));
+ size_t chunk_size = rounded_stack_info_size + sum_frames * sizeof(jvmtiFrameInfo);
+ unsigned char* chunk_data;
+ jvmtiError alloc_result = env->Allocate(chunk_size, &chunk_data);
+ if (alloc_result != ERR(NONE)) {
+ return alloc_result;
+ }
+
+ jvmtiStackInfo* stack_info = reinterpret_cast<jvmtiStackInfo*>(chunk_data);
+ jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>(
+ chunk_data + rounded_stack_info_size);
+
+ for (size_t i = 0; i < static_cast<size_t>(thread_count); ++i) {
+ // Check whether we found a running thread for this.
+ // Note: For simplicity, and with the expectation that the list is usually small, use a simple
+ // search. (The list is *not* sorted!)
+ auto it = std::find(data.thread_list_indices.begin(), data.thread_list_indices.end(), i);
+ if (it == data.thread_list_indices.end()) {
+ // No native thread. Must be new or dead. We need to fill out the stack info now.
+ // (Need to read the Java "started" field to know whether this is starting or terminated.)
+ art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread_list[i]);
+ art::ObjPtr<art::mirror::Class> klass = peer->GetClass();
+ art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z");
+ CHECK(started_field != nullptr);
+ bool started = started_field->GetBoolean(peer) != 0;
+ constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW;
+ constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED |
+ JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
+ stack_info[i].thread = reinterpret_cast<JNIEnv*>(soa.Env())->NewLocalRef(thread_list[i]);
+ stack_info[i].state = started ? kTerminatedState : kStartedState;
+ stack_info[i].frame_count = 0;
+ stack_info[i].frame_buffer = nullptr;
+ } else {
+ // Had a native thread and frames.
+ size_t f_index = it - data.thread_list_indices.begin();
+
+ jvmtiStackInfo& old_stack_info = stack_info_array.get()[f_index];
+ jvmtiStackInfo& new_stack_info = stack_info[i];
+
+ memcpy(&new_stack_info, &old_stack_info, sizeof(jvmtiStackInfo));
+ new_stack_info.thread = reinterpret_cast<JNIEnv*>(soa.Env())->NewLocalRef(thread_list[i]);
+ if (old_stack_info.frame_count > 0) {
+ // Only copy when there's data - leave the nullptr alone.
+ size_t frames_size =
+ static_cast<size_t>(old_stack_info.frame_count) * sizeof(jvmtiFrameInfo);
+ memcpy(frame_info, old_stack_info.frame_buffer, frames_size);
+ new_stack_info.frame_buffer = frame_info;
+ frame_info += old_stack_info.frame_count;
+ }
+ }
+ }
+
+ *stack_info_ptr = stack_info;
+
+ return ERR(NONE);
+}
+
+// Walks up the stack counting Java frames. This is not StackVisitor::ComputeNumFrames, as
+// runtime methods and transitions must not be counted.
+struct GetFrameCountVisitor : public art::StackVisitor {
+ explicit GetFrameCountVisitor(art::Thread* thread)
+ : art::StackVisitor(thread, nullptr, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ count(0) {}
+
+ bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::ArtMethod* m = GetMethod();
+ const bool do_count = !(m == nullptr || m->IsRuntimeMethod());
+ if (do_count) {
+ count++;
+ }
+ return true;
+ }
+
+ size_t count;
+};
+
+struct GetFrameCountClosure : public art::Closure {
+ public:
+ GetFrameCountClosure() : count(0) {}
+
+ void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ GetFrameCountVisitor visitor(self);
+ visitor.WalkStack(false);
+
+ count = visitor.count;
+ }
+
+ size_t count;
+};
+
+jvmtiError StackUtil::GetFrameCount(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread java_thread,
+ jint* count_ptr) {
+ // It is not great that we have to hold these locks for so long, but it is necessary to ensure
+ // that the thread isn't dying on us.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+
+ art::Thread* thread;
+ jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(),
+ soa,
+ java_thread,
+ &thread);
+
+ if (thread_error != ERR(NONE)) {
+ return thread_error;
+ }
+ DCHECK(thread != nullptr);
+
+ if (count_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ GetFrameCountClosure closure;
+ thread->RequestSynchronousCheckpoint(&closure);
+
+ *count_ptr = closure.count;
+ return ERR(NONE);
+}
+
+// Walks up the stack 'n' callers, when used with Thread::WalkStack.
+struct GetLocationVisitor : public art::StackVisitor {
+ GetLocationVisitor(art::Thread* thread, size_t n_in)
+ : art::StackVisitor(thread, nullptr, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ n(n_in),
+ count(0),
+ caller(nullptr),
+ caller_dex_pc(0) {}
+
+ bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::ArtMethod* m = GetMethod();
+ const bool do_count = !(m == nullptr || m->IsRuntimeMethod());
+ if (do_count) {
+ DCHECK(caller == nullptr);
+ if (count == n) {
+ caller = m;
+ caller_dex_pc = GetDexPc(false);
+ return false;
+ }
+ count++;
+ }
+ return true;
+ }
+
+ const size_t n;
+ size_t count;
+ art::ArtMethod* caller;
+ uint32_t caller_dex_pc;
+};
+
+struct GetLocationClosure : public art::Closure {
+ public:
+ explicit GetLocationClosure(size_t n_in) : n(n_in), method(nullptr), dex_pc(0) {}
+
+ void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ GetLocationVisitor visitor(self, n);
+ visitor.WalkStack(false);
+
+ method = visitor.caller;
+ dex_pc = visitor.caller_dex_pc;
+ }
+
+ const size_t n;
+ art::ArtMethod* method;
+ uint32_t dex_pc;
+};
+
+jvmtiError StackUtil::GetFrameLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread java_thread,
+ jint depth,
+ jmethodID* method_ptr,
+ jlocation* location_ptr) {
+ // It is not great that we have to hold these locks for so long, but it is necessary to ensure
+ // that the thread isn't dying on us.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+
+ art::Thread* thread;
+ jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(),
+ soa,
+ java_thread,
+ &thread);
+ if (thread_error != ERR(NONE)) {
+ return thread_error;
+ }
+ DCHECK(thread != nullptr);
+
+ if (depth < 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ }
+ if (method_ptr == nullptr || location_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ GetLocationClosure closure(static_cast<size_t>(depth));
+ thread->RequestSynchronousCheckpoint(&closure);
+
+ if (closure.method == nullptr) {
+ return ERR(NO_MORE_FRAMES);
+ }
+
+ *method_ptr = art::jni::EncodeArtMethod(closure.method);
+ if (closure.method->IsNative()) {
+ *location_ptr = -1;
+ } else {
+ if (closure.dex_pc == art::DexFile::kDexNoIndex) {
+ return ERR(INTERNAL);
+ }
+ *location_ptr = static_cast<jlocation>(closure.dex_pc);
+ }
+
+ return ERR(NONE);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h
new file mode 100644
index 0000000..2e96b82
--- /dev/null
+++ b/openjdkjvmti/ti_stack.h
@@ -0,0 +1,74 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_STACK_H_
+#define ART_OPENJDKJVMTI_TI_STACK_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/mutex.h"
+
+namespace openjdkjvmti {
+
+class StackUtil {
+ public:
+ static jvmtiError GetAllStackTraces(jvmtiEnv* env,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr,
+ jint* thread_count_ptr)
+ REQUIRES(!art::Locks::thread_list_lock_);
+
+ static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr);
+
+ static jvmtiError GetFrameLocation(jvmtiEnv* env,
+ jthread thread,
+ jint depth,
+ jmethodID* method_ptr,
+ jlocation* location_ptr);
+
+ static jvmtiError GetStackTrace(jvmtiEnv* env,
+ jthread thread,
+ jint start_depth,
+ jint max_frame_count,
+ jvmtiFrameInfo* frame_buffer,
+ jint* count_ptr);
+
+ static jvmtiError GetThreadListStackTraces(jvmtiEnv* env,
+ jint thread_count,
+ const jthread* thread_list,
+ jint max_frame_count,
+ jvmtiStackInfo** stack_info_ptr);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_STACK_H_
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
new file mode 100644
index 0000000..6fa73f8
--- /dev/null
+++ b/openjdkjvmti/ti_thread.cc
@@ -0,0 +1,909 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_thread.h"
+
+#include "android-base/strings.h"
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "base/mutex.h"
+#include "events-inl.h"
+#include "gc/system_weak.h"
+#include "gc_root-inl.h"
+#include "jni_internal.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "mirror/string.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "obj_ptr.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+art::ArtField* ThreadUtil::context_class_loader_ = nullptr;
+
+struct ThreadCallback : public art::ThreadLifecycleCallback, public art::RuntimePhaseCallback {
+ jthread GetThreadObject(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (self->GetPeer() == nullptr) {
+ return nullptr;
+ }
+ return self->GetJniEnv()->AddLocalReference<jthread>(self->GetPeer());
+ }
+ template <ArtJvmtiEvent kEvent>
+ void Post(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ DCHECK_EQ(self, art::Thread::Current());
+ ScopedLocalRef<jthread> thread(self->GetJniEnv(), GetThreadObject(self));
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+ event_handler->DispatchEvent<kEvent>(self,
+ reinterpret_cast<JNIEnv*>(self->GetJniEnv()),
+ thread.get());
+ }
+
+ void ThreadStart(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (!started) {
+ // Runtime isn't started. We only expect at most the signal handler or JIT threads to be
+ // started here.
+ if (art::kIsDebugBuild) {
+ std::string name;
+ self->GetThreadName(name);
+ if (name != "JDWP" &&
+ name != "Signal Catcher" &&
+ !android::base::StartsWith(name, "Jit thread pool")) {
+ LOG(FATAL) << "Unexpected thread before start: " << name;
+ }
+ }
+ return;
+ }
+ Post<ArtJvmtiEvent::kThreadStart>(self);
+ }
+
+ void ThreadDeath(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ Post<ArtJvmtiEvent::kThreadEnd>(self);
+ }
+
+ void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (phase == RuntimePhase::kInit) {
+ // We moved to VMInit. Report the main thread as started (it was attached early, and must
+ // not be reported until Init.
+ started = true;
+ Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current());
+ }
+ }
+
+ EventHandler* event_handler = nullptr;
+ bool started = false;
+};
+
+ThreadCallback gThreadCallback;
+
+void ThreadUtil::Register(EventHandler* handler) {
+ art::Runtime* runtime = art::Runtime::Current();
+
+ gThreadCallback.started = runtime->IsStarted();
+ gThreadCallback.event_handler = handler;
+
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Add thread callback");
+ runtime->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&gThreadCallback);
+ runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gThreadCallback);
+}
+
+void ThreadUtil::CacheData() {
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ObjPtr<art::mirror::Class> thread_class =
+ soa.Decode<art::mirror::Class>(art::WellKnownClasses::java_lang_Thread);
+ CHECK(thread_class != nullptr);
+ context_class_loader_ = thread_class->FindDeclaredInstanceField("contextClassLoader",
+ "Ljava/lang/ClassLoader;");
+ CHECK(context_class_loader_ != nullptr);
+}
+
+void ThreadUtil::Unregister() {
+ art::ScopedThreadStateChange stsc(art::Thread::Current(),
+ art::ThreadState::kWaitingForDebuggerToAttach);
+ art::ScopedSuspendAll ssa("Remove thread callback");
+ art::Runtime* runtime = art::Runtime::Current();
+ runtime->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&gThreadCallback);
+ runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gThreadCallback);
+}
+
+jvmtiError ThreadUtil::GetCurrentThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread* thread_ptr) {
+ art::Thread* self = art::Thread::Current();
+
+ art::ScopedObjectAccess soa(self);
+
+ jthread thread_peer;
+ if (self->IsStillStarting()) {
+ thread_peer = nullptr;
+ } else {
+ thread_peer = soa.AddLocalReference<jthread>(self->GetPeer());
+ }
+
+ *thread_ptr = thread_peer;
+ return ERR(NONE);
+}
+
+// Get the native thread. The spec says a null object denotes the current thread.
+art::Thread* ThreadUtil::GetNativeThread(jthread thread,
+ const art::ScopedObjectAccessAlreadyRunnable& soa) {
+ if (thread == nullptr) {
+ return art::Thread::Current();
+ }
+
+ return art::Thread::FromManagedThread(soa, thread);
+}
+
+jvmtiError ThreadUtil::GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr) {
+ if (info_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ if (!PhaseUtil::IsLivePhase()) {
+ return JVMTI_ERROR_WRONG_PHASE;
+ }
+
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+
+ art::Thread* target = GetNativeThread(thread, soa);
+ if (target == nullptr && thread == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+
+ JvmtiUniquePtr<char[]> name_uptr;
+ if (target != nullptr) {
+ // Have a native thread object, this thread is alive.
+ std::string name;
+ target->GetThreadName(name);
+ jvmtiError name_result;
+ name_uptr = CopyString(env, name.c_str(), &name_result);
+ if (name_uptr == nullptr) {
+ return name_result;
+ }
+ info_ptr->name = name_uptr.get();
+
+ info_ptr->priority = target->GetNativePriority();
+
+ info_ptr->is_daemon = target->IsDaemon();
+
+ art::ObjPtr<art::mirror::Object> peer = target->GetPeerFromOtherThread();
+
+ // ThreadGroup.
+ if (peer != nullptr) {
+ art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+ CHECK(f != nullptr);
+ art::ObjPtr<art::mirror::Object> group = f->GetObject(peer);
+ info_ptr->thread_group = group == nullptr
+ ? nullptr
+ : soa.AddLocalReference<jthreadGroup>(group);
+ } else {
+ info_ptr->thread_group = nullptr;
+ }
+
+ // Context classloader.
+ DCHECK(context_class_loader_ != nullptr);
+ art::ObjPtr<art::mirror::Object> ccl = peer != nullptr
+ ? context_class_loader_->GetObject(peer)
+ : nullptr;
+ info_ptr->context_class_loader = ccl == nullptr
+ ? nullptr
+ : soa.AddLocalReference<jobject>(ccl);
+ } else {
+ // Only the peer. This thread has either not been started, or is dead. Read things from
+ // the Java side.
+ art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread);
+
+ // Name.
+ {
+ art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_name);
+ CHECK(f != nullptr);
+ art::ObjPtr<art::mirror::Object> name = f->GetObject(peer);
+ std::string name_cpp;
+ const char* name_cstr;
+ if (name != nullptr) {
+ name_cpp = name->AsString()->ToModifiedUtf8();
+ name_cstr = name_cpp.c_str();
+ } else {
+ name_cstr = "";
+ }
+ jvmtiError name_result;
+ name_uptr = CopyString(env, name_cstr, &name_result);
+ if (name_uptr == nullptr) {
+ return name_result;
+ }
+ info_ptr->name = name_uptr.get();
+ }
+
+ // Priority.
+ {
+ art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_priority);
+ CHECK(f != nullptr);
+ info_ptr->priority = static_cast<jint>(f->GetInt(peer));
+ }
+
+ // Daemon.
+ {
+ art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_daemon);
+ CHECK(f != nullptr);
+ info_ptr->is_daemon = f->GetBoolean(peer) == 0 ? JNI_FALSE : JNI_TRUE;
+ }
+
+ // ThreadGroup.
+ {
+ art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+ CHECK(f != nullptr);
+ art::ObjPtr<art::mirror::Object> group = f->GetObject(peer);
+ info_ptr->thread_group = group == nullptr
+ ? nullptr
+ : soa.AddLocalReference<jthreadGroup>(group);
+ }
+
+ // Context classloader.
+ DCHECK(context_class_loader_ != nullptr);
+ art::ObjPtr<art::mirror::Object> ccl = peer != nullptr
+ ? context_class_loader_->GetObject(peer)
+ : nullptr;
+ info_ptr->context_class_loader = ccl == nullptr
+ ? nullptr
+ : soa.AddLocalReference<jobject>(ccl);
+ }
+
+ name_uptr.release();
+
+ return ERR(NONE);
+}
+
+struct InternalThreadState {
+ art::Thread* native_thread;
+ art::ThreadState art_state;
+ int thread_user_code_suspend_count;
+};
+
+// Return the thread's (or current thread, if null) thread state.
+static InternalThreadState GetNativeThreadState(jthread thread,
+ const art::ScopedObjectAccessAlreadyRunnable& soa)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(art::Locks::thread_list_lock_, art::Locks::user_code_suspension_lock_) {
+ art::Thread* self = nullptr;
+ if (thread == nullptr) {
+ self = art::Thread::Current();
+ } else {
+ self = art::Thread::FromManagedThread(soa, thread);
+ }
+ InternalThreadState thread_state = {};
+ art::MutexLock tscl_mu(soa.Self(), *art::Locks::thread_suspend_count_lock_);
+ thread_state.native_thread = self;
+ if (self == nullptr || self->IsStillStarting()) {
+ thread_state.art_state = art::ThreadState::kStarting;
+ thread_state.thread_user_code_suspend_count = 0;
+ } else {
+ thread_state.art_state = self->GetState();
+ thread_state.thread_user_code_suspend_count = self->GetUserCodeSuspendCount();
+ }
+ return thread_state;
+}
+
+static jint GetJvmtiThreadStateFromInternal(const InternalThreadState& state) {
+ art::ThreadState internal_thread_state = state.art_state;
+ jint jvmti_state = JVMTI_THREAD_STATE_ALIVE;
+
+ if (state.thread_user_code_suspend_count != 0) {
+ jvmti_state |= JVMTI_THREAD_STATE_SUSPENDED;
+ // Note: We do not have data about the previous state. Otherwise we should load the previous
+ // state here.
+ }
+
+ if (state.native_thread->IsInterrupted()) {
+ jvmti_state |= JVMTI_THREAD_STATE_INTERRUPTED;
+ }
+
+ if (internal_thread_state == art::ThreadState::kNative) {
+ jvmti_state |= JVMTI_THREAD_STATE_IN_NATIVE;
+ }
+
+ if (internal_thread_state == art::ThreadState::kRunnable ||
+ internal_thread_state == art::ThreadState::kWaitingWeakGcRootRead ||
+ internal_thread_state == art::ThreadState::kSuspended) {
+ jvmti_state |= JVMTI_THREAD_STATE_RUNNABLE;
+ } else if (internal_thread_state == art::ThreadState::kBlocked) {
+ jvmti_state |= JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
+ } else {
+ // Should be in waiting state.
+ jvmti_state |= JVMTI_THREAD_STATE_WAITING;
+
+ if (internal_thread_state == art::ThreadState::kTimedWaiting ||
+ internal_thread_state == art::ThreadState::kSleeping) {
+ jvmti_state |= JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT;
+ } else {
+ jvmti_state |= JVMTI_THREAD_STATE_WAITING_INDEFINITELY;
+ }
+
+ if (internal_thread_state == art::ThreadState::kSleeping) {
+ jvmti_state |= JVMTI_THREAD_STATE_SLEEPING;
+ }
+
+ if (internal_thread_state == art::ThreadState::kTimedWaiting ||
+ internal_thread_state == art::ThreadState::kWaiting) {
+ jvmti_state |= JVMTI_THREAD_STATE_IN_OBJECT_WAIT;
+ }
+
+ // TODO: PARKED. We'll have to inspect the stack.
+ }
+
+ return jvmti_state;
+}
+
+static jint GetJavaStateFromInternal(const InternalThreadState& state) {
+ switch (state.art_state) {
+ case art::ThreadState::kTerminated:
+ return JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
+
+ case art::ThreadState::kRunnable:
+ case art::ThreadState::kNative:
+ case art::ThreadState::kWaitingWeakGcRootRead:
+ case art::ThreadState::kSuspended:
+ return JVMTI_JAVA_LANG_THREAD_STATE_RUNNABLE;
+
+ case art::ThreadState::kTimedWaiting:
+ case art::ThreadState::kSleeping:
+ return JVMTI_JAVA_LANG_THREAD_STATE_TIMED_WAITING;
+
+ case art::ThreadState::kBlocked:
+ return JVMTI_JAVA_LANG_THREAD_STATE_BLOCKED;
+
+ case art::ThreadState::kStarting:
+ return JVMTI_JAVA_LANG_THREAD_STATE_NEW;
+
+ case art::ThreadState::kWaiting:
+ case art::ThreadState::kWaitingForGcToComplete:
+ case art::ThreadState::kWaitingPerformingGc:
+ case art::ThreadState::kWaitingForCheckPointsToRun:
+ case art::ThreadState::kWaitingForDebuggerSend:
+ case art::ThreadState::kWaitingForDebuggerToAttach:
+ case art::ThreadState::kWaitingInMainDebuggerLoop:
+ case art::ThreadState::kWaitingForDebuggerSuspension:
+ case art::ThreadState::kWaitingForDeoptimization:
+ case art::ThreadState::kWaitingForGetObjectsAllocated:
+ case art::ThreadState::kWaitingForJniOnLoad:
+ case art::ThreadState::kWaitingForSignalCatcherOutput:
+ case art::ThreadState::kWaitingInMainSignalCatcherLoop:
+ case art::ThreadState::kWaitingForMethodTracingStart:
+ case art::ThreadState::kWaitingForVisitObjects:
+ case art::ThreadState::kWaitingForGcThreadFlip:
+ return JVMTI_JAVA_LANG_THREAD_STATE_WAITING;
+ }
+ LOG(FATAL) << "Unreachable";
+ UNREACHABLE();
+}
+
+// Suspends the current thread if it has any suspend requests on it.
+static void SuspendCheck(art::Thread* self)
+ REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_) {
+ art::ScopedObjectAccess soa(self);
+ // Really this is only needed if we are in FastJNI and actually have the mutator_lock_ already.
+ self->FullSuspendCheck();
+}
+
+jvmtiError ThreadUtil::GetThreadState(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread thread,
+ jint* thread_state_ptr) {
+ if (thread_state_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Thread* self = art::Thread::Current();
+ InternalThreadState state = {};
+ // Loop since we need to bail out and try again if we would end up getting suspended while holding
+ // the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we
+ // release the lock, wait to get resumed and try again.
+ do {
+ SuspendCheck(self);
+ art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_);
+ {
+ art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_);
+ if (self->GetUserCodeSuspendCount() != 0) {
+ // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_
+ // by a user-code suspension. We retry and do another SuspendCheck to clear this.
+ continue;
+ }
+ }
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+ state = GetNativeThreadState(thread, soa);
+ if (state.art_state == art::ThreadState::kStarting) {
+ break;
+ }
+ DCHECK(state.native_thread != nullptr);
+
+ // Translate internal thread state to JVMTI and Java state.
+ jint jvmti_state = GetJvmtiThreadStateFromInternal(state);
+
+ // Java state is derived from nativeGetState.
+ // TODO: Our implementation assigns "runnable" to suspended. As such, we will have slightly
+ // different mask if a thread got suspended due to user-code. However, this is for
+ // consistency with the Java view.
+ jint java_state = GetJavaStateFromInternal(state);
+
+ *thread_state_ptr = jvmti_state | java_state;
+
+ return ERR(NONE);
+ } while (true);
+
+ DCHECK_EQ(state.art_state, art::ThreadState::kStarting);
+
+ if (thread == nullptr) {
+ // No native thread, and no Java thread? We must be starting up. Report as wrong phase.
+ return ERR(WRONG_PHASE);
+ }
+
+ art::ScopedObjectAccess soa(self);
+
+ // Need to read the Java "started" field to know whether this is starting or terminated.
+ art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread);
+ art::ObjPtr<art::mirror::Class> klass = peer->GetClass();
+ art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z");
+ CHECK(started_field != nullptr);
+ bool started = started_field->GetBoolean(peer) != 0;
+ constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW;
+ constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED |
+ JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
+ *thread_state_ptr = started ? kTerminatedState : kStartedState;
+ return ERR(NONE);
+}
+
+jvmtiError ThreadUtil::GetAllThreads(jvmtiEnv* env,
+ jint* threads_count_ptr,
+ jthread** threads_ptr) {
+ if (threads_count_ptr == nullptr || threads_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Thread* current = art::Thread::Current();
+
+ art::ScopedObjectAccess soa(current);
+
+ art::MutexLock mu(current, *art::Locks::thread_list_lock_);
+ std::list<art::Thread*> thread_list = art::Runtime::Current()->GetThreadList()->GetList();
+
+ std::vector<art::ObjPtr<art::mirror::Object>> peers;
+
+ for (art::Thread* thread : thread_list) {
+ // Skip threads that are still starting.
+ if (thread->IsStillStarting()) {
+ continue;
+ }
+
+ art::ObjPtr<art::mirror::Object> peer = thread->GetPeerFromOtherThread();
+ if (peer != nullptr) {
+ peers.push_back(peer);
+ }
+ }
+
+ if (peers.empty()) {
+ *threads_count_ptr = 0;
+ *threads_ptr = nullptr;
+ } else {
+ unsigned char* data;
+ jvmtiError data_result = env->Allocate(peers.size() * sizeof(jthread), &data);
+ if (data_result != ERR(NONE)) {
+ return data_result;
+ }
+ jthread* threads = reinterpret_cast<jthread*>(data);
+ for (size_t i = 0; i != peers.size(); ++i) {
+ threads[i] = soa.AddLocalReference<jthread>(peers[i]);
+ }
+
+ *threads_count_ptr = static_cast<jint>(peers.size());
+ *threads_ptr = threads;
+ }
+ return ERR(NONE);
+}
+
+// The struct that we store in the art::Thread::custom_tls_ that maps the jvmtiEnvs to the data
+// stored with that thread. This is needed since different jvmtiEnvs are not supposed to share TLS
+// data but we only have a single slot in Thread objects to store data.
+struct JvmtiGlobalTLSData {
+ std::unordered_map<jvmtiEnv*, const void*> data GUARDED_BY(art::Locks::thread_list_lock_);
+};
+
+static void RemoveTLSData(art::Thread* target, void* ctx) REQUIRES(art::Locks::thread_list_lock_) {
+ jvmtiEnv* env = reinterpret_cast<jvmtiEnv*>(ctx);
+ art::Locks::thread_list_lock_->AssertHeld(art::Thread::Current());
+ JvmtiGlobalTLSData* global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS());
+ if (global_tls != nullptr) {
+ global_tls->data.erase(env);
+ }
+}
+
+void ThreadUtil::RemoveEnvironment(jvmtiEnv* env) {
+ art::Thread* self = art::Thread::Current();
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::ThreadList* list = art::Runtime::Current()->GetThreadList();
+ list->ForEach(RemoveTLSData, env);
+}
+
+jvmtiError ThreadUtil::SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data) {
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = GetNativeThread(thread, soa);
+ if (target == nullptr && thread == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+ if (target == nullptr) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+
+ JvmtiGlobalTLSData* global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS());
+ if (global_tls == nullptr) {
+ target->SetCustomTLS(new JvmtiGlobalTLSData);
+ global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS());
+ }
+
+ global_tls->data[env] = data;
+
+ return ERR(NONE);
+}
+
+jvmtiError ThreadUtil::GetThreadLocalStorage(jvmtiEnv* env,
+ jthread thread,
+ void** data_ptr) {
+ if (data_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = GetNativeThread(thread, soa);
+ if (target == nullptr && thread == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+ if (target == nullptr) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+
+ JvmtiGlobalTLSData* global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS());
+ if (global_tls == nullptr) {
+ *data_ptr = nullptr;
+ return OK;
+ }
+ auto it = global_tls->data.find(env);
+ if (it != global_tls->data.end()) {
+ *data_ptr = const_cast<void*>(it->second);
+ } else {
+ *data_ptr = nullptr;
+ }
+
+ return ERR(NONE);
+}
+
+struct AgentData {
+ const void* arg;
+ jvmtiStartFunction proc;
+ jthread thread;
+ JavaVM* java_vm;
+ jvmtiEnv* jvmti_env;
+ jint priority;
+};
+
+static void* AgentCallback(void* arg) {
+ std::unique_ptr<AgentData> data(reinterpret_cast<AgentData*>(arg));
+ CHECK(data->thread != nullptr);
+
+ // We already have a peer. So call our special Attach function.
+ art::Thread* self = art::Thread::Attach("JVMTI Agent thread", true, data->thread);
+ CHECK(self != nullptr);
+ // The name in Attach() is only for logging. Set the thread name. This is important so
+ // that the thread is no longer seen as starting up.
+ {
+ art::ScopedObjectAccess soa(self);
+ self->SetThreadName("JVMTI Agent thread");
+ }
+
+ // Release the peer.
+ JNIEnv* env = self->GetJniEnv();
+ env->DeleteGlobalRef(data->thread);
+ data->thread = nullptr;
+
+ // Run the agent code.
+ data->proc(data->jvmti_env, env, const_cast<void*>(data->arg));
+
+ // Detach the thread.
+ int detach_result = data->java_vm->DetachCurrentThread();
+ CHECK_EQ(detach_result, 0);
+
+ return nullptr;
+}
+
+jvmtiError ThreadUtil::RunAgentThread(jvmtiEnv* jvmti_env,
+ jthread thread,
+ jvmtiStartFunction proc,
+ const void* arg,
+ jint priority) {
+ if (priority < JVMTI_THREAD_MIN_PRIORITY || priority > JVMTI_THREAD_MAX_PRIORITY) {
+ return ERR(INVALID_PRIORITY);
+ }
+ JNIEnv* env = art::Thread::Current()->GetJniEnv();
+ if (thread == nullptr || !env->IsInstanceOf(thread, art::WellKnownClasses::java_lang_Thread)) {
+ return ERR(INVALID_THREAD);
+ }
+ if (proc == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ std::unique_ptr<AgentData> data(new AgentData);
+ data->arg = arg;
+ data->proc = proc;
+ // We need a global ref for Java objects, as local refs will be invalid.
+ data->thread = env->NewGlobalRef(thread);
+ data->java_vm = art::Runtime::Current()->GetJavaVM();
+ data->jvmti_env = jvmti_env;
+ data->priority = priority;
+
+ pthread_t pthread;
+ int pthread_create_result = pthread_create(&pthread,
+ nullptr,
+ &AgentCallback,
+ reinterpret_cast<void*>(data.get()));
+ if (pthread_create_result != 0) {
+ return ERR(INTERNAL);
+ }
+ data.release();
+
+ return ERR(NONE);
+}
+
+jvmtiError ThreadUtil::SuspendOther(art::Thread* self,
+ jthread target_jthread) {
+ // Loop since we need to bail out and try again if we would end up getting suspended while holding
+ // the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we
+ // release the lock, wait to get resumed and try again.
+ do {
+ // Suspend ourself if we have any outstanding suspends. This is so we won't suspend due to
+ // another SuspendThread in the middle of suspending something else potentially causing a
+ // deadlock. We need to do this in the loop because if we ended up back here then we had
+ // outstanding SuspendReason::kForUserCode suspensions and we should wait for them to be cleared
+ // before continuing.
+ SuspendCheck(self);
+ art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
+ {
+ art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
+ // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by
+ // a user-code suspension. We retry and do another SuspendCheck to clear this.
+ if (self->GetUserCodeSuspendCount() != 0) {
+ continue;
+ }
+ // We are not going to be suspended by user code from now on.
+ }
+ {
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock thread_list_mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = GetNativeThread(target_jthread, soa);
+ art::ThreadState state = target->GetState();
+ if (state == art::ThreadState::kTerminated || state == art::ThreadState::kStarting) {
+ return ERR(THREAD_NOT_ALIVE);
+ } else {
+ art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
+ if (target->GetUserCodeSuspendCount() != 0) {
+ return ERR(THREAD_SUSPENDED);
+ }
+ }
+ }
+ bool timeout = true;
+ art::Thread* ret_target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer(
+ target_jthread,
+ /* request_suspension */ true,
+ art::SuspendReason::kForUserCode,
+ &timeout);
+ if (ret_target == nullptr && !timeout) {
+ // TODO It would be good to get more information about why exactly the thread failed to
+ // suspend.
+ return ERR(INTERNAL);
+ } else if (!timeout) {
+ // we didn't time out and got a result.
+ return OK;
+ }
+ // We timed out. Just go around and try again.
+ } while (true);
+ UNREACHABLE();
+}
+
+jvmtiError ThreadUtil::SuspendSelf(art::Thread* self) {
+ CHECK(self == art::Thread::Current());
+ {
+ art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
+ art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_);
+ if (self->GetUserCodeSuspendCount() != 0) {
+ // This can only happen if we race with another thread to suspend 'self' and we lose.
+ return ERR(THREAD_SUSPENDED);
+ }
+ // We shouldn't be able to fail this.
+ if (!self->ModifySuspendCount(self, +1, nullptr, art::SuspendReason::kForUserCode)) {
+ // TODO More specific error would be nice.
+ return ERR(INTERNAL);
+ }
+ }
+ // Once we have requested the suspend we actually go to sleep. We need to do this after releasing
+ // the suspend_lock to make sure we can be woken up. This call gains the mutator lock causing us
+ // to go to sleep until we are resumed.
+ SuspendCheck(self);
+ return OK;
+}
+
+jvmtiError ThreadUtil::SuspendThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) {
+ art::Thread* self = art::Thread::Current();
+ bool target_is_self = false;
+ {
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = GetNativeThread(thread, soa);
+ if (target == nullptr) {
+ return ERR(INVALID_THREAD);
+ } else if (target == self) {
+ target_is_self = true;
+ }
+ }
+ if (target_is_self) {
+ return SuspendSelf(self);
+ } else {
+ return SuspendOther(self, thread);
+ }
+}
+
+jvmtiError ThreadUtil::ResumeThread(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread thread) {
+ if (thread == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ art::Thread* self = art::Thread::Current();
+ art::Thread* target;
+ // Retry until we know we won't get suspended by user code while resuming something.
+ do {
+ SuspendCheck(self);
+ art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_);
+ {
+ art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_);
+ // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by
+ // a user-code suspension. We retry and do another SuspendCheck to clear this.
+ if (self->GetUserCodeSuspendCount() != 0) {
+ continue;
+ }
+ }
+ // From now on we know we cannot get suspended by user-code.
+ {
+ // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't
+ // have the 'suspend_lock' locked here.
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+ target = GetNativeThread(thread, soa);
+ if (target == nullptr) {
+ return ERR(INVALID_THREAD);
+ } else if (target == self) {
+ // We would have paused until we aren't suspended anymore due to the ScopedObjectAccess so
+ // we can just return THREAD_NOT_SUSPENDED. Unfortunately we cannot do any real DCHECKs
+ // about current state since it's all concurrent.
+ return ERR(THREAD_NOT_SUSPENDED);
+ } else if (target->GetState() == art::ThreadState::kTerminated) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ // The JVMTI spec requires us to return THREAD_NOT_SUSPENDED if it is alive but we really
+ // cannot tell why resume failed.
+ {
+ art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
+ if (target->GetUserCodeSuspendCount() == 0) {
+ return ERR(THREAD_NOT_SUSPENDED);
+ }
+ }
+ }
+ // It is okay that we don't have a thread_list_lock here since we know that the thread cannot
+ // die since it is currently held suspended by a SuspendReason::kForUserCode suspend.
+ DCHECK(target != self);
+ if (!art::Runtime::Current()->GetThreadList()->Resume(target,
+ art::SuspendReason::kForUserCode)) {
+ // TODO Give a better error.
+ // This is most likely THREAD_NOT_SUSPENDED but we cannot really be sure.
+ return ERR(INTERNAL);
+ } else {
+ return OK;
+ }
+ } while (true);
+}
+
+// Suspends all the threads in the list at the same time. Getting this behavior is a little tricky
+// since we can have threads in the list multiple times. This generally doesn't matter unless the
+// current thread is present multiple times. In that case we need to suspend only once and either
+// return the same error code in all the other slots if it failed or return ERR(THREAD_SUSPENDED) if
+// it didn't. We also want to handle the current thread last to make the behavior of the code
+// simpler to understand.
+jvmtiError ThreadUtil::SuspendThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* threads,
+ jvmtiError* results) {
+ if (request_count == 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ } else if (results == nullptr || threads == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ // This is the list of the indexes in 'threads' and 'results' that correspond to the currently
+ // running thread. These indexes we need to handle specially since we need to only actually
+ // suspend a single time.
+ std::vector<jint> current_thread_indexes;
+ art::Thread* self = art::Thread::Current();
+ for (jint i = 0; i < request_count; i++) {
+ {
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ if (threads[i] == nullptr || GetNativeThread(threads[i], soa) == self) {
+ current_thread_indexes.push_back(i);
+ continue;
+ }
+ }
+ results[i] = env->SuspendThread(threads[i]);
+ }
+ if (!current_thread_indexes.empty()) {
+ jint first_current_thread_index = current_thread_indexes[0];
+ // Suspend self.
+ jvmtiError res = env->SuspendThread(threads[first_current_thread_index]);
+ results[first_current_thread_index] = res;
+ // Fill in the rest of the error values as appropriate.
+ jvmtiError other_results = (res != OK) ? res : ERR(THREAD_SUSPENDED);
+ for (auto it = ++current_thread_indexes.begin(); it != current_thread_indexes.end(); ++it) {
+ results[*it] = other_results;
+ }
+ }
+ return OK;
+}
+
+jvmtiError ThreadUtil::ResumeThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* threads,
+ jvmtiError* results) {
+ if (request_count == 0) {
+ return ERR(ILLEGAL_ARGUMENT);
+ } else if (results == nullptr || threads == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ for (jint i = 0; i < request_count; i++) {
+ results[i] = env->ResumeThread(threads[i]);
+ }
+ return OK;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_thread.h b/openjdkjvmti/ti_thread.h
new file mode 100644
index 0000000..03c49d7
--- /dev/null
+++ b/openjdkjvmti/ti_thread.h
@@ -0,0 +1,116 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_THREAD_H_
+#define ART_OPENJDKJVMTI_TI_THREAD_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/macros.h"
+#include "base/mutex.h"
+
+namespace art {
+class ArtField;
+class ScopedObjectAccessAlreadyRunnable;
+class Thread;
+} // namespace art
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class ThreadUtil {
+ public:
+ static void Register(EventHandler* event_handler);
+ static void Unregister();
+
+ // To be called when it is safe to cache data.
+ static void CacheData();
+
+ // Handle a jvmtiEnv going away.
+ static void RemoveEnvironment(jvmtiEnv* env);
+
+ static jvmtiError GetAllThreads(jvmtiEnv* env, jint* threads_count_ptr, jthread** threads_ptr);
+
+ static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr);
+
+ static jvmtiError GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr);
+
+ static jvmtiError GetThreadState(jvmtiEnv* env, jthread thread, jint* thread_state_ptr);
+
+ static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data);
+ static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr);
+
+ static jvmtiError RunAgentThread(jvmtiEnv* env,
+ jthread thread,
+ jvmtiStartFunction proc,
+ const void* arg,
+ jint priority);
+
+ static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread);
+ static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread);
+
+ static jvmtiError SuspendThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* threads,
+ jvmtiError* results);
+ static jvmtiError ResumeThreadList(jvmtiEnv* env,
+ jint request_count,
+ const jthread* threads,
+ jvmtiError* results);
+
+ static art::Thread* GetNativeThread(jthread thread,
+ const art::ScopedObjectAccessAlreadyRunnable& soa)
+ REQUIRES_SHARED(art::Locks::mutator_lock_)
+ REQUIRES(art::Locks::thread_list_lock_);
+
+ private:
+ // We need to make sure only one thread tries to suspend threads at a time so we can get the
+ // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a
+ // counted state, allowing a single thread to be suspended multiple times by different users. This
+ // makes mapping into the JVMTI idea of thread suspension difficult. We have decided to split the
+ // difference and ensure that JVMTI tries to treat suspension as the boolean flag as much as
+ // possible with the suspend/resume methods but only do best effort. On the other hand
+ // GetThreadState will be totally accurate as much as possible. This means that calling
+ // ResumeThread on a thread that has state JVMTI_THREAD_STATE_SUSPENDED will not necessarily
+ // cause the thread to wake up if the thread is suspended for the debugger or gc or something.
+ static jvmtiError SuspendSelf(art::Thread* self)
+ REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
+ static jvmtiError SuspendOther(art::Thread* self, jthread target_jthread)
+ REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
+
+ static art::ArtField* context_class_loader_;
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_THREAD_H_
diff --git a/openjdkjvmti/ti_threadgroup.cc b/openjdkjvmti/ti_threadgroup.cc
new file mode 100644
index 0000000..c0597ad
--- /dev/null
+++ b/openjdkjvmti/ti_threadgroup.cc
@@ -0,0 +1,279 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_threadgroup.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "handle_scope-inl.h"
+#include "jni_internal.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "mirror/string.h"
+#include "obj_ptr.h"
+#include "object_lock.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+
+jvmtiError ThreadGroupUtil::GetTopThreadGroups(jvmtiEnv* env,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr) {
+ // We only have a single top group. So we can take the current thread and move upwards.
+ if (group_count_ptr == nullptr || groups_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::Runtime* runtime = art::Runtime::Current();
+ if (runtime == nullptr) {
+ // Must be starting the runtime, or dying.
+ return ERR(WRONG_PHASE);
+ }
+
+ jobject sys_thread_group = runtime->GetSystemThreadGroup();
+ if (sys_thread_group == nullptr) {
+ // Seems we're still starting up.
+ return ERR(WRONG_PHASE);
+ }
+
+ unsigned char* data;
+ jvmtiError result = env->Allocate(sizeof(jthreadGroup), &data);
+ if (result != ERR(NONE)) {
+ return result;
+ }
+
+ jthreadGroup* groups = reinterpret_cast<jthreadGroup*>(data);
+ *groups =
+ reinterpret_cast<JNIEnv*>(art::Thread::Current()->GetJniEnv())->NewLocalRef(sys_thread_group);
+ *groups_ptr = groups;
+ *group_count_ptr = 1;
+
+ return ERR(NONE);
+}
+
+jvmtiError ThreadGroupUtil::GetThreadGroupInfo(jvmtiEnv* env,
+ jthreadGroup group,
+ jvmtiThreadGroupInfo* info_ptr) {
+ if (group == nullptr) {
+ return ERR(INVALID_THREAD_GROUP);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (soa.Env()->IsInstanceOf(group, art::WellKnownClasses::java_lang_ThreadGroup) == JNI_FALSE) {
+ return ERR(INVALID_THREAD_GROUP);
+ }
+
+ art::ObjPtr<art::mirror::Object> obj = soa.Decode<art::mirror::Object>(group);
+
+ // Do the name first. It's the only thing that can fail.
+ {
+ art::ArtField* name_field =
+ art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_name);
+ CHECK(name_field != nullptr);
+ art::ObjPtr<art::mirror::String> name_obj =
+ art::ObjPtr<art::mirror::String>::DownCast(name_field->GetObject(obj));
+ std::string tmp_str;
+ const char* tmp_cstr;
+ if (name_obj == nullptr) {
+ tmp_cstr = "";
+ } else {
+ tmp_str = name_obj->ToModifiedUtf8();
+ tmp_cstr = tmp_str.c_str();
+ }
+ jvmtiError result;
+ JvmtiUniquePtr<char[]> copy = CopyString(env, tmp_cstr, &result);
+ if (copy == nullptr) {
+ return result;
+ }
+ info_ptr->name = copy.release();
+ }
+
+ // Parent.
+ {
+ art::ArtField* parent_field =
+ art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_parent);
+ CHECK(parent_field != nullptr);
+ art::ObjPtr<art::mirror::Object> parent_group = parent_field->GetObject(obj);
+ info_ptr->parent = parent_group == nullptr
+ ? nullptr
+ : soa.AddLocalReference<jthreadGroup>(parent_group);
+ }
+
+ // Max priority.
+ {
+ art::ArtField* prio_field = obj->GetClass()->FindDeclaredInstanceField("maxPriority", "I");
+ CHECK(prio_field != nullptr);
+ info_ptr->max_priority = static_cast<jint>(prio_field->GetInt(obj));
+ }
+
+ // Daemon.
+ {
+ art::ArtField* daemon_field = obj->GetClass()->FindDeclaredInstanceField("daemon", "Z");
+ CHECK(daemon_field != nullptr);
+ info_ptr->is_daemon = daemon_field->GetBoolean(obj) == 0 ? JNI_FALSE : JNI_TRUE;
+ }
+
+ return ERR(NONE);
+}
+
+
+static bool IsInDesiredThreadGroup(art::Handle<art::mirror::Object> desired_thread_group,
+ art::ObjPtr<art::mirror::Object> peer)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ CHECK(desired_thread_group != nullptr);
+
+ art::ArtField* thread_group_field =
+ art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+ DCHECK(thread_group_field != nullptr);
+ art::ObjPtr<art::mirror::Object> group = thread_group_field->GetObject(peer);
+ return (group == desired_thread_group.Get());
+}
+
+static void GetThreads(art::Handle<art::mirror::Object> thread_group,
+ std::vector<art::ObjPtr<art::mirror::Object>>* thread_peers)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) REQUIRES(!art::Locks::thread_list_lock_) {
+ CHECK(thread_group != nullptr);
+
+ art::MutexLock mu(art::Thread::Current(), *art::Locks::thread_list_lock_);
+ for (art::Thread* t : art::Runtime::Current()->GetThreadList()->GetList()) {
+ if (t->IsStillStarting()) {
+ continue;
+ }
+ art::ObjPtr<art::mirror::Object> peer = t->GetPeerFromOtherThread();
+ if (peer == nullptr) {
+ continue;
+ }
+ if (IsInDesiredThreadGroup(thread_group, peer)) {
+ thread_peers->push_back(peer);
+ }
+ }
+}
+
+static void GetChildThreadGroups(art::Handle<art::mirror::Object> thread_group,
+ std::vector<art::ObjPtr<art::mirror::Object>>* thread_groups)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ CHECK(thread_group != nullptr);
+
+ // Get the ThreadGroup[] "groups" out of this thread group...
+ art::ArtField* groups_field =
+ art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_groups);
+ art::ObjPtr<art::mirror::Object> groups_array = groups_field->GetObject(thread_group.Get());
+
+ if (groups_array == nullptr) {
+ return;
+ }
+ CHECK(groups_array->IsObjectArray());
+
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> groups_array_as_array =
+ groups_array->AsObjectArray<art::mirror::Object>();
+
+ // Copy all non-null elements.
+ for (int32_t i = 0; i < groups_array_as_array->GetLength(); ++i) {
+ art::ObjPtr<art::mirror::Object> entry = groups_array_as_array->Get(i);
+ if (entry != nullptr) {
+ thread_groups->push_back(entry);
+ }
+ }
+}
+
+jvmtiError ThreadGroupUtil::GetThreadGroupChildren(jvmtiEnv* env,
+ jthreadGroup group,
+ jint* thread_count_ptr,
+ jthread** threads_ptr,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr) {
+ if (group == nullptr) {
+ return ERR(INVALID_THREAD_GROUP);
+ }
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+
+ if (!soa.Env()->IsInstanceOf(group, art::WellKnownClasses::java_lang_ThreadGroup)) {
+ return ERR(INVALID_THREAD_GROUP);
+ }
+
+ art::StackHandleScope<1> hs(soa.Self());
+ art::Handle<art::mirror::Object> thread_group = hs.NewHandle(
+ soa.Decode<art::mirror::Object>(group));
+
+ art::ObjectLock<art::mirror::Object> thread_group_lock(soa.Self(), thread_group);
+
+ std::vector<art::ObjPtr<art::mirror::Object>> thread_peers;
+ GetThreads(thread_group, &thread_peers);
+
+ std::vector<art::ObjPtr<art::mirror::Object>> thread_groups;
+ GetChildThreadGroups(thread_group, &thread_groups);
+
+ JvmtiUniquePtr<jthread[]> peers_uptr;
+ if (!thread_peers.empty()) {
+ jvmtiError res;
+ peers_uptr = AllocJvmtiUniquePtr<jthread[]>(env, thread_peers.size(), &res);
+ if (peers_uptr == nullptr) {
+ return res;
+ }
+ }
+
+ JvmtiUniquePtr<jthreadGroup[]> group_uptr;
+ if (!thread_groups.empty()) {
+ jvmtiError res;
+ group_uptr = AllocJvmtiUniquePtr<jthreadGroup[]>(env, thread_groups.size(), &res);
+ if (group_uptr == nullptr) {
+ return res;
+ }
+ }
+
+ // Can't fail anymore from here on.
+
+ // Copy data into out buffers.
+ for (size_t i = 0; i != thread_peers.size(); ++i) {
+ peers_uptr[i] = soa.AddLocalReference<jthread>(thread_peers[i]);
+ }
+ for (size_t i = 0; i != thread_groups.size(); ++i) {
+ group_uptr[i] = soa.AddLocalReference<jthreadGroup>(thread_groups[i]);
+ }
+
+ *thread_count_ptr = static_cast<jint>(thread_peers.size());
+ *threads_ptr = peers_uptr.release();
+ *group_count_ptr = static_cast<jint>(thread_groups.size());
+ *groups_ptr = group_uptr.release();
+
+ return ERR(NONE);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_threadgroup.h b/openjdkjvmti/ti_threadgroup.h
new file mode 100644
index 0000000..4911566
--- /dev/null
+++ b/openjdkjvmti/ti_threadgroup.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_THREADGROUP_H_
+#define ART_OPENJDKJVMTI_TI_THREADGROUP_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class ThreadGroupUtil {
+ public:
+ static jvmtiError GetTopThreadGroups(jvmtiEnv* env,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr);
+
+ static jvmtiError GetThreadGroupInfo(jvmtiEnv* env,
+ jthreadGroup group,
+ jvmtiThreadGroupInfo* info_ptr);
+
+ static jvmtiError GetThreadGroupChildren(jvmtiEnv* env,
+ jthreadGroup group,
+ jint* thread_count_ptr,
+ jthread** threads_ptr,
+ jint* group_count_ptr,
+ jthreadGroup** groups_ptr);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_THREADGROUP_H_
diff --git a/openjdkjvmti/ti_timers.cc b/openjdkjvmti/ti_timers.cc
new file mode 100644
index 0000000..24fb041
--- /dev/null
+++ b/openjdkjvmti/ti_timers.cc
@@ -0,0 +1,93 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_timers.h"
+
+#include <limits>
+
+#ifndef __APPLE__
+#include <time.h>
+#else
+#include <sys/time.h>
+#endif
+#include <unistd.h>
+
+#include "art_jvmti.h"
+#include "base/macros.h"
+
+namespace openjdkjvmti {
+
+jvmtiError TimerUtil::GetAvailableProcessors(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jint* processor_count_ptr) {
+ if (processor_count_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ *processor_count_ptr = static_cast<jint>(sysconf(_SC_NPROCESSORS_CONF));
+
+ return ERR(NONE);
+}
+
+jvmtiError TimerUtil::GetTimerInfo(jvmtiEnv* env ATTRIBUTE_UNUSED, jvmtiTimerInfo* info_ptr) {
+ if (info_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ info_ptr->max_value = static_cast<jlong>(std::numeric_limits<uint64_t>::max());
+ info_ptr->may_skip_forward = JNI_TRUE;
+ info_ptr->may_skip_backward = JNI_TRUE;
+ info_ptr->kind = jvmtiTimerKind::JVMTI_TIMER_ELAPSED;
+
+ return ERR(NONE);
+}
+
+jvmtiError TimerUtil::GetTime(jvmtiEnv* env ATTRIBUTE_UNUSED, jlong* nanos_ptr) {
+ if (nanos_ptr == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+#ifndef __APPLE__
+ // Use the same implementation as System.nanoTime.
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ *nanos_ptr = now.tv_sec * 1000000000LL + now.tv_nsec;
+#else
+ // No CLOCK_MONOTONIC support on older Mac OS.
+ struct timeval t;
+ t.tv_sec = t.tv_usec = 0;
+ gettimeofday(&t, NULL);
+ *nanos_ptr = static_cast<jlong>(t.tv_sec)*1000000000LL + static_cast<jlong>(t.tv_usec)*1000LL;
+#endif
+
+ return ERR(NONE);
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_timers.h b/openjdkjvmti/ti_timers.h
new file mode 100644
index 0000000..892205a
--- /dev/null
+++ b/openjdkjvmti/ti_timers.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_TIMERS_H_
+#define ART_OPENJDKJVMTI_TI_TIMERS_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class TimerUtil {
+ public:
+ static jvmtiError GetAvailableProcessors(jvmtiEnv* env, jint* processor_count_ptr);
+
+ static jvmtiError GetTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr);
+
+ static jvmtiError GetTime(jvmtiEnv* env, jlong* nanos_ptr);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_TIMERS_H_
diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
new file mode 100644
index 0000000..1d7f137
--- /dev/null
+++ b/openjdkjvmti/transform.cc
@@ -0,0 +1,152 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include "transform.h"
+
+#include "art_method.h"
+#include "base/array_ref.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "dex_file_types.h"
+#include "events-inl.h"
+#include "gc_root-inl.h"
+#include "globals.h"
+#include "jni_env_ext-inl.h"
+#include "jvalue.h"
+#include "jvmti.h"
+#include "linear_alloc.h"
+#include "mem_map.h"
+#include "mirror/array.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
+#include "mirror/class_loader-inl.h"
+#include "mirror/string-inl.h"
+#include "oat_file.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread_list.h"
+#include "ti_redefine.h"
+#include "transform.h"
+#include "utf.h"
+#include "utils/dex_cache_arrays_layout-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError Transformer::RetransformClassesDirect(
+ ArtJvmTiEnv* env,
+ EventHandler* event_handler,
+ art::Thread* self,
+ /*in-out*/std::vector<ArtClassDefinition>* definitions) {
+ for (ArtClassDefinition& def : *definitions) {
+ jint new_len = -1;
+ unsigned char* new_data = nullptr;
+ art::ArrayRef<const unsigned char> dex_data = def.GetDexData();
+ event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+ self,
+ GetJniEnv(env),
+ def.GetClass(),
+ def.GetLoader(),
+ def.GetName().c_str(),
+ def.GetProtectionDomain(),
+ static_cast<jint>(dex_data.size()),
+ dex_data.data(),
+ /*out*/&new_len,
+ /*out*/&new_data);
+ def.SetNewDexData(env, new_len, new_data);
+ }
+ return OK;
+}
+
+jvmtiError Transformer::RetransformClasses(ArtJvmTiEnv* env,
+ EventHandler* event_handler,
+ art::Runtime* runtime,
+ art::Thread* self,
+ jint class_count,
+ const jclass* classes,
+ /*out*/std::string* error_msg) {
+ if (env == nullptr) {
+ *error_msg = "env was null!";
+ return ERR(INVALID_ENVIRONMENT);
+ } else if (class_count < 0) {
+ *error_msg = "class_count was less then 0";
+ return ERR(ILLEGAL_ARGUMENT);
+ } else if (class_count == 0) {
+ // We don't actually need to do anything. Just return OK.
+ return OK;
+ } else if (classes == nullptr) {
+ *error_msg = "null classes!";
+ return ERR(NULL_POINTER);
+ }
+ // A holder that will Deallocate all the class bytes buffers on destruction.
+ std::vector<ArtClassDefinition> definitions;
+ jvmtiError res = OK;
+ for (jint i = 0; i < class_count; i++) {
+ jboolean is_modifiable = JNI_FALSE;
+ res = env->IsModifiableClass(classes[i], &is_modifiable);
+ if (res != OK) {
+ return res;
+ } else if (!is_modifiable) {
+ return ERR(UNMODIFIABLE_CLASS);
+ }
+ ArtClassDefinition def;
+ res = def.Init(env, classes[i]);
+ if (res != OK) {
+ return res;
+ }
+ definitions.push_back(std::move(def));
+ }
+ res = RetransformClassesDirect(env, event_handler, self, &definitions);
+ if (res != OK) {
+ return res;
+ }
+ return Redefiner::RedefineClassesDirect(env, runtime, self, definitions, error_msg);
+}
+
+// TODO Move this somewhere else, ti_class?
+jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* location) {
+ JNIEnv* jni_env = nullptr;
+ jint ret = env->art_vm->GetEnv(reinterpret_cast<void**>(&jni_env), JNI_VERSION_1_1);
+ if (ret != JNI_OK) {
+ // TODO Different error might be better?
+ return ERR(INTERNAL);
+ }
+ art::ScopedObjectAccess soa(jni_env);
+ art::StackHandleScope<1> hs(art::Thread::Current());
+ art::Handle<art::mirror::Class> hs_klass(hs.NewHandle(soa.Decode<art::mirror::Class>(klass)));
+ const art::DexFile& dex = hs_klass->GetDexFile();
+ *location = dex.GetLocation();
+ return OK;
+}
+
+} // namespace openjdkjvmti
diff --git a/openjdkjvmti/transform.h b/openjdkjvmti/transform.h
new file mode 100644
index 0000000..6bbe60a
--- /dev/null
+++ b/openjdkjvmti/transform.h
@@ -0,0 +1,69 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TRANSFORM_H_
+#define ART_OPENJDKJVMTI_TRANSFORM_H_
+
+#include <string>
+
+#include <jni.h>
+#include "jvmti.h"
+
+#include "art_jvmti.h"
+#include "ti_class_definition.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* location);
+
+class Transformer {
+ public:
+ static jvmtiError RetransformClassesDirect(
+ ArtJvmTiEnv* env,
+ EventHandler* event_handler,
+ art::Thread* self,
+ /*in-out*/std::vector<ArtClassDefinition>* definitions);
+
+ static jvmtiError RetransformClasses(ArtJvmTiEnv* env,
+ EventHandler* event_handler,
+ art::Runtime* runtime,
+ art::Thread* self,
+ jint class_count,
+ const jclass* classes,
+ /*out*/std::string* error_msg);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TRANSFORM_H_
+