You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
786 lines
32 KiB
786 lines
32 KiB
/* 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,
|
|
*
|
|
* 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 <vector>
|
|
|
|
#include "jvmti.h"
|
|
#include "ti_extension.h"
|
|
|
|
#include "art_jvmti.h"
|
|
#include "events.h"
|
|
#include "jni_id_type.h"
|
|
#include "runtime-inl.h"
|
|
#include "ti_allocator.h"
|
|
#include "ti_class.h"
|
|
#include "ti_ddms.h"
|
|
#include "ti_dump.h"
|
|
#include "ti_heap.h"
|
|
#include "ti_logging.h"
|
|
#include "ti_monitor.h"
|
|
#include "ti_redefine.h"
|
|
#include "ti_search.h"
|
|
#include "transform.h"
|
|
|
|
#include "thread-inl.h"
|
|
|
|
namespace openjdkjvmti {
|
|
|
|
struct CParamInfo {
|
|
const char* name;
|
|
jvmtiParamKind kind;
|
|
jvmtiParamTypes base_type;
|
|
jboolean null_ok;
|
|
|
|
jvmtiParamInfo ToParamInfo(jvmtiEnv* env,
|
|
/*out*/std::vector<JvmtiUniquePtr<char[]>>* char_buffers,
|
|
/*out*/jvmtiError* err) const {
|
|
JvmtiUniquePtr<char[]> param_name = CopyString(env, name, err);
|
|
char* name_ptr = param_name.get();
|
|
char_buffers->push_back(std::move(param_name));
|
|
return jvmtiParamInfo{ name_ptr, kind, base_type, null_ok };
|
|
}
|
|
};
|
|
|
|
jvmtiError ExtensionUtil::GetExtensionFunctions(jvmtiEnv* env,
|
|
jint* extension_count_ptr,
|
|
jvmtiExtensionFunctionInfo** extensions) {
|
|
if (extension_count_ptr == nullptr || extensions == nullptr) {
|
|
return ERR(NULL_POINTER);
|
|
}
|
|
|
|
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;
|
|
|
|
auto add_extension = [&](jvmtiExtensionFunction func,
|
|
const char* id,
|
|
const char* short_description,
|
|
const std::vector<CParamInfo>& params,
|
|
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 = params.size();
|
|
if (!params.empty()) {
|
|
JvmtiUniquePtr<jvmtiParamInfo[]> params_ptr =
|
|
AllocJvmtiUniquePtr<jvmtiParamInfo[]>(env, params.size(), &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 != func_info.param_count; ++i) {
|
|
func_info.params[i] = params[i].ToParamInfo(env, &char_buffers, &error);
|
|
if (error != OK) {
|
|
return error;
|
|
}
|
|
}
|
|
} else {
|
|
func_info.params = nullptr;
|
|
}
|
|
|
|
func_info.error_count = errors.size();
|
|
if (!errors.empty()) {
|
|
JvmtiUniquePtr<jvmtiError[]> errors_ptr =
|
|
AllocJvmtiUniquePtr<jvmtiError[]>(env, errors.size(), &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 != func_info.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.",
|
|
{
|
|
{ "tag", JVMTI_KIND_IN, JVMTI_TYPE_JLONG, false},
|
|
{ "heap_id", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false}
|
|
},
|
|
{ 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.",
|
|
{
|
|
{ "heap_id", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false},
|
|
{ "heap_name", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, false}
|
|
},
|
|
{ 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).",
|
|
{
|
|
{ "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}
|
|
},
|
|
{
|
|
ERR(MUST_POSSESS_CAPABILITY),
|
|
ERR(INVALID_CLASS),
|
|
ERR(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.",
|
|
{
|
|
{ "currently_allocated", JVMTI_KIND_OUT, JVMTI_TYPE_JLONG, false},
|
|
},
|
|
{ ERR(NULL_POINTER) });
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// DDMS extension
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(DDMSUtil::HandleChunk),
|
|
"com.android.art.internal.ddm.process_chunk",
|
|
"Handles a single ddms chunk request and returns a response. The reply data is in the ddms"
|
|
" chunk format. It returns the processed chunk. This is provided for backwards compatibility"
|
|
" reasons only. Agents should avoid making use of this extension when possible and instead"
|
|
" use the other JVMTI entrypoints explicitly.",
|
|
{
|
|
{ "type_in", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
{ "length_in", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
{ "data_in", JVMTI_KIND_IN_BUF, JVMTI_TYPE_JBYTE, true },
|
|
{ "type_out", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
|
|
{ "data_len_out", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
|
|
{ "data_out", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_JBYTE, false }
|
|
},
|
|
{ ERR(NULL_POINTER), ERR(ILLEGAL_ARGUMENT), ERR(OUT_OF_MEMORY) });
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// GetClassLoaderClassDescriptors extension
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(ClassUtil::GetClassLoaderClassDescriptors),
|
|
"com.android.art.class.get_class_loader_class_descriptors",
|
|
"Retrieves a list of all the classes (as class descriptors) that the given class loader is"
|
|
" capable of being the defining class loader for. The return format is a list of"
|
|
" null-terminated descriptor strings of the form \"L/java/lang/Object;\". Each descriptor"
|
|
" will be in the list at most once. If the class_loader is null the bootclassloader will be"
|
|
" used. If the class_loader is not null it must either be a java.lang.BootClassLoader, a"
|
|
" dalvik.system.BaseDexClassLoader or a derived type. The data_out list and all elements"
|
|
" must be deallocated by the caller.",
|
|
{
|
|
{ "class_loader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, true },
|
|
{ "class_descriptor_count_out", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
|
|
{ "data_out", JVMTI_KIND_ALLOC_ALLOC_BUF, JVMTI_TYPE_CCHAR, false },
|
|
},
|
|
{
|
|
ERR(NULL_POINTER),
|
|
ERR(ILLEGAL_ARGUMENT),
|
|
ERR(OUT_OF_MEMORY),
|
|
ERR(NOT_IMPLEMENTED),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// Raw monitors no suspend
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(MonitorUtil::RawMonitorEnterNoSuspend),
|
|
"com.android.art.concurrent.raw_monitor_enter_no_suspend",
|
|
"Normally entering a monitor will not return until both the monitor is locked and the"
|
|
" current thread is not suspended. This method will return once the monitor is locked"
|
|
" even if the thread is suspended. Note that using rawMonitorWait will wait until the"
|
|
" thread is not suspended again on wakeup and so should be avoided.",
|
|
{
|
|
{ "raw_monitor", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CVOID, false },
|
|
},
|
|
{
|
|
ERR(NULL_POINTER),
|
|
ERR(INVALID_MONITOR),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// GetLastError extension
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(LogUtil::GetLastError),
|
|
"com.android.art.misc.get_last_error_message",
|
|
"In some cases the jvmti plugin will log data about errors to the android logcat. These can"
|
|
" be useful to tools so we make (some) of the messages available here as well. This will"
|
|
" fill the given 'msg' buffer with the last non-fatal message associated with this"
|
|
" jvmti-env. Note this is best-effort only, not all log messages will be accessible through"
|
|
" this API. This will return the last error-message from all threads. Care should be taken"
|
|
" interpreting the return value when used with a multi-threaded program. The error message"
|
|
" will only be cleared by a call to 'com.android.art.misc.clear_last_error_message' and will"
|
|
" not be cleared by intervening successful calls. If no (tracked) error message has been"
|
|
" sent since the last call to clear_last_error_message this API will return"
|
|
" JVMTI_ERROR_ABSENT_INFORMATION. Not all failures will cause an error message to be"
|
|
" recorded.",
|
|
{
|
|
{ "msg", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, false },
|
|
},
|
|
{
|
|
ERR(NULL_POINTER),
|
|
ERR(ABSENT_INFORMATION),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// ClearLastError extension
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(LogUtil::ClearLastError),
|
|
"com.android.art.misc.clear_last_error_message",
|
|
"Clears the error message returned by 'com.android.art.misc.get_last_error_message'.",
|
|
{ },
|
|
{ });
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// DumpInternalState
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(DumpUtil::DumpInternalState),
|
|
"com.android.art.misc.get_plugin_internal_state",
|
|
"Gets internal state about the plugin and serializes it to the given msg. "
|
|
"There is no particular format to this message beyond being human readable.",
|
|
{
|
|
{ "msg", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, false },
|
|
},
|
|
{ ERR(NULL_POINTER) });
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// AddToDexClassLoader
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(SearchUtil::AddToDexClassLoader),
|
|
"com.android.art.classloader.add_to_dex_class_loader",
|
|
"Adds a dexfile to a given dalvik.system.BaseDexClassLoader in a manner similar to"
|
|
" AddToSystemClassLoader.",
|
|
{
|
|
{ "classloader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
|
|
{ "segment", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, false },
|
|
},
|
|
{
|
|
ERR(NULL_POINTER),
|
|
ERR(CLASS_LOADER_UNSUPPORTED),
|
|
ERR(ILLEGAL_ARGUMENT),
|
|
ERR(WRONG_PHASE),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// AddToDexClassLoaderInMemory
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(SearchUtil::AddToDexClassLoaderInMemory),
|
|
"com.android.art.classloader.add_to_dex_class_loader_in_memory",
|
|
"Adds a dexfile buffer to a given dalvik.system.BaseDexClassLoader in a manner similar to"
|
|
" AddToSystemClassLoader. This may only be done during the LIVE phase. The buffer is copied"
|
|
" and the caller is responsible for deallocating it after this call.",
|
|
{
|
|
{ "classloader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
|
|
{ "dex_bytes", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false },
|
|
{ "dex_bytes_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
},
|
|
{
|
|
ERR(NULL_POINTER),
|
|
ERR(CLASS_LOADER_UNSUPPORTED),
|
|
ERR(ILLEGAL_ARGUMENT),
|
|
ERR(WRONG_PHASE),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// ChangeArraySize
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::ChangeArraySize),
|
|
"com.android.art.heap.change_array_size",
|
|
"Changes the size of a java array. As far as all JNI and java code is concerned this is"
|
|
" atomic. Must have can_tag_objects capability. If the new length of the array is smaller"
|
|
" than the original length, then the array will be truncated to the new length. Otherwise,"
|
|
" all new slots will be filled with null, 0, or False as appropriate for the array type.",
|
|
{
|
|
{ "array", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
|
|
{ "new_size", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
},
|
|
{
|
|
ERR(NULL_POINTER),
|
|
ERR(MUST_POSSESS_CAPABILITY),
|
|
ERR(ILLEGAL_ARGUMENT),
|
|
ERR(OUT_OF_MEMORY),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// These require index-ids and debuggable to function
|
|
art::Runtime* runtime = art::Runtime::Current();
|
|
if (runtime->GetJniIdType() == art::JniIdType::kIndices &&
|
|
(runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) {
|
|
// IsStructurallyModifiableClass
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(Redefiner::IsStructurallyModifiableClass),
|
|
"com.android.art.class.is_structurally_modifiable_class",
|
|
"Returns whether a class can potentially be 'structurally' redefined using the various"
|
|
" structural redefinition extensions provided.",
|
|
{
|
|
{ "klass", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, false },
|
|
{ "result", JVMTI_KIND_OUT, JVMTI_TYPE_JBOOLEAN, false },
|
|
},
|
|
{
|
|
ERR(INVALID_CLASS),
|
|
ERR(NULL_POINTER),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// StructurallyRedefineClasses
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClasses),
|
|
"com.android.art.class.structurally_redefine_classes",
|
|
"Entrypoint for structural class redefinition. Has the same signature as RedefineClasses."
|
|
" Only supports additive changes, methods and fields may not be removed. Supertypes and"
|
|
" implemented interfaces may not be changed. After calling this"
|
|
" com.android.art.structural_dex_file_load_hook events will be triggered, followed by"
|
|
" re-transformable ClassFileLoadHook events. After this method completes subsequent"
|
|
" RetransformClasses calls will use the input to this function as the initial class"
|
|
" definition.",
|
|
{
|
|
{ "num_classes", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
{ "class_definitions", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, false },
|
|
},
|
|
{
|
|
ERR(CLASS_LOADER_UNSUPPORTED),
|
|
ERR(FAILS_VERIFICATION),
|
|
ERR(ILLEGAL_ARGUMENT),
|
|
ERR(INVALID_CLASS),
|
|
ERR(MUST_POSSESS_CAPABILITY),
|
|
ERR(MUST_POSSESS_CAPABILITY),
|
|
ERR(NULL_POINTER),
|
|
ERR(OUT_OF_MEMORY),
|
|
ERR(UNMODIFIABLE_CLASS),
|
|
ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
|
|
ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
|
|
ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
|
|
ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
|
|
// StructurallyRedefineClassDirect
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClassDirect),
|
|
"com.android.art.UNSAFE.class.structurally_redefine_class_direct",
|
|
"Temporary prototype entrypoint for redefining a single class structurally. Currently this"
|
|
" only supports adding new static fields to a class without any instances."
|
|
" ClassFileLoadHook events will NOT be triggered. This does not currently support creating"
|
|
" obsolete methods. This function only has rudimentary error checking. This should not be"
|
|
" used except for testing.",
|
|
{
|
|
{ "klass", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, false },
|
|
{ "new_def", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false },
|
|
{ "new_def_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
},
|
|
{
|
|
ERR(CLASS_LOADER_UNSUPPORTED),
|
|
ERR(FAILS_VERIFICATION),
|
|
ERR(ILLEGAL_ARGUMENT),
|
|
ERR(INVALID_CLASS),
|
|
ERR(MUST_POSSESS_CAPABILITY),
|
|
ERR(MUST_POSSESS_CAPABILITY),
|
|
ERR(NULL_POINTER),
|
|
ERR(OUT_OF_MEMORY),
|
|
ERR(UNMODIFIABLE_CLASS),
|
|
ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
|
|
ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
|
|
ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
|
|
ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
} else {
|
|
LOG(INFO) << "debuggable & jni-type indices are required to implement structural "
|
|
<< "class redefinition extensions.";
|
|
}
|
|
// SetVerboseFlagExt
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(LogUtil::SetVerboseFlagExt),
|
|
"com.android.art.misc.set_verbose_flag_ext",
|
|
"Sets the verbose flags selected by the 'option' c-string. Valid options are anything that"
|
|
" would be accepted by the -verbose:<option> runtime flag. The verbose selections are turned"
|
|
" on if 'enable' is set to true and disabled otherwise. You may select multiple options at"
|
|
" once using commas just like with the -verbose:<option> flag. For example \"class,deopt,gc\""
|
|
" is equivalent to turning on all of the VLOG(class_linker), VLOG(deopt) and VLOG(gc)"
|
|
" messages.",
|
|
{
|
|
{ "option", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false },
|
|
{ "enable", JVMTI_KIND_IN, JVMTI_TYPE_JBOOLEAN, false },
|
|
},
|
|
{
|
|
ERR(NULL_POINTER),
|
|
ERR(ILLEGAL_ARGUMENT),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
// GetHiddenApiEnforcementPolicy
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(ClassUtil::GetHiddenApiEnforcementPolicy),
|
|
"com.android.art.misc.get_hidden_api_enforcement_policy",
|
|
"Gets the current hiddenapi enforcement policy. Policy values are defined in"
|
|
" `frameworks/base/core/java/android/content/pm/ApplicationInfo.java` as the"
|
|
" HIDDEN_API_ENFORCEMENT_ static fields. See the comments in `art/runtime/hidden_api.h` for"
|
|
" more information. This should be used with"
|
|
" `com.android.art.misc.set_hidden_api_enforcement_policy` in order to restore the"
|
|
" hidden-api state after temporarily toggling it.",
|
|
{
|
|
{ "policy", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
|
|
},
|
|
{
|
|
ERR(NULL_POINTER),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
// SetHiddenApiEnforcementPolicy
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(ClassUtil::SetHiddenApiEnforcementPolicy),
|
|
"com.android.art.misc.set_hidden_api_enforcement_policy",
|
|
"Sets the hiddenapi enforcement policy to the given value. Policy values are defined in"
|
|
" `frameworks/base/core/java/android/content/pm/ApplicationInfo.java` as the"
|
|
" HIDDEN_API_ENFORCEMENT_ static fields. See the comments in `art/runtime/hidden_api.h` for"
|
|
" more information. This API should always be used sparingly and in conjunction with"
|
|
" `com.android.art.misc.get_hidden_api_enforcement_policy` to temporarily toggle"
|
|
" hidden-api on and off as changes are required.",
|
|
{
|
|
{ "policy", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
},
|
|
{
|
|
ERR(ILLEGAL_ARGUMENT),
|
|
});
|
|
if (error != ERR(NONE)) {
|
|
return error;
|
|
}
|
|
// DisableHiddenApiEnforcementPolicy
|
|
error = add_extension(
|
|
reinterpret_cast<jvmtiExtensionFunction>(ClassUtil::DisableHiddenApiEnforcementPolicy),
|
|
"com.android.art.misc.disable_hidden_api_enforcement_policy",
|
|
"Sets the hiddenapi enforcement policy to disabled. This API should always be"
|
|
" used sparingly and in conjunction with"
|
|
" `com.android.art.misc.get_hidden_api_enforcement_policy` and"
|
|
" `com.android.art.misc.set_hidden_api_enforcement_policy` to temporarily"
|
|
" toggle hidden-api on and off as changes are required.",
|
|
{},
|
|
{});
|
|
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 OK;
|
|
}
|
|
|
|
|
|
jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env,
|
|
jint* extension_count_ptr,
|
|
jvmtiExtensionEventInfo** extensions) {
|
|
std::vector<jvmtiExtensionEventInfo> ext_vector;
|
|
|
|
// Holders for allocated values.
|
|
std::vector<JvmtiUniquePtr<char[]>> char_buffers;
|
|
std::vector<JvmtiUniquePtr<jvmtiParamInfo[]>> param_buffers;
|
|
|
|
auto add_extension = [&](ArtJvmtiEvent extension_event_index,
|
|
const char* id,
|
|
const char* short_description,
|
|
const std::vector<CParamInfo>& params) {
|
|
DCHECK(IsExtensionEvent(extension_event_index)) << static_cast<jint>(extension_event_index);
|
|
jvmtiExtensionEventInfo event_info;
|
|
jvmtiError error;
|
|
|
|
event_info.extension_event_index = static_cast<jint>(extension_event_index);
|
|
|
|
JvmtiUniquePtr<char[]> id_ptr = CopyString(env, id, &error);
|
|
if (id_ptr == nullptr) {
|
|
return error;
|
|
}
|
|
event_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;
|
|
}
|
|
event_info.short_description = descr.get();
|
|
char_buffers.push_back(std::move(descr));
|
|
|
|
event_info.param_count = params.size();
|
|
if (!params.empty()) {
|
|
JvmtiUniquePtr<jvmtiParamInfo[]> params_ptr =
|
|
AllocJvmtiUniquePtr<jvmtiParamInfo[]>(env, params.size(), &error);
|
|
if (params_ptr == nullptr) {
|
|
return error;
|
|
}
|
|
event_info.params = params_ptr.get();
|
|
param_buffers.push_back(std::move(params_ptr));
|
|
|
|
for (jint i = 0; i != event_info.param_count; ++i) {
|
|
event_info.params[i] = params[i].ToParamInfo(env, &char_buffers, &error);
|
|
if (error != OK) {
|
|
return error;
|
|
}
|
|
}
|
|
} else {
|
|
event_info.params = nullptr;
|
|
}
|
|
|
|
ext_vector.push_back(event_info);
|
|
|
|
return ERR(NONE);
|
|
};
|
|
|
|
jvmtiError error;
|
|
error = add_extension(
|
|
ArtJvmtiEvent::kDdmPublishChunk,
|
|
"com.android.art.internal.ddm.publish_chunk_safe",
|
|
"Called when there is new ddms information that the agent or other clients can use. The"
|
|
" agent is given the 'type' of the ddms chunk and a 'data_size' byte-buffer in 'data'."
|
|
" The 'data' pointer is only valid for the duration of the publish_chunk event. The agent"
|
|
" is responsible for interpreting the information present in the 'data' buffer. This is"
|
|
" provided for backwards-compatibility support only. Agents should prefer to use relevant"
|
|
" JVMTI events and functions above listening for this event. Previous publish_chunk"
|
|
" event was inherently unsafe since using the JNIEnv could cause deadlocks in some scenarios."
|
|
" The current version does not have these issues.",
|
|
{
|
|
{ "type", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
{ "data_size", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
{ "data", JVMTI_KIND_IN_BUF, JVMTI_TYPE_JBYTE, false },
|
|
});
|
|
if (error != OK) {
|
|
return error;
|
|
}
|
|
error = add_extension(
|
|
ArtJvmtiEvent::kObsoleteObjectCreated,
|
|
"com.android.art.heap.obsolete_object_created",
|
|
"Called when an obsolete object is created.\n"
|
|
"An object becomes obsolete when, due to some jvmti function call all references to the"
|
|
" object are replaced with a reference to a different object. After this call finishes there"
|
|
" will be no strong references to the obsolete object anywere. If the object is retrieved"
|
|
" using GetObjectsWithTags its type (class) may have changed and any data it contains may"
|
|
" have been deleted. This is primarily designed to support memory tracking agents which make"
|
|
" use of the ObjectFree and VMObjectAlloc events for tracking. To support this use-case if"
|
|
" this event is not being handled it will by default act as though the following code was"
|
|
" registered as a handler:\n"
|
|
"\n"
|
|
" void HandleObsoleteObjectCreated(jvmtiEnv* env, jlong* obsolete_tag, jlong* new_tag) {\n"
|
|
" jlong temp = *obsolete_tag;\n"
|
|
" *obsolete_tag = *new_tag;\n"
|
|
" *new_tag = temp;\n"
|
|
" }\n"
|
|
"\n"
|
|
"Note that this event does not support filtering based on thread. This event has the same"
|
|
" restrictions on JNI and JVMTI function calls as the ObjectFree event.\n"
|
|
"\n"
|
|
"Arguments:\n"
|
|
" obsolete_tag: Pointer to the tag the old object (now obsolete) has. Setting the pointer"
|
|
" will update the tag value.\n"
|
|
" new_tag: Pointer to the tag the new object (replacing the obsolete one) has. Setting the"
|
|
" pointer will update the tag value.",
|
|
{
|
|
{ "obsolete_tag", JVMTI_KIND_IN_PTR, JVMTI_TYPE_JLONG, false },
|
|
{ "new_tag", JVMTI_KIND_IN_PTR, JVMTI_TYPE_JLONG, false },
|
|
});
|
|
if (error != OK) {
|
|
return error;
|
|
}
|
|
art::Runtime* runtime = art::Runtime::Current();
|
|
if (runtime->GetJniIdType() == art::JniIdType::kIndices &&
|
|
(runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) {
|
|
error = add_extension(
|
|
ArtJvmtiEvent::kStructuralDexFileLoadHook,
|
|
"com.android.art.class.structural_dex_file_load_hook",
|
|
"Called during class load, after a 'RetransformClasses' call, or after a 'RedefineClasses'"
|
|
" call in order to allow the agent to modify the class. This event is called after any"
|
|
" non-can_retransform_classes ClassFileLoadHookEvents and before any"
|
|
" can_retransform_classes ClassFileLoadHookEvents. The transformations applied are"
|
|
" restricted in the same way that transformations applied via the "
|
|
" 'com.android.art.class.structurally_redefine_classes' extension function. The arguments"
|
|
" to the event are identical to the ones in the ClassFileLoadHook and have the same"
|
|
" semantics.",
|
|
{
|
|
{ "jni_env", JVMTI_KIND_IN, JVMTI_TYPE_JNIENV, false },
|
|
{ "class_being_redefined", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, true },
|
|
{ "loader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
|
|
{ "name", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, false },
|
|
{ "protection_domain", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, true },
|
|
{ "dex_data_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
|
|
{ "dex_data", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false },
|
|
{ "new_dex_data_len", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
|
|
{ "new_dex_data", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, true },
|
|
});
|
|
} else {
|
|
LOG(INFO) << "debuggable & jni-type indices are required to implement structural "
|
|
<< "class redefinition extensions.";
|
|
}
|
|
// Copy into output buffer.
|
|
|
|
*extension_count_ptr = ext_vector.size();
|
|
JvmtiUniquePtr<jvmtiExtensionEventInfo[]> out_data =
|
|
AllocJvmtiUniquePtr<jvmtiExtensionEventInfo[]>(env, ext_vector.size(), &error);
|
|
if (out_data == nullptr) {
|
|
return error;
|
|
}
|
|
memcpy(out_data.get(),
|
|
ext_vector.data(),
|
|
ext_vector.size() * sizeof(jvmtiExtensionEventInfo));
|
|
*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();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
jvmtiError ExtensionUtil::SetExtensionEventCallback(jvmtiEnv* env,
|
|
jint extension_event_index,
|
|
jvmtiExtensionEvent callback,
|
|
EventHandler* event_handler) {
|
|
if (!IsExtensionEvent(extension_event_index)) {
|
|
return ERR(ILLEGAL_ARGUMENT);
|
|
}
|
|
ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(env);
|
|
jvmtiEventMode mode = callback == nullptr ? JVMTI_DISABLE : JVMTI_ENABLE;
|
|
// Lock the event_info_mutex_ while we set the event to make sure it isn't lost by a concurrent
|
|
// change to the normal callbacks.
|
|
{
|
|
art::WriterMutexLock lk(art::Thread::Current(), art_env->event_info_mutex_);
|
|
if (art_env->event_callbacks.get() == nullptr) {
|
|
art_env->event_callbacks.reset(new ArtJvmtiEventCallbacks());
|
|
}
|
|
jvmtiError err = art_env->event_callbacks->Set(extension_event_index, callback);
|
|
if (err != OK) {
|
|
return err;
|
|
}
|
|
}
|
|
return event_handler->SetEvent(art_env,
|
|
/*thread=*/nullptr,
|
|
static_cast<ArtJvmtiEvent>(extension_event_index),
|
|
mode);
|
|
}
|
|
|
|
} // namespace openjdkjvmti
|