diff options
Diffstat (limited to 'runtime/openjdkjvmti')
23 files changed, 1304 insertions, 467 deletions
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index 976a1e7902..c01e3f4152 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -22,6 +22,7 @@ cc_defaults { "OpenjdkJvmTi.cc", "ti_class.cc", "ti_class_definition.cc", + "ti_class_loader.cc", "ti_dump.cc", "ti_field.cc", "ti_heap.cc", diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 417d1041a8..a815a603a7 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -138,6 +138,7 @@ class JvmtiFunctions { } static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } @@ -145,10 +146,12 @@ class JvmtiFunctions { jint request_count, const jthread* request_list, jvmtiError* results) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } @@ -156,14 +159,17 @@ class JvmtiFunctions { jint request_count, const jthread* request_list, jvmtiError* results) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } static jvmtiError StopThread(jvmtiEnv* env, jthread thread, jobject exception) { + ENSURE_HAS_CAP(env, can_signal_thread); return ERR(NOT_IMPLEMENTED); } static jvmtiError InterruptThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_signal_thread); return ERR(NOT_IMPLEMENTED); } @@ -175,6 +181,7 @@ class JvmtiFunctions { jthread thread, jint* owned_monitor_count_ptr, jobject** owned_monitors_ptr) { + ENSURE_HAS_CAP(env, can_get_owned_monitor_info); return ERR(NOT_IMPLEMENTED); } @@ -182,12 +189,14 @@ class JvmtiFunctions { jthread thread, jint* monitor_info_count_ptr, jvmtiMonitorStackDepthInfo** monitor_info_ptr) { + ENSURE_HAS_CAP(env, can_get_owned_monitor_stack_depth_info); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env, jthread thread, jobject* monitor_ptr) { + ENSURE_HAS_CAP(env, can_get_current_contended_monitor); return ERR(NOT_IMPLEMENTED); } @@ -271,6 +280,7 @@ class JvmtiFunctions { } static jvmtiError PopFrame(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_pop_frame); return ERR(NOT_IMPLEMENTED); } @@ -283,30 +293,37 @@ class JvmtiFunctions { } static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) { + ENSURE_HAS_CAP(env, can_generate_frame_pop_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env, jthread thread, jobject value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnInt(jvmtiEnv* env, jthread thread, jint value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnLong(jvmtiEnv* env, jthread thread, jlong value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnFloat(jvmtiEnv* env, jthread thread, jfloat value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnDouble(jvmtiEnv* env, jthread thread, jdouble value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnVoid(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } @@ -316,6 +333,7 @@ class JvmtiFunctions { jobject initial_object, const jvmtiHeapCallbacks* callbacks, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); HeapUtil heap_util(&gObjectTagTable); return heap_util.FollowReferences(env, heap_filter, @@ -402,6 +420,7 @@ class JvmtiFunctions { jobject object, jvmtiObjectReferenceCallback object_reference_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -410,6 +429,7 @@ class JvmtiFunctions { jvmtiStackReferenceCallback stack_ref_callback, jvmtiObjectReferenceCallback object_ref_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -417,6 +437,7 @@ class JvmtiFunctions { jvmtiHeapObjectFilter object_filter, jvmtiHeapObjectCallback heap_object_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -425,6 +446,7 @@ class JvmtiFunctions { jvmtiHeapObjectFilter object_filter, jvmtiHeapObjectCallback heap_object_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -433,6 +455,7 @@ class JvmtiFunctions { jint depth, jint slot, jobject* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -440,6 +463,7 @@ class JvmtiFunctions { jthread thread, jint depth, jobject* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -448,6 +472,7 @@ class JvmtiFunctions { jint depth, jint slot, jint* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -456,6 +481,7 @@ class JvmtiFunctions { jint depth, jint slot, jlong* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -464,6 +490,7 @@ class JvmtiFunctions { jint depth, jint slot, jfloat* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -472,6 +499,7 @@ class JvmtiFunctions { jint depth, jint slot, jdouble* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -480,6 +508,7 @@ class JvmtiFunctions { jint depth, jint slot, jobject value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -488,6 +517,7 @@ class JvmtiFunctions { jint depth, jint slot, jint value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -496,6 +526,7 @@ class JvmtiFunctions { jint depth, jint slot, jlong value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -504,6 +535,7 @@ class JvmtiFunctions { jint depth, jint slot, jfloat value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -512,30 +544,37 @@ class JvmtiFunctions { jint depth, jint slot, jdouble value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { + ENSURE_HAS_CAP(env, can_generate_breakpoint_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { + ENSURE_HAS_CAP(env, can_generate_breakpoint_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_access_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_access_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_modification_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_modification_events); return ERR(NOT_IMPLEMENTED); } @@ -563,6 +602,7 @@ class JvmtiFunctions { } static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) { + ENSURE_HAS_CAP(env, can_get_source_file_name); return ERR(NOT_IMPLEMENTED); } @@ -603,6 +643,7 @@ class JvmtiFunctions { jint* constant_pool_count_ptr, jint* constant_pool_byte_count_ptr, unsigned char** constant_pool_bytes_ptr) { + ENSURE_HAS_CAP(env, can_get_constant_pool); return ERR(NOT_IMPLEMENTED); } @@ -629,10 +670,12 @@ class JvmtiFunctions { static jvmtiError GetSourceDebugExtension(jvmtiEnv* env, jclass klass, char** source_debug_extension_ptr) { + ENSURE_HAS_CAP(env, can_get_source_debug_extension); return ERR(NOT_IMPLEMENTED); } static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) { + ENSURE_HAS_CAP(env, can_retransform_classes); std::string error_msg; jvmtiError res = Transformer::RetransformClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env), art::Runtime::Current(), @@ -649,6 +692,7 @@ class JvmtiFunctions { static jvmtiError RedefineClasses(jvmtiEnv* env, jint class_count, const jvmtiClassDefinition* class_definitions) { + ENSURE_HAS_CAP(env, can_redefine_classes); std::string error_msg; jvmtiError res = Redefiner::RedefineClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env), art::Runtime::Current(), @@ -673,6 +717,7 @@ class JvmtiFunctions { static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env, jobject object, jvmtiMonitorUsage* info_ptr) { + ENSURE_HAS_CAP(env, can_get_monitor_info); return ERR(NOT_IMPLEMENTED); } @@ -703,6 +748,7 @@ class JvmtiFunctions { jclass klass, jfieldID field, jboolean* is_synthetic_ptr) { + ENSURE_HAS_CAP(env, can_get_synthetic_attribute); return FieldUtil::IsFieldSynthetic(env, klass, field, is_synthetic_ptr); } @@ -742,6 +788,7 @@ class JvmtiFunctions { jmethodID method, jint* entry_count_ptr, jvmtiLineNumberEntry** table_ptr) { + ENSURE_HAS_CAP(env, can_get_line_numbers); return MethodUtil::GetLineNumberTable(env, method, entry_count_ptr, table_ptr); } @@ -756,6 +803,7 @@ class JvmtiFunctions { jmethodID method, jint* entry_count_ptr, jvmtiLocalVariableEntry** table_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -763,6 +811,7 @@ class JvmtiFunctions { jmethodID method, jint* bytecode_count_ptr, unsigned char** bytecodes_ptr) { + ENSURE_HAS_CAP(env, can_get_bytecodes); return ERR(NOT_IMPLEMENTED); } @@ -771,6 +820,7 @@ class JvmtiFunctions { } static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr) { + ENSURE_HAS_CAP(env, can_get_synthetic_attribute); return MethodUtil::IsMethodSynthetic(env, method, is_synthetic_ptr); } @@ -779,10 +829,12 @@ class JvmtiFunctions { } static jvmtiError SetNativeMethodPrefix(jvmtiEnv* env, const char* prefix) { + ENSURE_HAS_CAP(env, can_set_native_method_prefix); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetNativeMethodPrefixes(jvmtiEnv* env, jint prefix_count, char** prefixes) { + ENSURE_HAS_CAP(env, can_set_native_method_prefix); return ERR(NOT_IMPLEMENTED); } @@ -855,7 +907,6 @@ class JvmtiFunctions { jthread event_thread, ...) { ENSURE_VALID_ENV(env); - // TODO: Check for capabilities. art::Thread* art_thread = nullptr; if (event_thread != nullptr) { // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD. @@ -1053,18 +1104,22 @@ class JvmtiFunctions { } static jvmtiError GetCurrentThreadCpuTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr) { + ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetCurrentThreadCpuTime(jvmtiEnv* env, jlong* nanos_ptr) { + ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetThreadCpuTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr) { + ENSURE_HAS_CAP(env, can_get_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetThreadCpuTime(jvmtiEnv* env, jthread thread, jlong* nanos_ptr) { + ENSURE_HAS_CAP(env, can_get_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } @@ -1313,6 +1368,7 @@ extern "C" bool ArtPlugin_Initialize() { ThreadUtil::Register(&gEventHandler); ClassUtil::Register(&gEventHandler); DumpUtil::Register(&gEventHandler); + SearchUtil::Register(); runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler); runtime->AddSystemWeakHolder(&gObjectTagTable); @@ -1325,6 +1381,7 @@ extern "C" bool ArtPlugin_Deinitialize() { ThreadUtil::Unregister(); ClassUtil::Unregister(); DumpUtil::Unregister(); + SearchUtil::Unregister(); return true; } diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 256c3a6cec..106165c38b 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -114,6 +114,21 @@ static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) { } 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 jvmtiError CopyString(jvmtiEnv* env, const char* src, unsigned char** copy) { size_t len = strlen(src) + 1; unsigned char* buf; @@ -131,7 +146,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_generate_field_modification_events = 0, .can_generate_field_access_events = 0, .can_get_bytecodes = 0, - .can_get_synthetic_attribute = 0, + .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 0, .can_get_current_contended_monitor = 0, .can_get_monitor_info = 0, @@ -139,7 +154,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_redefine_classes = 1, .can_signal_thread = 0, .can_get_source_file_name = 0, - .can_get_line_numbers = 0, + .can_get_line_numbers = 1, .can_get_source_debug_extension = 0, .can_access_local_variables = 0, .can_maintain_original_method_order = 0, @@ -156,10 +171,10 @@ const jvmtiCapabilities kPotentialCapabilities = { .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 = 0, + .can_generate_vm_object_alloc_events = 1, .can_generate_native_method_bind_events = 0, - .can_generate_garbage_collection_events = 0, - .can_generate_object_free_events = 0, + .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, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index 21ec731ba5..4f5eb0c33f 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -37,96 +37,84 @@ static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e) { } } -template <typename FnType> -ALWAYS_INLINE static inline FnType* GetCallback(ArtJvmTiEnv* env, ArtJvmtiEvent event) { - if (env->event_callbacks == nullptr) { - return nullptr; - } +namespace impl { - // TODO: Add a type check. Can be done, for example, by an explicitly instantiated template - // function. +// Infrastructure to achieve type safety for event dispatch. - switch (event) { - case ArtJvmtiEvent::kVmInit: - return reinterpret_cast<FnType*>(env->event_callbacks->VMInit); - case ArtJvmtiEvent::kVmDeath: - return reinterpret_cast<FnType*>(env->event_callbacks->VMDeath); - case ArtJvmtiEvent::kThreadStart: - return reinterpret_cast<FnType*>(env->event_callbacks->ThreadStart); - case ArtJvmtiEvent::kThreadEnd: - return reinterpret_cast<FnType*>(env->event_callbacks->ThreadEnd); - case ArtJvmtiEvent::kClassFileLoadHookRetransformable: - case ArtJvmtiEvent::kClassFileLoadHookNonRetransformable: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassFileLoadHook); - case ArtJvmtiEvent::kClassLoad: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassLoad); - case ArtJvmtiEvent::kClassPrepare: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassPrepare); - case ArtJvmtiEvent::kVmStart: - return reinterpret_cast<FnType*>(env->event_callbacks->VMStart); - case ArtJvmtiEvent::kException: - return reinterpret_cast<FnType*>(env->event_callbacks->Exception); - case ArtJvmtiEvent::kExceptionCatch: - return reinterpret_cast<FnType*>(env->event_callbacks->ExceptionCatch); - case ArtJvmtiEvent::kSingleStep: - return reinterpret_cast<FnType*>(env->event_callbacks->SingleStep); - case ArtJvmtiEvent::kFramePop: - return reinterpret_cast<FnType*>(env->event_callbacks->FramePop); - case ArtJvmtiEvent::kBreakpoint: - return reinterpret_cast<FnType*>(env->event_callbacks->Breakpoint); - case ArtJvmtiEvent::kFieldAccess: - return reinterpret_cast<FnType*>(env->event_callbacks->FieldAccess); - case ArtJvmtiEvent::kFieldModification: - return reinterpret_cast<FnType*>(env->event_callbacks->FieldModification); - case ArtJvmtiEvent::kMethodEntry: - return reinterpret_cast<FnType*>(env->event_callbacks->MethodEntry); - case ArtJvmtiEvent::kMethodExit: - return reinterpret_cast<FnType*>(env->event_callbacks->MethodExit); - case ArtJvmtiEvent::kNativeMethodBind: - return reinterpret_cast<FnType*>(env->event_callbacks->NativeMethodBind); - case ArtJvmtiEvent::kCompiledMethodLoad: - return reinterpret_cast<FnType*>(env->event_callbacks->CompiledMethodLoad); - case ArtJvmtiEvent::kCompiledMethodUnload: - return reinterpret_cast<FnType*>(env->event_callbacks->CompiledMethodUnload); - case ArtJvmtiEvent::kDynamicCodeGenerated: - return reinterpret_cast<FnType*>(env->event_callbacks->DynamicCodeGenerated); - case ArtJvmtiEvent::kDataDumpRequest: - return reinterpret_cast<FnType*>(env->event_callbacks->DataDumpRequest); - case ArtJvmtiEvent::kMonitorWait: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorWait); - case ArtJvmtiEvent::kMonitorWaited: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorWaited); - case ArtJvmtiEvent::kMonitorContendedEnter: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorContendedEnter); - case ArtJvmtiEvent::kMonitorContendedEntered: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorContendedEntered); - case ArtJvmtiEvent::kResourceExhausted: - return reinterpret_cast<FnType*>(env->event_callbacks->ResourceExhausted); - case ArtJvmtiEvent::kGarbageCollectionStart: - return reinterpret_cast<FnType*>(env->event_callbacks->GarbageCollectionStart); - case ArtJvmtiEvent::kGarbageCollectionFinish: - return reinterpret_cast<FnType*>(env->event_callbacks->GarbageCollectionFinish); - case ArtJvmtiEvent::kObjectFree: - return reinterpret_cast<FnType*>(env->event_callbacks->ObjectFree); - case ArtJvmtiEvent::kVmObjectAlloc: - return reinterpret_cast<FnType*>(env->event_callbacks->VMObjectAlloc); - } - return nullptr; -} +#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) -template <typename ...Args> -inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread*, - ArtJvmtiEvent event, - Args... args ATTRIBUTE_UNUSED) const { - CHECK(event == ArtJvmtiEvent::kClassFileLoadHookRetransformable || - event == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); - LOG(FATAL) << "Incorrect arguments to ClassFileLoadHook!"; +#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 <> +template <ArtJvmtiEvent kEvent> inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, - ArtJvmtiEvent event, JNIEnv* jnienv, jclass class_being_redefined, jobject loader, @@ -136,26 +124,16 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) const { - CHECK(event == ArtJvmtiEvent::kClassFileLoadHookRetransformable || - event == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); - using FnType = void(jvmtiEnv* /* jvmti_env */, - 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 */); + static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || + kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event"); 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 (ShouldDispatch(event, env, thread)) { - jint new_len; - unsigned char* new_data; - FnType* callback = GetCallback<FnType>(env, event); + if (ShouldDispatch<kEvent>(env, thread)) { + jint new_len = 0; + unsigned char* new_data = nullptr; + auto callback = impl::GetCallback<kEvent>(env); callback(env, jnienv, class_being_redefined, @@ -186,28 +164,16 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, } } -template <typename ...Args> +// 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, - ArtJvmtiEvent event, Args... args) const { - switch (event) { - case ArtJvmtiEvent::kClassFileLoadHookRetransformable: - case ArtJvmtiEvent::kClassFileLoadHookNonRetransformable: - return DispatchClassFileLoadHookEvent(thread, event, args...); - default: - return GenericDispatchEvent(thread, event, args...); - } -} - -// TODO Locking of some type! -template <typename ...Args> -inline void EventHandler::GenericDispatchEvent(art::Thread* thread, - ArtJvmtiEvent event, - Args... args) const { using FnType = void(jvmtiEnv*, Args...); for (ArtJvmTiEnv* env : envs) { - if (ShouldDispatch(event, env, thread)) { - FnType* callback = GetCallback<FnType>(env, event); + if (ShouldDispatch<kEvent>(env, thread)) { + FnType* callback = impl::GetCallback<kEvent>(env); if (callback != nullptr) { (*callback)(env, args...); } @@ -215,14 +181,66 @@ inline void EventHandler::GenericDispatchEvent(art::Thread* thread, } } -inline bool EventHandler::ShouldDispatch(ArtJvmtiEvent event, - ArtJvmTiEnv* env, +// 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(event); + bool dispatch = env->event_masks.global_event_mask.Test(kEvent); - if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(event)) { + 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(event); + dispatch = mask != nullptr && mask->Test(kEvent); } return dispatch; } diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index d3f8001d69..34492a91fd 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -206,13 +206,12 @@ class JvmtiAllocationListener : public art::gc::AllocationListener { ScopedLocalRef<jclass> klass( jni_env, jni_env->AddLocalReference<jclass>(obj->Ptr()->GetClass())); - handler_->DispatchEvent(self, - ArtJvmtiEvent::kVmObjectAlloc, - jni_env, - thread.get(), - object.get(), - klass.get(), - static_cast<jlong>(byte_count)); + handler_->DispatchEvent<ArtJvmtiEvent::kVmObjectAlloc>(self, + reinterpret_cast<JNIEnv*>(jni_env), + thread.get(), + object.get(), + klass.get(), + static_cast<jlong>(byte_count)); } } @@ -241,11 +240,11 @@ class JvmtiGcPauseListener : public art::gc::GcPauseListener { finish_enabled_(false) {} void StartPause() OVERRIDE { - handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kGarbageCollectionStart); + handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionStart>(nullptr); } void EndPause() OVERRIDE { - handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kGarbageCollectionFinish); + handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionFinish>(nullptr); } bool IsEnabled() { @@ -303,6 +302,64 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { } } +// 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, @@ -319,8 +376,6 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, } } - // TODO: Capability check. - if (mode != JVMTI_ENABLE && mode != JVMTI_DISABLE) { return ERR(ILLEGAL_ARGUMENT); } @@ -329,6 +384,10 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, 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) { diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h index 8e246ded8e..4e20d1776b 100644 --- a/runtime/openjdkjvmti/events.h +++ b/runtime/openjdkjvmti/events.h @@ -156,9 +156,9 @@ class EventHandler { ArtJvmtiEvent event, jvmtiEventMode mode); - template <typename ...Args> + template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE - inline void DispatchEvent(art::Thread* thread, ArtJvmtiEvent event, Args... args) const; + inline void DispatchEvent(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 @@ -169,8 +169,9 @@ class EventHandler { bool added); private: + template <ArtJvmtiEvent kEvent> ALWAYS_INLINE - static inline bool ShouldDispatch(ArtJvmtiEvent event, ArtJvmTiEnv* env, art::Thread* thread); + static inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread); ALWAYS_INLINE inline bool NeedsEventUpdate(ArtJvmTiEnv* env, @@ -181,14 +182,17 @@ class EventHandler { ALWAYS_INLINE inline void RecalculateGlobalEventMask(ArtJvmtiEvent event); - template <typename ...Args> - ALWAYS_INLINE inline void GenericDispatchEvent(art::Thread* thread, - ArtJvmtiEvent event, - Args... args) const; - template <typename ...Args> + template <ArtJvmtiEvent kEvent> ALWAYS_INLINE inline void DispatchClassFileLoadHookEvent(art::Thread* thread, - ArtJvmtiEvent event, - Args... args) const; + 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); diff --git a/runtime/openjdkjvmti/object_tagging.cc b/runtime/openjdkjvmti/object_tagging.cc index 94cb46a428..b27c2a3834 100644 --- a/runtime/openjdkjvmti/object_tagging.cc +++ b/runtime/openjdkjvmti/object_tagging.cc @@ -207,7 +207,7 @@ void ObjectTagTable::SweepImpl(art::IsMarkedVisitor* visitor) { } void ObjectTagTable::HandleNullSweep(jlong tag) { - event_handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kObjectFree, tag); + event_handler_->DispatchEvent<ArtJvmtiEvent::kObjectFree>(nullptr, tag); } template <typename T, ObjectTagTable::TableUpdateNullTarget kTargetNull> diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index 450f75a813..c14fd84264 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -31,6 +31,8 @@ #include "ti_class.h" +#include "android-base/stringprintf.h" + #include <mutex> #include <unordered_set> @@ -38,34 +40,226 @@ #include "base/macros.h" #include "class_table-inl.h" #include "class_linker.h" +#include "common_throws.h" #include "events-inl.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 "runtime.h" #include "runtime_callbacks.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" #include "thread_list.h" +#include "ti_class_loader.h" +#include "ti_redefine.h" +#include "utils.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; + std::unique_ptr<art::MemMap> map(Redefiner::MoveDataToMemMap(orig_location, + final_len, + final_dex_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::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) { + 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; +} + 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; + } + 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())); + // 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> post_no_redefine_unique_ptr(nullptr); + 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>(initial_dex_file.Size()), + static_cast<const unsigned char*>(initial_dex_file.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*>(initial_dex_file.Begin()); + post_no_redefine_len = initial_dex_file.Size(); + } else { + post_no_redefine_unique_ptr = std::unique_ptr<const unsigned char>(post_no_redefine_dex_data); + 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> final_dex_unique_ptr(nullptr); + 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>(final_dex_data); + DCHECK_GT(final_len, 0); + } + + if (final_dex_data != initial_dex_file.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::Handle<art::mirror::ByteArray> arr(hs.NewHandle( + 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 byte array for initial dex-file bytes. 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->SetOriginalDexFileBytes(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())); - ScopedLocalRef<jclass> jthread( - thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(thread->GetPeer())); + ScopedLocalRef<jthread> thread_jni( + thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer())); { art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); - event_handler->DispatchEvent(thread, - ArtJvmtiEvent::kClassLoad, - reinterpret_cast<JNIEnv*>(thread->GetJniEnv()), - jthread.get(), - jklass.get()); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + jklass.get()); } AddTempClass(thread, jklass.get()); } @@ -78,14 +272,14 @@ struct ClassCallback : public art::ClassLoadCallback { art::Thread* thread = art::Thread::Current(); ScopedLocalRef<jclass> jklass(thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get())); - ScopedLocalRef<jclass> jthread( - thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(thread->GetPeer())); + ScopedLocalRef<jthread> thread_jni( + thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer())); art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); - event_handler->DispatchEvent(thread, - ArtJvmtiEvent::kClassPrepare, - reinterpret_cast<JNIEnv*>(thread->GetJniEnv()), - jthread.get(), - jklass.get()); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + jklass.get()); } } diff --git a/runtime/openjdkjvmti/ti_class_loader.cc b/runtime/openjdkjvmti/ti_class_loader.cc new file mode 100644 index 0000000000..afec0bfac0 --- /dev/null +++ b/runtime/openjdkjvmti/ti_class_loader.cc @@ -0,0 +1,211 @@ +/* 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_jvmti.h" +#include "base/array_slice.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 "object_lock.h" +#include "runtime.h" +#include "ScopedLocalRef.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. + // TODO Should I get the class from the classloader or directly? + 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(); +} + +// TODO Really wishing I had that mirror of java.lang.DexFile now. +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.Get() != 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.Get() == nullptr) { + self->AssertPendingOOMException(); + return nullptr; + } + // Copy the oat-dex field at the start. + // TODO Should I clear this field? + // TODO This is a really crappy thing here with the first element being different. + 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.Get() != 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.Get() != 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/runtime/openjdkjvmti/ti_class_loader.h b/runtime/openjdkjvmti/ti_class_loader.h new file mode 100644 index 0000000000..1ac49886cb --- /dev/null +++ b/runtime/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_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_ +#define ART_RUNTIME_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 "ti_class_definition.h" +#include "thread_list.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_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_ diff --git a/runtime/openjdkjvmti/ti_dump.cc b/runtime/openjdkjvmti/ti_dump.cc index 2ee5c409f6..d9e3ef1bcf 100644 --- a/runtime/openjdkjvmti/ti_dump.cc +++ b/runtime/openjdkjvmti/ti_dump.cc @@ -48,7 +48,7 @@ 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(nullptr, ArtJvmtiEvent::kDataDumpRequest); + event_handler->DispatchEvent<ArtJvmtiEvent::kDataDumpRequest>(nullptr); } EventHandler* event_handler = nullptr; diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc index 7b2521d63a..fe3e52b0c1 100644 --- a/runtime/openjdkjvmti/ti_heap.cc +++ b/runtime/openjdkjvmti/ti_heap.cc @@ -303,11 +303,11 @@ class FollowReferencesHelper FINAL { art::Thread* thread = FindThread(info); if (thread != nullptr) { - art::mirror::Object* thread_obj = thread->GetPeer(); + art::mirror::Object* thread_obj; if (thread->IsStillStarting()) { thread_obj = nullptr; } else { - thread_obj = thread->GetPeer(); + thread_obj = thread->GetPeerFromOtherThread(); } if (thread_obj != nullptr) { ref_info->jni_local.thread_tag = tag_table_->GetTagOrZero(thread_obj); @@ -333,11 +333,11 @@ class FollowReferencesHelper FINAL { art::Thread* thread = FindThread(info); if (thread != nullptr) { - art::mirror::Object* thread_obj = thread->GetPeer(); + art::mirror::Object* thread_obj; if (thread->IsStillStarting()) { thread_obj = nullptr; } else { - thread_obj = thread->GetPeer(); + thread_obj = thread->GetPeerFromOtherThread(); } if (thread_obj != nullptr) { ref_info->stack_local.thread_tag = tag_table_->GetTagOrZero(thread_obj); diff --git a/runtime/openjdkjvmti/ti_monitor.cc b/runtime/openjdkjvmti/ti_monitor.cc index b82768397b..645faea41b 100644 --- a/runtime/openjdkjvmti/ti_monitor.cc +++ b/runtime/openjdkjvmti/ti_monitor.cc @@ -54,7 +54,7 @@ class JvmtiMonitor { JvmtiMonitor() : owner_(nullptr), count_(0) { } - static bool Destroy(art::Thread* self, JvmtiMonitor* monitor) { + 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) { @@ -71,7 +71,7 @@ class JvmtiMonitor { return true; } - void MonitorEnter(art::Thread* self) { + void MonitorEnter(art::Thread* self) NO_THREAD_SAFETY_ANALYSIS { // Check for recursive enter. if (IsOwner(self)) { count_++; @@ -86,7 +86,7 @@ class JvmtiMonitor { count_ = 1; } - bool MonitorExit(art::Thread* self) { + bool MonitorExit(art::Thread* self) NO_THREAD_SAFETY_ANALYSIS { if (!IsOwner(self)) { return false; } diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc index 154406a5db..60371cfafe 100644 --- a/runtime/openjdkjvmti/ti_phase.cc +++ b/runtime/openjdkjvmti/ti_phase.cc @@ -64,7 +64,7 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback { case RuntimePhase::kStart: { art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative); - event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmStart, GetJniEnv()); + event_handler->DispatchEvent<ArtJvmtiEvent::kVmStart>(nullptr, GetJniEnv()); PhaseUtil::current_phase_ = JVMTI_PHASE_START; } break; @@ -72,17 +72,14 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback { { ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread()); art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative); - event_handler->DispatchEvent(nullptr, - ArtJvmtiEvent::kVmInit, - GetJniEnv(), - thread.get()); + 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(nullptr, ArtJvmtiEvent::kVmDeath, GetJniEnv()); + event_handler->DispatchEvent<ArtJvmtiEvent::kVmDeath>(nullptr, GetJniEnv()); PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD; } // TODO: Block events now. @@ -139,4 +136,8 @@ void PhaseUtil::Unregister() { art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gPhaseCallback); } +jvmtiPhase PhaseUtil::GetPhaseUnchecked() { + return PhaseUtil::current_phase_; +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_phase.h b/runtime/openjdkjvmti/ti_phase.h index bd15fa6891..851fc27de5 100644 --- a/runtime/openjdkjvmti/ti_phase.h +++ b/runtime/openjdkjvmti/ti_phase.h @@ -57,6 +57,8 @@ class PhaseUtil { struct PhaseCallback; + static jvmtiPhase GetPhaseUnchecked(); + private: static jvmtiPhase current_phase_; }; diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 34efc502e1..058b93a042 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -48,12 +48,13 @@ #include "jit/jit_code_cache.h" #include "jni_env_ext-inl.h" #include "jvmti_allocator.h" -#include "mirror/class.h" +#include "mirror/class-inl.h" #include "mirror/class_ext.h" #include "mirror/object.h" #include "object_lock.h" #include "runtime.h" #include "ScopedLocalRef.h" +#include "ti_class_loader.h" #include "transform.h" namespace openjdkjvmti { @@ -74,9 +75,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { StackVisitor::StackWalkKind::kIncludeInlinedFrames), allocator_(allocator), obsoleted_methods_(obsoleted_methods), - obsolete_maps_(obsolete_maps), - is_runtime_frame_(false) { - } + obsolete_maps_(obsolete_maps) { } ~ObsoleteMethodStackVisitor() OVERRIDE {} @@ -99,21 +98,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { bool VisitFrame() OVERRIDE REQUIRES(art::Locks::mutator_lock_) { art::ArtMethod* old_method = GetMethod(); - // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt - // works through runtime methods. - bool prev_was_runtime_frame_ = is_runtime_frame_; - is_runtime_frame_ = old_method->IsRuntimeMethod(); if (obsoleted_methods_.find(old_method) != obsoleted_methods_.end()) { - // The check below works since when we deoptimize we set shadow frames for all frames until a - // native/runtime transition and for those set the return PC to a function that will complete - // the deoptimization. This does leave us with the unfortunate side-effect that frames just - // below runtime frames cannot be deoptimized at the moment. - // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt - // works through runtime methods. - // TODO b/33616143 - if (!IsShadowFrame() && prev_was_runtime_frame_) { - LOG(FATAL) << "Deoptimization failed due to runtime method in stack. See b/33616143"; - } // 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"; @@ -136,6 +121,7 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { 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(); obsolete_maps_->insert({old_method, new_obsolete_method}); // Update JIT Data structures to point to the new method. art::jit::Jit* jit = art::Runtime::Current()->GetJit(); @@ -162,9 +148,6 @@ class ObsoleteMethodStackVisitor : public art::StackVisitor { // values in this map must be added to the obsolete_methods_ (and obsolete_dex_caches_) fields of // the redefined classes ClassExt by the caller. std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps_; - // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt - // works through runtime methods. - bool is_runtime_frame_; }; jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED, @@ -386,85 +369,6 @@ jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition return OK; } -// TODO *MAJOR* This should return the actual source java.lang.DexFile object for the klass. -// TODO Make mirror of DexFile and associated types to make this less hellish. -// TODO Make mirror of BaseDexClassLoader and associated types to make this less hellish. -art::mirror::Object* Redefiner::ClassRedefinition::FindSourceDexFileObject( - 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(!driver_->self_->IsExceptionPending()); - art::StackHandleScope<11> hs(driver_->self_); - art::ClassLinker* class_linker = driver_->runtime_->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( - driver_->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(driver_->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(driver_->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())); - 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.Get() != nullptr); - CHECK(!driver_->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(!driver_->self_->IsExceptionPending()); - CHECK(dex_elements_list.Get() != nullptr); - size_t num_elements = dex_elements_list->GetLength(); - art::MutableHandle<art::mirror::Object> current_element( - hs.NewHandle<art::mirror::Object>(nullptr)); - art::MutableHandle<art::mirror::Object> first_dex_file( - hs.NewHandle<art::mirror::Object>(nullptr)); - // Iterate over the DexPathList$Element to find the right one - // TODO Or not ATM just return the first one. - for (size_t i = 0; i < num_elements; i++) { - current_element.Assign(dex_elements_list->Get(i)); - CHECK(current_element.Get() != nullptr); - CHECK(!driver_->self_->IsExceptionPending()); - CHECK(dex_elements_list.Get() != nullptr); - CHECK_EQ(current_element->GetClass(), class_linker->FindClass(driver_->self_, - dex_path_list_element_name, - null_loader)); - // 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. - first_dex_file.Assign(element_dex_file_field->GetObject(current_element.Get())); - if (first_dex_file.Get() != nullptr) { - return first_dex_file.Get(); - } - } - return nullptr; -} - art::mirror::Class* Redefiner::ClassRedefinition::GetMirrorClass() { return driver_->self_->DecodeJObject(klass_)->AsClass(); } @@ -478,39 +382,6 @@ art::mirror::DexCache* Redefiner::ClassRedefinition::CreateNewDexCache( return driver_->runtime_->GetClassLinker()->RegisterDexFile(*dex_file_, loader.Get()); } -// TODO Really wishing I had that mirror of java.lang.DexFile now. -art::mirror::LongArray* Redefiner::ClassRedefinition::AllocateDexFileCookie( - art::Handle<art::mirror::Object> java_dex_file_obj) { - art::StackHandleScope<2> hs(driver_->self_); - // 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. - // TODO Should I get the class from the classloader or directly? - 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); - art::Handle<art::mirror::LongArray> cookie( - hs.NewHandle(internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray())); - // TODO Maybe make these non-fatal. - CHECK(cookie.Get() != nullptr); - CHECK_GE(cookie->GetLength(), 1); - art::Handle<art::mirror::LongArray> new_cookie( - hs.NewHandle(art::mirror::LongArray::Alloc(driver_->self_, cookie->GetLength() + 1))); - if (new_cookie.Get() == nullptr) { - driver_->self_->AssertPendingOOMException(); - return nullptr; - } - // Copy the oat-dex field at the start. - // TODO Should I clear this field? - // TODO This is a really crappy thing here with the first element being different. - new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0)); - new_cookie->SetWithoutChecks<false>( - 1, static_cast<int64_t>(reinterpret_cast<intptr_t>(dex_file_.get()))); - new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1); - return new_cookie.Get(); -} - void Redefiner::RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg) { @@ -520,28 +391,13 @@ void Redefiner::RecordFailure(jvmtiError result, result_ = result; } -// Allocates a ByteArray big enough to store the given number of bytes and copies them from the -// bytes pointer. -static art::mirror::ByteArray* AllocateAndFillBytes(art::Thread* self, - const uint8_t* bytes, - int32_t num_bytes) - REQUIRES_SHARED(art::Locks::mutator_lock_) { - art::StackHandleScope<1> hs(self); - art::Handle<art::mirror::ByteArray> arr(hs.NewHandle( - art::mirror::ByteArray::Alloc(self, num_bytes))); - if (!arr.IsNull()) { - // Copy it in. Just skip if it's null - memcpy(arr->GetData(), bytes, num_bytes); - } - return arr.Get(); -} - art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFileBytes() { // If we have been specifically given a new set of bytes use that if (original_dex_file_.size() != 0) { - return AllocateAndFillBytes(driver_->self_, - &original_dex_file_.At(0), - original_dex_file_.size()); + return art::mirror::ByteArray::AllocateAndFill( + driver_->self_, + reinterpret_cast<const signed char*>(&original_dex_file_.At(0)), + original_dex_file_.size()); } // See if we already have one set. @@ -561,7 +417,10 @@ art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFi 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 AllocateAndFillBytes(driver_->self_, current_dex_file.Begin(), current_dex_file.Size()); + return art::mirror::ByteArray::AllocateAndFill( + driver_->self_, + reinterpret_cast<const signed char*>(current_dex_file.Begin()), + current_dex_file.Size()); } struct CallbackCtx { @@ -587,7 +446,8 @@ void Redefiner::ClassRedefinition::FindAndAllocateObsoleteMethods(art::mirror::C art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking"); art::mirror::ClassExt* ext = art_klass->GetExtData(); CHECK(ext->GetObsoleteMethods() != nullptr); - CallbackCtx ctx(art_klass->GetClassLoader()->GetAllocator()); + art::ClassLinker* linker = driver_->runtime_->GetClassLinker(); + CallbackCtx ctx(linker->GetAllocatorForClassLoader(art_klass->GetClassLoader())); // Add all the declared methods to the map for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) { ctx.obsolete_methods.insert(&m); @@ -631,16 +491,141 @@ void Redefiner::ClassRedefinition::FillObsoleteMethodMap( } } -// TODO It should be possible to only deoptimize the specific obsolete methods. -// TODO ReJitEverything can (sort of) fail. In certain cases it will skip deoptimizing some frames. -// If one of these frames is an obsolete method we have a problem. b/33616143 -// TODO This shouldn't be necessary once we can ensure that the current method is not kept in -// registers across suspend points. -// TODO Pending b/33630159 -void Redefiner::EnsureObsoleteMethodsAreDeoptimized() { - art::ScopedAssertNoThreadSuspension nts("Deoptimizing everything!"); - art::instrumentation::Instrumentation* i = runtime_->GetInstrumentation(); - i->ReJitEverything("libOpenJkdJvmti - Class Redefinition"); +// 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, + const char* name, + art::Signature sig) REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::ArtMethod* m = klass->FindDeclaredVirtualMethod(name, sig, art::kRuntimePointerSize); + if (m == nullptr) { + m = klass->FindDeclaredDirectMethod(name, sig, art::kRuntimePointerSize); + } + return m; +} + +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. + while (new_iter.HasNextStaticField() || new_iter.HasNextInstanceField()) { + new_iter.Next(); + } + // 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(); + if (new_flags != (old_method->GetAccessFlags() & art::kAccValidMethodFlags)) { + 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() { @@ -854,31 +839,85 @@ class RedefinitionDataHolder { DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder); }; +// 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( + int32_t klass_index, + art::Handle<art::mirror::ClassLoader> source_class_loader, + art::Handle<art::mirror::Object> dex_file_obj, + /*out*/RedefinitionDataHolder* holder) { + 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 (int32_t i = 0; i < klass_index; i++) { + if (holder->GetSourceClassLoader(i) == 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(holder->GetNewDexFileCookie(i)); + 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. + holder->SetNewDexFileCookie(klass_index, 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 (int32_t i = 0; i < klass_index; i++) { + // We will let the GC take care of the cookie we allocated for this one. + if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) { + holder->SetNewDexFileCookie(i, new_cookie.Get()); + } + } + } + + return true; +} + bool Redefiner::ClassRedefinition::FinishRemainingAllocations( int32_t klass_index, /*out*/RedefinitionDataHolder* holder) { + art::ScopedObjectAccessUnchecked soa(driver_->self_); art::StackHandleScope<2> hs(driver_->self_); holder->SetMirrorClass(klass_index, GetMirrorClass()); // This shouldn't allocate art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader())); - holder->SetSourceClassLoader(klass_index, loader.Get()); - if (loader.Get() == nullptr) { - // TODO Better error msg. - RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); - return false; - } - art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader))); - holder->SetJavaDexFile(klass_index, dex_file_obj.Get()); - if (dex_file_obj.Get() == nullptr) { - // TODO Better error msg. - RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); - return false; - } - holder->SetNewDexFileCookie(klass_index, AllocateDexFileCookie(dex_file_obj)); - if (holder->GetNewDexFileCookie(klass_index) == nullptr) { - driver_->self_->AssertPendingOOMException(); - driver_->self_->ClearException(); - RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); - return false; + // The bootclasspath is handled specially so it doesn't have a j.l.DexFile. + if (!art::ClassLinker::IsBootClassLoader(soa, loader.Get())) { + holder->SetSourceClassLoader(klass_index, loader.Get()); + art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle( + ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader))); + holder->SetJavaDexFile(klass_index, dex_file_obj.Get()); + if (dex_file_obj.Get() == nullptr) { + // TODO Better error msg. + RecordFailure(ERR(INTERNAL), "Unable to find dex file!"); + return false; + } + // Allocate the new dex file cookie. + if (!AllocateAndRememberNewDexFileCookie(klass_index, loader, dex_file_obj, holder)) { + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); + return false; + } } holder->SetNewDexCache(klass_index, CreateNewDexCache(loader)); if (holder->GetNewDexCache(klass_index) == nullptr) { @@ -965,6 +1004,13 @@ jvmtiError Redefiner::Run() { // cleaned up by the GC eventually. return result_; } + int32_t counter = 0; + for (Redefiner::ClassRedefinition& redef : redefinitions_) { + if (holder.GetSourceClassLoader(counter) == nullptr) { + runtime_->GetClassLinker()->AppendToBootClassPath(self_, redef.GetDexFile()); + } + counter++; + } // 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. art::gc::Heap* heap = runtime_->GetHeap(); @@ -983,24 +1029,20 @@ jvmtiError Redefiner::Run() { // TODO We need to update all debugger MethodIDs so they note the method they point to is // obsolete or implement some other well defined semantics. // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods. - int32_t cnt = 0; + counter = 0; for (Redefiner::ClassRedefinition& redef : redefinitions_) { - art::mirror::Class* klass = holder.GetMirrorClass(cnt); - redef.UpdateJavaDexFile(holder.GetJavaDexFile(cnt), holder.GetNewDexFileCookie(cnt)); + art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition"); + if (holder.GetSourceClassLoader(counter) != nullptr) { + ClassLoaderHelper::UpdateJavaDexFile(holder.GetJavaDexFile(counter), + holder.GetNewDexFileCookie(counter)); + } + art::mirror::Class* klass = holder.GetMirrorClass(counter); // TODO Rewrite so we don't do a stack walk for each and every class. redef.FindAndAllocateObsoleteMethods(klass); - redef.UpdateClass(klass, holder.GetNewDexCache(cnt), holder.GetOriginalDexFileBytes(cnt)); - cnt++; + redef.UpdateClass(klass, holder.GetNewDexCache(counter), + holder.GetOriginalDexFileBytes(counter)); + counter++; } - // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have - // pointers to their ArtMethod's stashed in registers that they then use to attempt to hit the - // DexCache. (b/33630159) - // TODO This can fail (leave some methods optimized) near runtime methods (including - // quick-to-interpreter transition function). - // TODO We probably don't need this at all once we have a way to ensure that the - // current_art_method is never stashed in a (physical) register by the JIT and lost to the - // stack-walker. - EnsureObsoleteMethodsAreDeoptimized(); // TODO Verify the new Class. // TODO Shrink the obsolete method maps if possible? // TODO find appropriate class loader. @@ -1102,24 +1144,6 @@ void Redefiner::ClassRedefinition::UpdateClass( ext->SetOriginalDexFileBytes(original_dex_file); } -void Redefiner::ClassRedefinition::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); - } -} - // This function does all (java) allocations we need to do for the Class being redefined. // TODO Change this name maybe? bool Redefiner::ClassRedefinition::EnsureClassAllocationsFinished() { diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 29a7e1f3ac..3209abbe64 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -96,6 +96,11 @@ class Redefiner { static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); + static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, + jint data_len, + const unsigned char* dex_data, + std::string* error_msg); + private: class ClassRedefinition { public: @@ -122,18 +127,13 @@ class Redefiner { art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_); art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_); - // This finds the java.lang.DexFile we will add the native DexFile to as part of the classpath. - // TODO Make sure the DexFile object returned is the one that the klass_ actually comes from. - art::mirror::Object* FindSourceDexFileObject(art::Handle<art::mirror::ClassLoader> loader) - 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_); - // Allocates and fills the new DexFileCookie - art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> j_dex_file_obj) - REQUIRES_SHARED(art::Locks::mutator_lock_); - // This may return nullptr with a OOME pending if allocation fails. art::mirror::ByteArray* AllocateOrGetOriginalDexFileBytes() REQUIRES_SHARED(art::Locks::mutator_lock_); @@ -145,6 +145,13 @@ class Redefiner { bool FinishRemainingAllocations(int32_t klass_index, /*out*/RedefinitionDataHolder* holder) REQUIRES_SHARED(art::Locks::mutator_lock_); + bool AllocateAndRememberNewDexFileCookie( + int32_t klass_index, + art::Handle<art::mirror::ClassLoader> source_class_loader, + art::Handle<art::mirror::Object> dex_file_obj, + /*out*/RedefinitionDataHolder* holder) + REQUIRES_SHARED(art::Locks::mutator_lock_); + void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) REQUIRES(art::Locks::mutator_lock_); @@ -170,17 +177,11 @@ class Redefiner { // 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. - bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_) { - LOG(WARNING) << "methods are not checked for modification currently"; - return true; - } + // 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 - bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_) { - LOG(WARNING) << "Fields are not checked for modification currently"; - return true; - } + // 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) @@ -234,11 +235,6 @@ class Redefiner { /*out*/std::string* error_msg) REQUIRES_SHARED(art::Locks::mutator_lock_); - static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, - jint data_len, - const unsigned char* dex_data, - std::string* error_msg); - // TODO Put on all the lock qualifiers. jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_); @@ -248,14 +244,6 @@ class Redefiner { REQUIRES_SHARED(art::Locks::mutator_lock_); void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_); - // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have - // pointers to their ArtMethods stashed in registers that they then use to attempt to hit the - // DexCache. - void EnsureObsoleteMethodsAreDeoptimized() - REQUIRES(art::Locks::mutator_lock_) - REQUIRES(!art::Locks::thread_list_lock_, - !art::Locks::classlinker_classes_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); diff --git a/runtime/openjdkjvmti/ti_search.cc b/runtime/openjdkjvmti/ti_search.cc index 913d2b6a74..df80f85ed8 100644 --- a/runtime/openjdkjvmti/ti_search.cc +++ b/runtime/openjdkjvmti/ti_search.cc @@ -34,15 +34,177 @@ #include "jni.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 "obj_ptr-inl.h" #include "runtime.h" +#include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" #include "ScopedLocalRef.h" +#include "ti_phase.h" +#include "thread-inl.h" +#include "thread_list.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->FindDeclaredVirtualMethod( + "getProperty", + "(Ljava/lang/String;)Ljava/lang/String;", + art::kRuntimePointerSize); + DCHECK(get_property != nullptr); + art::ArtMethod* set_property = + properties_class->FindDeclaredVirtualMethod( + "setProperty", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + art::kRuntimePointerSize); + DCHECK(set_property != nullptr); + + // 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(); @@ -78,14 +240,21 @@ jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env ATTRIBUT return ERR(NULL_POINTER); } - art::Runtime* current = art::Runtime::Current(); - if (current == nullptr) { + 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 = current->GetSystemClassLoader(); + + jobject sys_class_loader = art::Runtime::Current()->GetSystemClassLoader(); if (sys_class_loader == nullptr) { - // TODO: Support classpath change in OnLoad. - return ERR(WRONG_PHASE); + // This is unexpected. + return ERR(INTERNAL); } // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside, diff --git a/runtime/openjdkjvmti/ti_search.h b/runtime/openjdkjvmti/ti_search.h index 6a52e80405..cd7b4be28c 100644 --- a/runtime/openjdkjvmti/ti_search.h +++ b/runtime/openjdkjvmti/ti_search.h @@ -32,12 +32,17 @@ #ifndef ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_ #define ART_RUNTIME_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); diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc index 4cf55a6a98..b5a6c6e1ee 100644 --- a/runtime/openjdkjvmti/ti_stack.cc +++ b/runtime/openjdkjvmti/ti_stack.cc @@ -377,7 +377,8 @@ jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env, jvmtiStackInfo& old_stack_info = stack_info_array.get()[i]; jvmtiStackInfo& new_stack_info = stack_info[i]; - jthread thread_peer = current->GetJniEnv()->AddLocalReference<jthread>(threads[i]->GetPeer()); + jthread thread_peer = current->GetJniEnv()->AddLocalReference<jthread>( + threads[i]->GetPeerFromOtherThread()); new_stack_info.thread = thread_peer; if (old_stack_info.frame_count > 0) { @@ -453,7 +454,7 @@ jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env, } // Get the peer, and check whether we know it. - art::ObjPtr<art::mirror::Object> peer = thread->GetPeer(); + 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. diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc index 9f81d6ba97..00d4144415 100644 --- a/runtime/openjdkjvmti/ti_thread.cc +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -61,11 +61,14 @@ struct ThreadCallback : public art::ThreadLifecycleCallback, public art::Runtime } return self->GetJniEnv()->AddLocalReference<jthread>(self->GetPeer()); } - void Post(art::Thread* self, ArtJvmtiEvent type) REQUIRES_SHARED(art::Locks::mutator_lock_) { + 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(self, type, self->GetJniEnv(), thread.get()); + event_handler->DispatchEvent<kEvent>(self, + reinterpret_cast<JNIEnv*>(self->GetJniEnv()), + thread.get()); } void ThreadStart(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { @@ -81,11 +84,11 @@ struct ThreadCallback : public art::ThreadLifecycleCallback, public art::Runtime } return; } - Post(self, ArtJvmtiEvent::kThreadStart); + Post<ArtJvmtiEvent::kThreadStart>(self); } void ThreadDeath(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { - Post(self, ArtJvmtiEvent::kThreadEnd); + Post<ArtJvmtiEvent::kThreadEnd>(self); } void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { @@ -93,7 +96,7 @@ struct ThreadCallback : public art::ThreadLifecycleCallback, public art::Runtime // We moved to VMInit. Report the main thread as started (it was attached early, and must // not be reported until Init. started = true; - Post(art::Thread::Current(), ArtJvmtiEvent::kThreadStart); + Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current()); } } @@ -197,7 +200,7 @@ jvmtiError ThreadUtil::GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadI info_ptr->is_daemon = self->IsDaemon(); - art::ObjPtr<art::mirror::Object> peer = self->GetPeer(); + art::ObjPtr<art::mirror::Object> peer = self->GetPeerFromOtherThread(); // ThreadGroup. if (peer != nullptr) { @@ -455,7 +458,7 @@ jvmtiError ThreadUtil::GetAllThreads(jvmtiEnv* env, continue; } - art::ObjPtr<art::mirror::Object> peer = thread->GetPeer(); + art::ObjPtr<art::mirror::Object> peer = thread->GetPeerFromOtherThread(); if (peer != nullptr) { peers.push_back(peer); } diff --git a/runtime/openjdkjvmti/ti_threadgroup.cc b/runtime/openjdkjvmti/ti_threadgroup.cc index 35b1bfd920..e63ce6576a 100644 --- a/runtime/openjdkjvmti/ti_threadgroup.cc +++ b/runtime/openjdkjvmti/ti_threadgroup.cc @@ -174,7 +174,7 @@ static void GetThreads(art::Handle<art::mirror::Object> thread_group, if (t->IsStillStarting()) { continue; } - art::ObjPtr<art::mirror::Object> peer = t->GetPeer(); + art::ObjPtr<art::mirror::Object> peer = t->GetPeerFromOtherThread(); if (peer == nullptr) { continue; } diff --git a/runtime/openjdkjvmti/transform.cc b/runtime/openjdkjvmti/transform.cc index af4fb7187a..2fec631c00 100644 --- a/runtime/openjdkjvmti/transform.cc +++ b/runtime/openjdkjvmti/transform.cc @@ -68,19 +68,17 @@ jvmtiError Transformer::RetransformClassesDirect( for (ArtClassDefinition& def : *definitions) { jint new_len = -1; unsigned char* new_data = nullptr; - // Static casts are so that we get the right template initialization for the special event - // handling code required by the ClassFileLoadHooks. - gEventHandler.DispatchEvent(self, - ArtJvmtiEvent::kClassFileLoadHookRetransformable, - GetJniEnv(env), - static_cast<jclass>(def.klass), - static_cast<jobject>(def.loader), - static_cast<const char*>(def.name.c_str()), - static_cast<jobject>(def.protection_domain), - static_cast<jint>(def.dex_len), - static_cast<const unsigned char*>(def.dex_data.get()), - static_cast<jint*>(&new_len), - static_cast<unsigned char**>(&new_data)); + gEventHandler.DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + self, + GetJniEnv(env), + def.klass, + def.loader, + def.name.c_str(), + def.protection_domain, + def.dex_len, + static_cast<const unsigned char*>(def.dex_data.get()), + &new_len, + &new_data); def.SetNewDexData(env, new_len, new_data); } return OK; @@ -139,20 +137,6 @@ jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* return OK; } -static 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; -} - jvmtiError Transformer::GetDexDataForRetransformation(ArtJvmTiEnv* env, art::Handle<art::mirror::Class> klass, /*out*/jint* dex_data_len, @@ -195,7 +179,9 @@ jvmtiError Transformer::FillInTransformationData(ArtJvmTiEnv* env, } def->klass = klass; def->loader = soa.AddLocalReference<jobject>(hs_klass->GetClassLoader()); - def->name = art::mirror::Class::ComputeName(hs_klass)->ToModifiedUtf8(); + std::string descriptor_store; + std::string descriptor(hs_klass->GetDescriptor(&descriptor_store)); + def->name = descriptor.substr(1, descriptor.size() - 2); // TODO is this always null? def->protection_domain = nullptr; if (def->dex_data.get() == nullptr) { |