From cefaa14b9343b0169f800a6fe64e60c0500907be Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Mon, 23 Jan 2017 15:04:59 -0800 Subject: [PATCH] ART: Add OnLoad system classloader search support Add support for extension of the system classloader search path during the OnLoad phase. Add test. Bug: 34359699 Test: m test-art-host-run-test-936-search-onload Change-Id: Ie0a5b4bb77999dfae87f955dde40b73ffe053b8e --- runtime/openjdkjvmti/OpenjdkJvmTi.cc | 2 + runtime/openjdkjvmti/ti_phase.cc | 4 + runtime/openjdkjvmti/ti_phase.h | 2 + runtime/openjdkjvmti/ti_search.cc | 179 +++++++++++++++++++++++++++++++- runtime/openjdkjvmti/ti_search.h | 5 + test/936-search-onload/build | 17 +++ test/936-search-onload/expected.txt | 3 + test/936-search-onload/info.txt | 1 + test/936-search-onload/run | 21 ++++ test/936-search-onload/search_onload.cc | 63 +++++++++++ test/936-search-onload/search_onload.h | 30 ++++++ test/936-search-onload/src-ex/A.java | 18 ++++ test/936-search-onload/src/B.java | 18 ++++ test/936-search-onload/src/Main.java | 47 +++++++++ test/Android.bp | 1 + test/ti-agent/common_load.cc | 2 + 16 files changed, 408 insertions(+), 5 deletions(-) create mode 100755 test/936-search-onload/build create mode 100644 test/936-search-onload/expected.txt create mode 100644 test/936-search-onload/info.txt create mode 100755 test/936-search-onload/run create mode 100644 test/936-search-onload/search_onload.cc create mode 100644 test/936-search-onload/search_onload.h create mode 100644 test/936-search-onload/src-ex/A.java create mode 100644 test/936-search-onload/src/B.java create mode 100644 test/936-search-onload/src/Main.java diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index c0c301fae..a815a603a 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -1368,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); @@ -1380,6 +1381,7 @@ extern "C" bool ArtPlugin_Deinitialize() { ThreadUtil::Unregister(); ClassUtil::Unregister(); DumpUtil::Unregister(); + SearchUtil::Unregister(); return true; } diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc index 497028825..60371cfaf 100644 --- a/runtime/openjdkjvmti/ti_phase.cc +++ b/runtime/openjdkjvmti/ti_phase.cc @@ -136,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 bd15fa689..851fc27de 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_search.cc b/runtime/openjdkjvmti/ti_search.cc index 913d2b6a7..df80f85ed 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 gSystemOnloadSegments; + +static art::ObjPtr GetSystemProperties(art::Thread* self, + art::ClassLinker* class_linker) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::ObjPtr 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 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 properties_class = + class_linker->LookupClass(self, "Ljava/util/Properties;", nullptr); + DCHECK(properties_class != nullptr); + + ScopedLocalRef defaults_jobj(self->GetJniEnv(), nullptr); + { + art::ObjPtr props_obj = GetSystemProperties(self, class_linker); + + art::ArtField* defaults_field = + properties_class->FindDeclaredInstanceField("defaults", "Ljava/util/Properties;"); + DCHECK(defaults_field != nullptr); + + art::ObjPtr defaults_obj = defaults_field->GetObject(props_obj); + DCHECK(defaults_obj != nullptr); + defaults_jobj.reset(self->GetJniEnv()->AddLocalReference(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 cp_jobj(self->GetJniEnv(), nullptr); + { + art::ObjPtr 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(cp_key)); + } + + // OK, now get the current value. + std::string str_value; + { + ScopedLocalRef 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 new_val_jobj(self->GetJniEnv(), nullptr); + { + art::ObjPtr 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(new_value)); + } + + // Write to the defaults. + ScopedLocalRef 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 6a52e8040..cd7b4be28 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 + #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/test/936-search-onload/build b/test/936-search-onload/build new file mode 100755 index 000000000..898e2e54a --- /dev/null +++ b/test/936-search-onload/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./default-build "$@" --experimental agents diff --git a/test/936-search-onload/expected.txt b/test/936-search-onload/expected.txt new file mode 100644 index 000000000..2eec8e1aa --- /dev/null +++ b/test/936-search-onload/expected.txt @@ -0,0 +1,3 @@ +B was loaded with boot classloader +A was loaded with system classloader +Done diff --git a/test/936-search-onload/info.txt b/test/936-search-onload/info.txt new file mode 100644 index 000000000..875a5f6ec --- /dev/null +++ b/test/936-search-onload/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/936-search-onload/run b/test/936-search-onload/run new file mode 100755 index 000000000..67923a798 --- /dev/null +++ b/test/936-search-onload/run @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This test checks whether dex files can be injected into parent classloaders. App images preload +# classes, which will make the injection moot. Turn off app images to avoid the issue. + +./default-run "$@" --jvmti \ + --no-app-image diff --git a/test/936-search-onload/search_onload.cc b/test/936-search-onload/search_onload.cc new file mode 100644 index 000000000..2286a467d --- /dev/null +++ b/test/936-search-onload/search_onload.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "search_onload.h" + +#include + +#include "android-base/stringprintf.h" +#include "base/logging.h" +#include "base/macros.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "ScopedUtfChars.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test936SearchOnload { + +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetAllCapabilities(jvmti_env); + + char* dex_loc = getenv("DEX_LOCATION"); + std::string dex1 = android::base::StringPrintf("%s/936-search-onload.jar", dex_loc); + std::string dex2 = android::base::StringPrintf("%s/936-search-onload-ex.jar", dex_loc); + + jvmtiError result = jvmti_env->AddToBootstrapClassLoaderSearch(dex1.c_str()); + if (result != JVMTI_ERROR_NONE) { + printf("Could not add to bootstrap classloader.\n"); + return 1; + } + + result = jvmti_env->AddToSystemClassLoaderSearch(dex2.c_str()); + if (result != JVMTI_ERROR_NONE) { + printf("Could not add to system classloader.\n"); + return 1; + } + + return JNI_OK; +} + +} // namespace Test936SearchOnload +} // namespace art diff --git a/test/936-search-onload/search_onload.h b/test/936-search-onload/search_onload.h new file mode 100644 index 000000000..e55689252 --- /dev/null +++ b/test/936-search-onload/search_onload.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_TEST_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_ +#define ART_TEST_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_ + +#include + +namespace art { +namespace Test936SearchOnload { + +jint OnLoad(JavaVM* vm, char* options, void* reserved); + +} // namespace Test936SearchOnload +} // namespace art + +#endif // ART_TEST_936_SEARCH_ONLOAD_SEARCH_ONLOAD_H_ diff --git a/test/936-search-onload/src-ex/A.java b/test/936-search-onload/src-ex/A.java new file mode 100644 index 000000000..64acb2fcf --- /dev/null +++ b/test/936-search-onload/src-ex/A.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class A { +} \ No newline at end of file diff --git a/test/936-search-onload/src/B.java b/test/936-search-onload/src/B.java new file mode 100644 index 000000000..f1458c3bc --- /dev/null +++ b/test/936-search-onload/src/B.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class B { +} \ No newline at end of file diff --git a/test/936-search-onload/src/Main.java b/test/936-search-onload/src/Main.java new file mode 100644 index 000000000..2e7a87193 --- /dev/null +++ b/test/936-search-onload/src/Main.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Arrays; + +public class Main { + public static void main(String[] args) throws Exception { + doTest(); + } + + private static void doTest() throws Exception { + doTest(true, "B"); + doTest(false, "A"); + System.out.println("Done"); + } + + private static void doTest(boolean boot, String className) throws Exception { + ClassLoader expectedClassLoader; + if (boot) { + expectedClassLoader = Object.class.getClassLoader(); + } else { + expectedClassLoader = ClassLoader.getSystemClassLoader(); + } + + Class c = Class.forName(className, false, ClassLoader.getSystemClassLoader()); + if (c.getClassLoader() != expectedClassLoader) { + throw new RuntimeException(className + "(" + boot + "): " + + c.getClassLoader() + " vs " + expectedClassLoader); + } else { + System.out.println(className + " was loaded with " + (boot ? "boot" : "system") + + " classloader"); + } + } +} diff --git a/test/Android.bp b/test/Android.bp index 287df1375..107064504 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -272,6 +272,7 @@ art_cc_defaults { "929-search/search.cc", "931-agent-thread/agent_thread.cc", "933-misc-events/misc_events.cc", + "936-search-onload/search_onload.cc", ], shared_libs: [ "libbase", diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index c30c2b111..621d45a1b 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -28,6 +28,7 @@ #include "901-hello-ti-agent/basics.h" #include "909-attach-agent/attach.h" +#include "936-search-onload/search_onload.h" namespace art { @@ -112,6 +113,7 @@ static AgentLib agents[] = { { "932-transform-saves", common_retransform::OnLoad, nullptr }, { "934-load-transform", common_retransform::OnLoad, nullptr }, { "935-non-retransformable", common_transform::OnLoad, nullptr }, + { "936-search-onload", Test936SearchOnload::OnLoad, nullptr }, { "937-hello-retransform-package", common_retransform::OnLoad, nullptr }, }; -- 2.11.0