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
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index 19c8db5..b57383b 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 @@
};
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 @@
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 9c6ff5c..b31eaf6 100644
--- a/runtime/Android.mk
+++ b/runtime/Android.mk
@@ -164,6 +164,7 @@
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 8e5a4ff..7faa2dc 100644
--- a/runtime/experimental_flags.h
+++ b/runtime/experimental_flags.h
@@ -27,6 +27,7 @@
enum {
kNone = 0x0000,
kAgents = 0x0001, // 0b00000001
+ kRuntimePlugins = 0x0002, // 0b00000010
};
constexpr ExperimentalFlags() : value_(0x0000) {}
@@ -68,6 +69,10 @@
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 c644cde..2401bec 100644
--- a/runtime/java_vm_ext.cc
+++ b/runtime/java_vm_ext.cc
@@ -48,7 +48,7 @@
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 @@
}
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 @@
*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 @@
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 @@
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() {
}
+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 @@
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 @@
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 3d055cd..ed9d3ab 100644
--- a/runtime/java_vm_ext.h
+++ b/runtime/java_vm_ext.h
@@ -36,6 +36,10 @@
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 @@
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 @@
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 1ee1611..40efc89 100644
--- a/runtime/jni_env_ext.cc
+++ b/runtime/jni_env_ext.cc
@@ -45,6 +45,20 @@
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 d4accc3..ac287d4 100644
--- a/runtime/jni_env_ext.h
+++ b/runtime/jni_env_ext.h
@@ -54,6 +54,8 @@
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 74cf752..174da79 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -297,6 +297,9 @@
.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 @@
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 @@
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 0000000..481b1ca
--- /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 0000000..18f3977
--- /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 d84cdee..ddcfb6d 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -287,6 +287,11 @@
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 @@
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 @@
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 @@
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 10cc960..6da60f2 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -83,6 +83,7 @@
class MonitorPool;
class NullPointerHandler;
class OatFileManager;
+class Plugin;
struct RuntimeArgumentMap;
class SignalCatcher;
class StackOverflowHandler;
@@ -702,6 +703,7 @@
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 1409a4e..146afc7 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -120,6 +120,7 @@
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 d31eead..43db31c 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 04bb3c8..47b15c2 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 3315a86..a38cc3d 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 c533422..bb9b415 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