Create a basic agent loading framework.
Currently we only allow agents to be loaded at runtime startup, though
this is expected to change soon.
Test: ./test/run-test --host 900
Change-Id: Id648eaed4bbbe6fdef41d64922d023a4db0bfa54
diff --git a/runtime/Android.mk b/runtime/Android.mk
index 2f8b113..9c6ff5c 100644
--- a/runtime/Android.mk
+++ b/runtime/Android.mk
@@ -177,6 +177,7 @@
thread.cc \
thread_list.cc \
thread_pool.cc \
+ ti/agent.cc \
trace.cc \
transaction.cc \
type_lookup_table.cc \
@@ -370,6 +371,7 @@
stack.h \
thread.h \
thread_state.h \
+ ti/agent.h \
verifier/method_verifier.h
LIBOPENJDKJVM_SRC_FILES := openjdkjvm/OpenjdkJvm.cc
@@ -419,7 +421,7 @@
endif
ifneq ($(4),libart)
ifneq ($(4),libopenjdkjvm)
- $$(error expected libart of libopenjdkjvm for argument 4, received $(4))
+ $$(error expected libart or libopenjdkjvm for argument 4, received $(4))
endif
endif
diff --git a/runtime/base/logging.h b/runtime/base/logging.h
index 6323eee..ac21a3f 100644
--- a/runtime/base/logging.h
+++ b/runtime/base/logging.h
@@ -57,6 +57,7 @@
bool verifier;
bool image;
bool systrace_lock_logging; // Enabled with "-verbose:sys-locks".
+ bool agents;
};
// Global log verbosity setting, initialized by InitLogging.
diff --git a/runtime/experimental_flags.h b/runtime/experimental_flags.h
index fde1a5f..8e5a4ff 100644
--- a/runtime/experimental_flags.h
+++ b/runtime/experimental_flags.h
@@ -26,6 +26,7 @@
// The actual flag values.
enum {
kNone = 0x0000,
+ kAgents = 0x0001, // 0b00000001
};
constexpr ExperimentalFlags() : value_(0x0000) {}
@@ -61,9 +62,15 @@
uint32_t value_;
};
-inline std::ostream& operator<<(std::ostream& stream,
- const ExperimentalFlags& e ATTRIBUTE_UNUSED) {
- stream << "kNone";
+inline std::ostream& operator<<(std::ostream& stream, const ExperimentalFlags& e) {
+ bool started = false;
+ if (e & ExperimentalFlags::kAgents) {
+ stream << (started ? "|" : "") << "kAgents";
+ started = true;
+ }
+ if (!started) {
+ stream << "kNone";
+ }
return stream;
}
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index c7e4f8b..74cf752 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -23,6 +23,7 @@
#include "gc/heap.h"
#include "monitor.h"
#include "runtime.h"
+#include "ti/agent.h"
#include "trace.h"
#include "utils.h"
@@ -90,6 +91,13 @@
.Define({"-Xrunjdwp:_", "-agentlib:jdwp=_"})
.WithType<JDWP::JdwpOptions>()
.IntoKey(M::JdwpOptions)
+ // TODO Re-enable -agentlib: once I have a good way to transform the values.
+ // .Define("-agentlib:_")
+ // .WithType<std::vector<ti::Agent>>().AppendValues()
+ // .IntoKey(M::AgentLib)
+ .Define("-agentpath:_")
+ .WithType<std::vector<ti::Agent>>().AppendValues()
+ .IntoKey(M::AgentPath)
.Define("-Xms_")
.WithType<MemoryKiB>()
.IntoKey(M::MemoryInitialSize)
@@ -583,6 +591,30 @@
args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize));
}
+ 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 "
+ << "implementation. Use at your own risk. Do not attempt to write shipping code "
+ << "that relies on the implementation of any part of this api.";
+ } else if (!args.GetOrDefault(M::AgentLib).empty() || !args.GetOrDefault(M::AgentPath).empty()) {
+ LOG(WARNING) << "agent support has not been enabled. Enable experimental agent "
+ << " support with '-XExperimental:agent'. Ignored options are:";
+ for (auto op : args.GetOrDefault(M::AgentLib)) {
+ if (op.HasArgs()) {
+ LOG(WARNING) << " -agentlib:" << op.GetName() << "=" << op.GetArgs();
+ } else {
+ LOG(WARNING) << " -agentlib:" << op.GetName();
+ }
+ }
+ for (auto op : args.GetOrDefault(M::AgentPath)) {
+ if (op.HasArgs()) {
+ LOG(WARNING) << " -agentpath:" << op.GetName() << "=" << op.GetArgs();
+ } else {
+ LOG(WARNING) << " -agentpath:" << op.GetName();
+ }
+ }
+ }
+
*runtime_options = std::move(args);
return true;
}
@@ -627,6 +659,11 @@
UsageMessage(stream, " -showversion\n");
UsageMessage(stream, " -help\n");
UsageMessage(stream, " -agentlib:jdwp=options\n");
+ // TODO add back in once -agentlib actually does something.
+ // UsageMessage(stream, " -agentlib:library=options (Experimental feature, "
+ // "requires -Xexperimental:agent, some features might not be supported)\n");
+ UsageMessage(stream, " -agentpath:library_path=options (Experimental feature, "
+ "requires -Xexperimental:agent, some features might not be supported)\n");
UsageMessage(stream, "\n");
UsageMessage(stream, "The following extended options are supported:\n");
@@ -703,6 +740,8 @@
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, " -Xexperimental:agents"
+ "(Enable new and experimental agent support)\n");
UsageMessage(stream, "\n");
UsageMessage(stream, "The following previously supported Dalvik options are ignored:\n");
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 68fa0d3..d84cdee 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -130,6 +130,7 @@
#include "signal_set.h"
#include "thread.h"
#include "thread_list.h"
+#include "ti/agent.h"
#include "trace.h"
#include "transaction.h"
#include "utils.h"
@@ -281,6 +282,11 @@
jit_->StopProfileSaver();
}
+ // TODO Maybe do some locking.
+ for (auto& agent : agents_) {
+ agent.Unload();
+ }
+
// Make sure our internal threads are dead before we start tearing down things they're using.
Dbg::StopJdwp();
delete signal_catcher_;
@@ -960,6 +966,13 @@
experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental);
is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode);
+ if (experimental_flags_ & ExperimentalFlags::kAgents) {
+ agents_ = runtime_options.ReleaseOrDefault(Opt::AgentPath);
+ // TODO Add back in -agentlib
+ // for (auto lib : runtime_options.ReleaseOrDefault(Opt::AgentLib)) {
+ // agents_.push_back(lib);
+ // }
+ }
XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption);
heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize),
runtime_options.GetOrDefault(Opt::HeapGrowthLimit),
@@ -1232,6 +1245,20 @@
is_native_bridge_loaded_ = LoadNativeBridge(native_bridge_file_name);
}
+ // Startup agents
+ // TODO Maybe we should start a new thread to run these on. Investigate RI behavior more.
+ for (auto& agent : agents_) {
+ // TODO Check err
+ int res = 0;
+ std::string err = "";
+ ti::Agent::LoadError result = agent.Load(&res, &err);
+ if (result == ti::Agent::kInitializationError) {
+ LOG(FATAL) << "Unable to initialize agent!";
+ } else if (result != ti::Agent::kNoError) {
+ LOG(ERROR) << "Unable to load an agent: " << err;
+ }
+ }
+
VLOG(startup) << "Runtime::Init exiting";
return true;
diff --git a/runtime/runtime.h b/runtime/runtime.h
index c971646..10cc960 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -63,6 +63,9 @@
class String;
class Throwable;
} // namespace mirror
+namespace ti {
+ class Agent;
+} // namespace ti
namespace verifier {
class MethodVerifier;
enum class VerifyMode : int8_t;
@@ -698,6 +701,8 @@
std::string class_path_string_;
std::vector<std::string> properties_;
+ std::vector<ti::Agent> agents_;
+
// 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 b95dfad..1409a4e 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -117,7 +117,9 @@
RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback)
RUNTIME_OPTIONS_KEY (std::string, CpuAbiList)
RUNTIME_OPTIONS_KEY (std::string, Fingerprint)
-RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{none}
+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
// 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/runtime/ti/agent.cc b/runtime/ti/agent.cc
new file mode 100644
index 0000000..41a21f7
--- /dev/null
+++ b/runtime/ti/agent.cc
@@ -0,0 +1,138 @@
+/*
+ * 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 "agent.h"
+#include "java_vm_ext.h"
+#include "runtime.h"
+
+namespace art {
+namespace ti {
+
+const char* AGENT_ON_LOAD_FUNCTION_NAME = "Agent_OnLoad";
+const char* AGENT_ON_ATTACH_FUNCTION_NAME = "Agent_OnAttach";
+const char* AGENT_ON_UNLOAD_FUNCTION_NAME = "Agent_OnUnload";
+
+Agent Agent::Create(std::string arg) {
+ size_t eq = arg.find_first_of('=');
+ if (eq == std::string::npos) {
+ return Agent(arg, "");
+ } else {
+ return Agent(arg.substr(0, eq), arg.substr(eq + 1, arg.length()));
+ }
+}
+
+// TODO We need to acquire some locks probably.
+Agent::LoadError Agent::Load(/*out*/jint* call_res, /*out*/ std::string* error_msg) {
+ DCHECK(call_res != nullptr);
+ DCHECK(error_msg != nullptr);
+ if (IsStarted()) {
+ *error_msg = StringPrintf("the agent at %s has already been started!", name_.c_str());
+ VLOG(agents) << "err: " << *error_msg;
+ return kAlreadyStarted;
+ }
+ LoadError err = DoDlOpen(error_msg);
+ if (err != kNoError) {
+ VLOG(agents) << "err: " << *error_msg;
+ return err;
+ }
+ if (onload_ == nullptr) {
+ *error_msg = StringPrintf("Unable to start agent %s: No Agent_OnLoad function found",
+ name_.c_str());
+ VLOG(agents) << "err: " << *error_msg;
+ return kLoadingError;
+ }
+ // TODO Need to do some checks that we are at a good spot etc.
+ *call_res = onload_(static_cast<JavaVM*>(Runtime::Current()->GetJavaVM()),
+ args_.c_str(),
+ nullptr);
+ if (*call_res != 0) {
+ *error_msg = StringPrintf("Initialization of %s returned non-zero value of %d",
+ name_.c_str(), *call_res);
+ VLOG(agents) << "err: " << *error_msg;
+ return kInitializationError;
+ } else {
+ return kNoError;
+ }
+}
+
+Agent::LoadError Agent::DoDlOpen(/*out*/std::string* error_msg) {
+ DCHECK(error_msg != nullptr);
+ dlopen_handle_ = dlopen(name_.c_str(), RTLD_LAZY);
+ if (dlopen_handle_ == nullptr) {
+ *error_msg = StringPrintf("Unable to dlopen %s: %s", name_.c_str(), dlerror());
+ return kLoadingError;
+ }
+
+ onload_ = reinterpret_cast<AgentOnLoadFunction>(dlsym(dlopen_handle_,
+ AGENT_ON_LOAD_FUNCTION_NAME));
+ if (onload_ == nullptr) {
+ VLOG(agents) << "Unable to find 'Agent_OnLoad' symbol in " << this;
+ }
+ onattach_ = reinterpret_cast<AgentOnAttachFunction>(dlsym(dlopen_handle_,
+ AGENT_ON_ATTACH_FUNCTION_NAME));
+ if (onattach_ == nullptr) {
+ VLOG(agents) << "Unable to find 'Agent_OnAttach' symbol in " << this;
+ }
+ onunload_= reinterpret_cast<AgentOnUnloadFunction>(dlsym(dlopen_handle_,
+ AGENT_ON_UNLOAD_FUNCTION_NAME));
+ if (onunload_ == nullptr) {
+ VLOG(agents) << "Unable to find 'Agent_OnUnload' symbol in " << this;
+ }
+ return kNoError;
+}
+
+// TODO Lock some stuff probably.
+void Agent::Unload() {
+ if (dlopen_handle_ != nullptr) {
+ if (onunload_ != nullptr) {
+ onunload_(Runtime::Current()->GetJavaVM());
+ }
+ dlclose(dlopen_handle_);
+ dlopen_handle_ = nullptr;
+ } else {
+ VLOG(agents) << this << " is not currently loaded!";
+ }
+}
+
+Agent::Agent(const Agent& other)
+ : name_(other.name_),
+ args_(other.args_),
+ dlopen_handle_(other.dlopen_handle_),
+ onload_(other.onload_),
+ onattach_(other.onattach_),
+ onunload_(other.onunload_) {
+ if (other.dlopen_handle_ != nullptr) {
+ dlopen(other.name_.c_str(), 0);
+ }
+}
+
+Agent::~Agent() {
+ if (dlopen_handle_ != nullptr) {
+ dlclose(dlopen_handle_);
+ }
+}
+
+std::ostream& operator<<(std::ostream &os, const Agent* m) {
+ return os << *m;
+}
+
+std::ostream& operator<<(std::ostream &os, Agent const& m) {
+ return os << "Agent { name=\"" << m.name_ << "\", args=\"" << m.args_ << "\", handle="
+ << m.dlopen_handle_ << " }";
+}
+
+} // namespace ti
+} // namespace art
diff --git a/runtime/ti/agent.h b/runtime/ti/agent.h
new file mode 100644
index 0000000..521e21e
--- /dev/null
+++ b/runtime/ti/agent.h
@@ -0,0 +1,130 @@
+/*
+ * 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_TI_AGENT_H_
+#define ART_RUNTIME_TI_AGENT_H_
+
+#include <dlfcn.h>
+#include <jni.h> // for jint, JavaVM* etc declarations
+
+#include "base/stringprintf.h"
+#include "runtime.h"
+#include "utils.h"
+
+namespace art {
+namespace ti {
+
+using AgentOnLoadFunction = jint (*)(JavaVM*, const char*, void*);
+using AgentOnAttachFunction = jint (*)(JavaVM*, const char*, void*);
+using AgentOnUnloadFunction = void (*)(JavaVM*);
+
+class Agent {
+ public:
+ enum LoadError {
+ kNoError, // No error occurred..
+ kAlreadyStarted, // The agent has already been loaded.
+ kLoadingError, // dlopen or dlsym returned an error.
+ kInitializationError, // The entrypoint did not return 0. This might require an abort.
+ };
+
+ bool IsStarted() const {
+ return dlopen_handle_ != nullptr;
+ }
+
+ const std::string& GetName() const {
+ return name_;
+ }
+
+ const std::string& GetArgs() const {
+ return args_;
+ }
+
+ bool HasArgs() const {
+ return !GetArgs().empty();
+ }
+
+ // TODO We need to acquire some locks probably.
+ LoadError Load(/*out*/jint* call_res, /*out*/std::string* error_msg);
+
+ // TODO We need to acquire some locks probably.
+ void Unload();
+
+ // Tries to attach the agent using its OnAttach method. Returns true on success.
+ // TODO We need to acquire some locks probably.
+ LoadError Attach(std::string* error_msg) {
+ // TODO
+ *error_msg = "Attach has not yet been implemented!";
+ return kLoadingError;
+ }
+
+ static Agent Create(std::string arg);
+
+ static Agent Create(std::string name, std::string args) {
+ return Agent(name, args);
+ }
+
+ ~Agent();
+
+ // We need move constructor and copy for vectors
+ Agent(const Agent& other);
+
+ Agent(Agent&& other)
+ : name_(other.name_),
+ args_(other.args_),
+ dlopen_handle_(nullptr),
+ onload_(nullptr),
+ onattach_(nullptr),
+ onunload_(nullptr) {
+ other.dlopen_handle_ = nullptr;
+ other.onload_ = nullptr;
+ other.onattach_ = nullptr;
+ other.onunload_ = nullptr;
+ }
+
+ // We don't need an operator=
+ void operator=(const Agent&) = delete;
+
+ private:
+ Agent(std::string name, std::string args)
+ : name_(name),
+ args_(args),
+ dlopen_handle_(nullptr),
+ onload_(nullptr),
+ onattach_(nullptr),
+ onunload_(nullptr) { }
+
+ LoadError DoDlOpen(/*out*/std::string* error_msg);
+
+ const std::string name_;
+ const std::string args_;
+ void* dlopen_handle_;
+
+ // The entrypoints.
+ AgentOnLoadFunction onload_;
+ AgentOnAttachFunction onattach_;
+ AgentOnUnloadFunction onunload_;
+
+ friend std::ostream& operator<<(std::ostream &os, Agent const& m);
+};
+
+std::ostream& operator<<(std::ostream &os, Agent const& m);
+std::ostream& operator<<(std::ostream &os, const Agent* m);
+
+} // namespace ti
+} // namespace art
+
+#endif // ART_RUNTIME_TI_AGENT_H_
+