diff options
author | 2016-08-11 10:48:03 -0700 | |
---|---|---|
committer | 2016-08-11 13:55:03 -0700 | |
commit | 185d134a3b43ab7529053e965917e0fa74bceba4 (patch) | |
tree | 5d3d0cee3aa4c64fc5f36fd5f648e53f6df984ae | |
parent | 7233c7e752c0d26387d143ee74420e9cd1f09390 (diff) |
Add basic runtime-plugins support.
This allows one to pass shared-libraries on the command line that the
runtime will load as plugins. They have access to runtime code and can
install hooks to add functionality. Currently the only hook they can
touch is JavaVMExt::AddEnvironmentHook to register a callback for
GetEnv(). More hooks might be added in the future.
Test: ./test/run-test 900
Change-Id: I852b4daf5a3fa71e9888722bc07794632c0e5010
-rw-r--r-- | cmdline/cmdline_types.h | 19 | ||||
-rw-r--r-- | runtime/Android.mk | 1 | ||||
-rw-r--r-- | runtime/experimental_flags.h | 5 | ||||
-rw-r--r-- | runtime/java_vm_ext.cc | 42 | ||||
-rw-r--r-- | runtime/java_vm_ext.h | 13 | ||||
-rw-r--r-- | runtime/jni_env_ext.cc | 14 | ||||
-rw-r--r-- | runtime/jni_env_ext.h | 2 | ||||
-rw-r--r-- | runtime/parsed_options.cc | 19 | ||||
-rw-r--r-- | runtime/plugin.cc | 88 | ||||
-rw-r--r-- | runtime/plugin.h | 82 | ||||
-rw-r--r-- | runtime/runtime.cc | 22 | ||||
-rw-r--r-- | runtime/runtime.h | 2 | ||||
-rw-r--r-- | runtime/runtime_options.def | 1 | ||||
-rw-r--r-- | test/900-hello-plugin/expected.txt | 5 | ||||
-rw-r--r-- | test/900-hello-plugin/info.txt | 2 | ||||
-rw-r--r-- | test/900-hello-plugin/load_unload.cc | 33 | ||||
-rwxr-xr-x | test/900-hello-plugin/run | 4 |
17 files changed, 337 insertions, 17 deletions
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index 19c8db520e..b57383b963 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -31,6 +31,7 @@ #include "gc/space/large_object_space.h" #include "jdwp/jdwp.h" #include "jit/profile_saver_options.h" +#include "plugin.h" #include "ti/agent.h" #include "unit.h" @@ -382,6 +383,22 @@ struct CmdlineType<std::string> : CmdlineTypeParser<std::string> { }; template <> +struct CmdlineType<std::vector<Plugin>> : CmdlineTypeParser<std::vector<Plugin>> { + Result Parse(const std::string& args) { + assert(false && "Use AppendValues() for a Plugin vector type"); + return Result::Failure("Unconditional failure: Plugin vector must be appended: " + args); + } + + Result ParseAndAppend(const std::string& args, + std::vector<Plugin>& existing_value) { + existing_value.push_back(Plugin::Create(args)); + return Result::SuccessNoValue(); + } + + static const char* Name() { return "std::vector<Plugin>"; } +}; + +template <> struct CmdlineType<std::vector<ti::Agent>> : CmdlineTypeParser<std::vector<ti::Agent>> { Result Parse(const std::string& args) { assert(false && "Use AppendValues() for an Agent vector type"); @@ -756,6 +773,8 @@ struct CmdlineType<ExperimentalFlags> : CmdlineTypeParser<ExperimentalFlags> { existing = ExperimentalFlags::kNone; } else if (option == "agents") { existing = existing | ExperimentalFlags::kAgents; + } else if (option == "runtime-plugins") { + existing = existing | ExperimentalFlags::kRuntimePlugins; } else { return Result::Failure(std::string("Unknown option '") + option + "'"); } diff --git a/runtime/Android.mk b/runtime/Android.mk index 9c6ff5c134..b31eaf60d8 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -164,6 +164,7 @@ LIBART_COMMON_SRC_FILES := \ offsets.cc \ os_linux.cc \ parsed_options.cc \ + plugin.cc \ primitive.cc \ quick_exception_handler.cc \ quick/inline_method_analyser.cc \ diff --git a/runtime/experimental_flags.h b/runtime/experimental_flags.h index 8e5a4ff233..7faa2dc7e3 100644 --- a/runtime/experimental_flags.h +++ b/runtime/experimental_flags.h @@ -27,6 +27,7 @@ struct ExperimentalFlags { enum { kNone = 0x0000, kAgents = 0x0001, // 0b00000001 + kRuntimePlugins = 0x0002, // 0b00000010 }; constexpr ExperimentalFlags() : value_(0x0000) {} @@ -68,6 +69,10 @@ inline std::ostream& operator<<(std::ostream& stream, const ExperimentalFlags& e stream << (started ? "|" : "") << "kAgents"; started = true; } + if (e & ExperimentalFlags::kRuntimePlugins) { + stream << (started ? "|" : "") << "kRuntimePlugins"; + started = true; + } if (!started) { stream << "kNone"; } diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc index c644cde5db..2401bec9f3 100644 --- a/runtime/java_vm_ext.cc +++ b/runtime/java_vm_ext.cc @@ -48,7 +48,7 @@ static size_t gGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 b static const size_t kWeakGlobalsInitial = 16; // Arbitrary. static const size_t kWeakGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.) -static bool IsBadJniVersion(int version) { +bool JavaVMExt::IsBadJniVersion(int version) { // We don't support JNI_VERSION_1_1. These are the only other valid versions. return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6; } @@ -344,13 +344,6 @@ class JII { } static jint GetEnv(JavaVM* vm, void** env, jint version) { - // GetEnv always returns a JNIEnv* for the most current supported JNI version, - // and unlike other calls that take a JNI version doesn't care if you supply - // JNI_VERSION_1_1, which we don't otherwise support. - if (IsBadJniVersion(version) && version != JNI_VERSION_1_1) { - LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version; - return JNI_EVERSION; - } if (vm == nullptr || env == nullptr) { return JNI_ERR; } @@ -359,8 +352,8 @@ class JII { *env = nullptr; return JNI_EDETACHED; } - *env = thread->GetJniEnv(); - return JNI_OK; + JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); + return raw_vm->HandleGetEnv(env, version); } private: @@ -388,7 +381,7 @@ class JII { const char* thread_name = nullptr; jobject thread_group = nullptr; if (args != nullptr) { - if (IsBadJniVersion(args->version)) { + if (JavaVMExt::IsBadJniVersion(args->version)) { LOG(ERROR) << "Bad JNI version passed to " << (as_daemon ? "AttachCurrentThreadAsDaemon" : "AttachCurrentThread") << ": " << args->version; @@ -436,7 +429,8 @@ JavaVMExt::JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options weak_globals_lock_("JNI weak global reference table lock", kJniWeakGlobalsLock), weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal), allow_accessing_weak_globals_(true), - weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) { + weak_globals_add_condition_("weak globals add condition", weak_globals_lock_), + env_hooks_() { functions = unchecked_functions_; SetCheckJniEnabled(runtime_options.Exists(RuntimeArgumentMap::CheckJni)); } @@ -444,6 +438,26 @@ JavaVMExt::JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options JavaVMExt::~JavaVMExt() { } +jint JavaVMExt::HandleGetEnv(/*out*/void** env, jint version) { + for (GetEnvHook hook : env_hooks_) { + jint res = hook(this, env, version); + if (res == JNI_OK) { + return JNI_OK; + } else if (res != JNI_EVERSION) { + LOG(ERROR) << "Error returned from a plugin GetEnv handler! " << res; + return res; + } + } + LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version; + return JNI_EVERSION; +} + +// Add a hook to handle getting environments from the GetEnv call. +void JavaVMExt::AddEnvironmentHook(GetEnvHook hook) { + CHECK(hook != nullptr) << "environment hooks shouldn't be null!"; + env_hooks_.push_back(hook); +} + void JavaVMExt::JniAbort(const char* jni_function_name, const char* msg) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); @@ -866,7 +880,7 @@ bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, if (version == JNI_ERR) { StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); - } else if (IsBadJniVersion(version)) { + } else if (JavaVMExt::IsBadJniVersion(version)) { StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d", path.c_str(), version); // It's unwise to call dlclose() here, but we can mark it @@ -939,7 +953,7 @@ void JavaVMExt::VisitRoots(RootVisitor* visitor) { extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { ScopedTrace trace(__FUNCTION__); const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args); - if (IsBadJniVersion(args->version)) { + if (JavaVMExt::IsBadJniVersion(args->version)) { LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version; return JNI_EVERSION; } diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h index 3d055cd7ce..ed9d3abfe2 100644 --- a/runtime/java_vm_ext.h +++ b/runtime/java_vm_ext.h @@ -36,6 +36,10 @@ class ParsedOptions; class Runtime; struct RuntimeArgumentMap; +class JavaVMExt; +// Hook definition for runtime plugins. +using GetEnvHook = jint (*)(JavaVMExt* vm, /*out*/void** new_env, jint version); + class JavaVMExt : public JavaVM { public: JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options); @@ -171,6 +175,12 @@ class JavaVMExt : public JavaVM { void TrimGlobals() SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!globals_lock_); + jint HandleGetEnv(/*out*/void** env, jint version); + + void AddEnvironmentHook(GetEnvHook hook); + + static bool IsBadJniVersion(int version); + private: // Return true if self can currently access weak globals. bool MayAccessWeakGlobalsUnlocked(Thread* self) const SHARED_REQUIRES(Locks::mutator_lock_); @@ -215,6 +225,9 @@ class JavaVMExt : public JavaVM { Atomic<bool> allow_accessing_weak_globals_; ConditionVariable weak_globals_add_condition_ GUARDED_BY(weak_globals_lock_); + // TODO Maybe move this to Runtime. + std::vector<GetEnvHook> env_hooks_; + DISALLOW_COPY_AND_ASSIGN(JavaVMExt); }; diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc index 1ee1611ef7..40efc898b8 100644 --- a/runtime/jni_env_ext.cc +++ b/runtime/jni_env_ext.cc @@ -45,6 +45,20 @@ static bool CheckLocalsValid(JNIEnvExt* in) NO_THREAD_SAFETY_ANALYSIS { return in->locals.IsValid(); } +jint JNIEnvExt::GetEnvHandler(JavaVMExt* vm, /*out*/void** env, jint version) { + UNUSED(vm); + // GetEnv always returns a JNIEnv* for the most current supported JNI version, + // and unlike other calls that take a JNI version doesn't care if you supply + // JNI_VERSION_1_1, which we don't otherwise support. + if (JavaVMExt::IsBadJniVersion(version) && version != JNI_VERSION_1_1) { + return JNI_EVERSION; + } + Thread* thread = Thread::Current(); + CHECK(thread != nullptr); + *env = thread->GetJniEnv(); + return JNI_OK; +} + JNIEnvExt* JNIEnvExt::Create(Thread* self_in, JavaVMExt* vm_in) { std::unique_ptr<JNIEnvExt> ret(new JNIEnvExt(self_in, vm_in)); if (CheckLocalsValid(ret.get())) { diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h index d4accc342b..ac287d488a 100644 --- a/runtime/jni_env_ext.h +++ b/runtime/jni_env_ext.h @@ -54,6 +54,8 @@ struct JNIEnvExt : public JNIEnv { static Offset LocalRefCookieOffset(size_t pointer_size); static Offset SelfOffset(size_t pointer_size); + static jint GetEnvHandler(JavaVMExt* vm, /*out*/void** out, jint version); + jobject NewLocalRef(mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_); void DeleteLocalRef(jobject obj) SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 74cf7520c9..174da79030 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -297,6 +297,9 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .IntoKey(M::Experimental) .Define("-Xforce-nb-testing") .IntoKey(M::ForceNativeBridge) + .Define("-Xplugin:_") + .WithType<std::vector<Plugin>>().AppendValues() + .IntoKey(M::Plugins) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", @@ -591,6 +594,18 @@ bool ParsedOptions::DoParse(const RuntimeOptions& options, args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize)); } + if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kRuntimePlugins) { + LOG(WARNING) << "Experimental runtime plugin support has been enabled. No guarantees are made " + << "about stability or usage of this plugin support. Use at your own risk. Do " + << "not attempt to write shipping code that relies on the implementation of " + << "runtime plugins."; + } else if (!args.GetOrDefault(M::Plugins).empty()) { + LOG(WARNING) << "Experimental runtime plugin support has not been enabled. Ignored options: "; + for (auto& op : args.GetOrDefault(M::Plugins)) { + LOG(WARNING) << " -plugin:" << op.GetLibrary(); + } + } + if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kAgents) { LOG(WARNING) << "Experimental runtime agent support has been enabled. No guarantees are made " << "the completeness, accuracy, reliability, or stability of the agent " @@ -740,6 +755,10 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -X[no]image-dex2oat (Whether to create and use a boot image)\n"); UsageMessage(stream, " -Xno-dex-file-fallback " "(Don't fall back to dex files without oat files)\n"); + UsageMessage(stream, " -Xplugin:<library.so> " + "(Load a runtime plugin, requires -Xexperimental:runtime-plugins)\n"); + UsageMessage(stream, " -Xexperimental:runtime-plugins" + "(Enable new and experimental agent support)\n"); UsageMessage(stream, " -Xexperimental:agents" "(Enable new and experimental agent support)\n"); UsageMessage(stream, "\n"); diff --git a/runtime/plugin.cc b/runtime/plugin.cc new file mode 100644 index 0000000000..481b1caa15 --- /dev/null +++ b/runtime/plugin.cc @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "plugin.h" + +#include <dlfcn.h> +#include "base/stringprintf.h" +#include "base/logging.h" + +namespace art { + +const char* PLUGIN_INITIALIZATION_FUNCTION_NAME = "ArtPlugin_Initialize"; +const char* PLUGIN_DEINITIALIZATION_FUNCTION_NAME = "ArtPlugin_Deinitialize"; + +Plugin::Plugin(const Plugin& other) : library_(other.library_), dlopen_handle_(nullptr) { + if (other.IsLoaded()) { + std::string err; + Load(&err); + } +} + +bool Plugin::Load(/*out*/std::string* error_msg) { + DCHECK(!IsLoaded()); + void* res = dlopen(library_.c_str(), RTLD_LAZY); + if (res == nullptr) { + *error_msg = StringPrintf("dlopen failed: %s", dlerror()); + return false; + } + // Get the initializer function + PluginInitializationFunction init = reinterpret_cast<PluginInitializationFunction>( + dlsym(res, PLUGIN_INITIALIZATION_FUNCTION_NAME)); + if (init != nullptr) { + if (!init()) { + dlclose(res); + *error_msg = StringPrintf("Initialization of plugin failed"); + return false; + } + } else { + LOG(WARNING) << this << " does not include an initialization function"; + } + dlopen_handle_ = res; + return true; +} + +bool Plugin::Unload() { + DCHECK(IsLoaded()); + bool ret = true; + void* handle = dlopen_handle_; + PluginDeinitializationFunction deinit = reinterpret_cast<PluginDeinitializationFunction>( + dlsym(handle, PLUGIN_DEINITIALIZATION_FUNCTION_NAME)); + if (deinit != nullptr) { + if (!deinit()) { + LOG(WARNING) << this << " failed deinitialization"; + ret = false; + } + } else { + LOG(WARNING) << this << " does not include a deinitialization function"; + } + dlopen_handle_ = nullptr; + if (dlclose(handle) != 0) { + LOG(ERROR) << this << " failed to dlclose: " << dlerror(); + ret = false; + } + return ret; +} + +std::ostream& operator<<(std::ostream &os, const Plugin* m) { + return os << *m; +} + +std::ostream& operator<<(std::ostream &os, Plugin const& m) { + return os << "Plugin { library=\"" << m.library_ << "\", handle=" << m.dlopen_handle_ << " }"; +} + +} // namespace art diff --git a/runtime/plugin.h b/runtime/plugin.h new file mode 100644 index 0000000000..18f3977bd5 --- /dev/null +++ b/runtime/plugin.h @@ -0,0 +1,82 @@ +/* + * 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_RUNTIME_PLUGIN_H_ +#define ART_RUNTIME_PLUGIN_H_ + +#include <string> +#include "base/logging.h" + +namespace art { + +// This function is loaded from the plugin (if present) and called during runtime initialization. +// By the time this has been called the runtime has been fully initialized but not other native +// libraries have been loaded yet. Failure to initialize is considered a fatal error. +// TODO might want to give initialization function some arguments +using PluginInitializationFunction = bool (*)(); +using PluginDeinitializationFunction = bool (*)(); + +// A class encapsulating a plugin. There is no stable plugin ABI or API and likely never will be. +// TODO Might want to put some locking in this but ATM we only load these at initialization in a +// single-threaded fashion so not much need +class Plugin { + public: + static Plugin Create(std::string lib) { + return Plugin(lib); + } + + bool IsLoaded() const { + return dlopen_handle_ != nullptr; + } + + const std::string& GetLibrary() const { + return library_; + } + + bool Load(/*out*/std::string* error_msg); + bool Unload(); + + + ~Plugin() { + if (IsLoaded() && !Unload()) { + LOG(ERROR) << "Error unloading " << this; + } + } + + Plugin(const Plugin& other); + + // Create move constructor for putting this in a list + Plugin(Plugin&& other) + : library_(other.library_), + dlopen_handle_(other.dlopen_handle_) { + other.dlopen_handle_ = nullptr; + } + + private: + explicit Plugin(std::string library) : library_(library), dlopen_handle_(nullptr) { } + + std::string library_; + void* dlopen_handle_; + + friend std::ostream& operator<<(std::ostream &os, Plugin const& m); +}; + +std::ostream& operator<<(std::ostream &os, Plugin const& m); +std::ostream& operator<<(std::ostream &os, const Plugin* m); + +} // namespace art + +#endif // ART_RUNTIME_PLUGIN_H_ diff --git a/runtime/runtime.cc b/runtime/runtime.cc index d84cdee320..ddcfb6d5aa 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -287,6 +287,11 @@ Runtime::~Runtime() { agent.Unload(); } + // TODO Maybe do some locking + for (auto& plugin : plugins_) { + plugin.Unload(); + } + // Make sure our internal threads are dead before we start tearing down things they're using. Dbg::StopJdwp(); delete signal_catcher_; @@ -966,6 +971,9 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental); is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode); + if (experimental_flags_ & ExperimentalFlags::kRuntimePlugins) { + plugins_ = runtime_options.ReleaseOrDefault(Opt::Plugins); + } if (experimental_flags_ & ExperimentalFlags::kAgents) { agents_ = runtime_options.ReleaseOrDefault(Opt::AgentPath); // TODO Add back in -agentlib @@ -1097,6 +1105,10 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { java_vm_ = new JavaVMExt(this, runtime_options); + // Add the JniEnv handler. + // TODO Refactor this stuff. + java_vm_->AddEnvironmentHook(JNIEnvExt::GetEnvHandler); + Thread::Startup(); // ClassLinker needs an attached thread, but we can't fully attach a thread without creating @@ -1213,6 +1225,16 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { pre_allocated_NoClassDefFoundError_ = GcRoot<mirror::Throwable>(self->GetException()); self->ClearException(); + // Runtime initialization is largely done now. + // We load plugins first since that can modify the runtime state slightly. + // Load all plugins + for (auto& plugin : plugins_) { + std::string err; + if (!plugin.Load(&err)) { + LOG(FATAL) << plugin << " failed to load: " << err; + } + } + // Look for a native bridge. // // The intended flow here is, in the case of a running system: diff --git a/runtime/runtime.h b/runtime/runtime.h index 10cc9605ba..6da60f27a3 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -83,6 +83,7 @@ class MonitorList; class MonitorPool; class NullPointerHandler; class OatFileManager; +class Plugin; struct RuntimeArgumentMap; class SignalCatcher; class StackOverflowHandler; @@ -702,6 +703,7 @@ class Runtime { std::vector<std::string> properties_; std::vector<ti::Agent> agents_; + std::vector<Plugin> plugins_; // The default stack size for managed threads created by the runtime. size_t default_stack_size_; diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 1409a4efd8..146afc7ad8 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -120,6 +120,7 @@ RUNTIME_OPTIONS_KEY (std::string, Fingerprint) RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{none, agents} RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentLib) // -agentlib:<libname>=<options>, Requires -Xexperimental:agents RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentPath) // -agentpath:<libname>=<options>, Requires -Xexperimental:agents +RUNTIME_OPTIONS_KEY (std::vector<Plugin>, Plugins) // -Xplugin:<library> Requires -Xexperimental:runtime-plugins // Not parse-able from command line, but can be provided explicitly. // (Do not add anything here that is defined in ParsedOptions::MakeParser) diff --git a/test/900-hello-plugin/expected.txt b/test/900-hello-plugin/expected.txt index d31eead3d7..43db31c722 100644 --- a/test/900-hello-plugin/expected.txt +++ b/test/900-hello-plugin/expected.txt @@ -1,3 +1,8 @@ +ArtPlugin_Initialize called in test 900 Agent_OnLoad called with options "test_900" +GetEnvHandler called in test 900 +GetEnvHandler called with version 0x900fffff +GetEnv returned '900' environment! Hello, world! Agent_OnUnload called +ArtPlugin_Deinitialize called in test 900 diff --git a/test/900-hello-plugin/info.txt b/test/900-hello-plugin/info.txt index 04bb3c8294..47b15c2e6a 100644 --- a/test/900-hello-plugin/info.txt +++ b/test/900-hello-plugin/info.txt @@ -1,2 +1,2 @@ -Test that agents are loaded. +Tests that agents and plugins are loaded. diff --git a/test/900-hello-plugin/load_unload.cc b/test/900-hello-plugin/load_unload.cc index 3315a86e6f..a38cc3d6ac 100644 --- a/test/900-hello-plugin/load_unload.cc +++ b/test/900-hello-plugin/load_unload.cc @@ -23,10 +23,41 @@ namespace art { -extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm ATTRIBUTE_UNUSED, +constexpr jint TEST_900_ENV_VERSION_NUMBER = 0x900FFFFF; +constexpr uintptr_t ENV_VALUE = 900; + +// Allow this library to be used as a plugin too so we can test the stack. +static jint GetEnvHandler(JavaVMExt* vm ATTRIBUTE_UNUSED, void** new_env, jint version) { + printf("%s called in test 900\n", __func__); + if (version != TEST_900_ENV_VERSION_NUMBER) { + return JNI_EVERSION; + } + printf("GetEnvHandler called with version 0x%x\n", version); + *new_env = reinterpret_cast<void*>(ENV_VALUE); + return JNI_OK; +} + +extern "C" bool ArtPlugin_Initialize() { + printf("%s called in test 900\n", __func__); + Runtime::Current()->GetJavaVM()->AddEnvironmentHook(GetEnvHandler); + return true; +} + +extern "C" bool ArtPlugin_Deinitialize() { + printf("%s called in test 900\n", __func__); + return true; +} + +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) { printf("Agent_OnLoad called with options \"%s\"\n", options); + uintptr_t env = 0; + jint res = vm->GetEnv(reinterpret_cast<void**>(&env), TEST_900_ENV_VERSION_NUMBER); + if (res != JNI_OK) { + printf("GetEnv(TEST_900_ENV_VERSION_NUMBER) returned non-zero\n"); + } + printf("GetEnv returned '%" PRIdPTR "' environment!\n", env); return 0; } diff --git a/test/900-hello-plugin/run b/test/900-hello-plugin/run index c533422274..bb9b41555d 100755 --- a/test/900-hello-plugin/run +++ b/test/900-hello-plugin/run @@ -15,4 +15,6 @@ # limitations under the License. ./default-run "$@" --experimental agents \ - --runtime-option -agentpath:libartagentd.so=test_900 + --experimental runtime-plugins \ + --runtime-option -agentpath:libartagentd.so=test_900 \ + --android-runtime-option -Xplugin:libartagentd.so |