Enable agent attaching during live phase
This is the ART part, the plumbing from VMDebug_attachAgent() to
actually loading the agent into the runtime.
Test: m test-art-host
Bug: 31682382
Change-Id: I3ccc67aa050c1f78278882128983686ed44ddec2
diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc
index b0aba59..0251776 100644
--- a/runtime/common_throws.cc
+++ b/runtime/common_throws.cc
@@ -686,6 +686,15 @@
va_end(args);
}
+// SecurityException
+
+void ThrowSecurityException(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ ThrowException("Ljava/lang/SecurityException;", nullptr, fmt, &args);
+ va_end(args);
+}
+
// Stack overflow.
void ThrowStackOverflowError(Thread* self) {
diff --git a/runtime/common_throws.h b/runtime/common_throws.h
index 5d0bc12..76ea2ae 100644
--- a/runtime/common_throws.h
+++ b/runtime/common_throws.h
@@ -215,6 +215,12 @@
__attribute__((__format__(__printf__, 1, 2)))
REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
+// SecurityException
+
+void ThrowSecurityException(const char* fmt, ...)
+ __attribute__((__format__(__printf__, 1, 2)))
+ REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
+
// Stack overflow.
void ThrowStackOverflowError(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index e2b8f51..1da888e 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -551,6 +551,10 @@
gJdwpAllowed = allowed;
}
+bool Dbg::IsJdwpAllowed() {
+ return gJdwpAllowed;
+}
+
DebugInvokeReq* Dbg::GetInvokeReq() {
return Thread::Current()->GetInvokeReq();
}
diff --git a/runtime/debugger.h b/runtime/debugger.h
index 5d0315e..3b4a5e1 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -202,6 +202,7 @@
class Dbg {
public:
static void SetJdwpAllowed(bool allowed);
+ static bool IsJdwpAllowed();
static void StartJdwp();
static void StopJdwp();
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 1852956..8d85425 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -482,6 +482,31 @@
return result;
}
+static void VMDebug_attachAgent(JNIEnv* env, jclass, jstring agent) {
+ if (agent == nullptr) {
+ ScopedObjectAccess soa(env);
+ ThrowNullPointerException("agent is null");
+ return;
+ }
+
+ if (!Dbg::IsJdwpAllowed()) {
+ ScopedObjectAccess soa(env);
+ ThrowSecurityException("Can't attach agent, process is not debuggable.");
+ return;
+ }
+
+ std::string filename;
+ {
+ ScopedUtfChars chars(env, agent);
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ filename = chars.c_str();
+ }
+
+ Runtime::Current()->AttachAgent(filename);
+}
+
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"),
NATIVE_METHOD(VMDebug, countInstancesOfClasses, "([Ljava/lang/Class;Z)[J"),
@@ -514,7 +539,8 @@
NATIVE_METHOD(VMDebug, stopMethodTracing, "()V"),
NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "!()J"),
NATIVE_METHOD(VMDebug, getRuntimeStatInternal, "(I)Ljava/lang/String;"),
- NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;")
+ NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;"),
+ NATIVE_METHOD(VMDebug, attachAgent, "(Ljava/lang/String;)V"),
};
void register_dalvik_system_VMDebug(JNIEnv* env) {
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index f937ca7..56eab5e 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -601,7 +601,7 @@
<< "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)) {
+ for (const auto& op : args.GetOrDefault(M::Plugins)) {
LOG(WARNING) << " -plugin:" << op.GetLibrary();
}
}
@@ -614,14 +614,14 @@
} 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)) {
+ for (const 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)) {
+ for (const auto& op : args.GetOrDefault(M::AgentPath)) {
if (op.HasArgs()) {
LOG(WARNING) << " -agentpath:" << op.GetName() << "=" << op.GetArgs();
} else {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index e8f41d4..4e600ae 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1311,6 +1311,28 @@
return true;
}
+// Attach a new agent and add it to the list of runtime agents
+//
+// TODO: once we decide on the threading model for agents,
+// revisit this and make sure we're doing this on the right thread
+// (and we synchronize access to any shared data structures like "agents_")
+//
+void Runtime::AttachAgent(const std::string& agent_arg) {
+ ti::Agent agent(agent_arg);
+
+ int res = 0;
+ std::string err;
+ ti::Agent::LoadError result = agent.Attach(&res, &err);
+
+ if (result == ti::Agent::kNoError) {
+ agents_.push_back(std::move(agent));
+ } else {
+ LOG(ERROR) << "Agent attach failed (result=" << result << ") : " << err;
+ ScopedObjectAccess soa(Thread::Current());
+ ThrowWrappedIOException("%s", err.c_str());
+ }
+}
+
void Runtime::InitNativeMethods() {
VLOG(startup) << "Runtime::InitNativeMethods entering";
Thread* self = Thread::Current();
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 7cb87ab..b25ec23 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -665,6 +665,8 @@
NO_RETURN
static void Aborter(const char* abort_message);
+ void AttachAgent(const std::string& agent_arg);
+
private:
static void InitPlatformSignalHandlers();
diff --git a/runtime/ti/agent.cc b/runtime/ti/agent.cc
index 7c0ea64..d21ff77 100644
--- a/runtime/ti/agent.cc
+++ b/runtime/ti/agent.cc
@@ -25,17 +25,10 @@
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) {
+Agent::LoadError Agent::DoLoadHelper(bool attaching,
+ /*out*/jint* call_res,
+ /*out*/std::string* error_msg) {
DCHECK(call_res != nullptr);
DCHECK(error_msg != nullptr);
@@ -49,8 +42,10 @@
VLOG(agents) << "err: " << *error_msg;
return err;
}
- if (onload_ == nullptr) {
- *error_msg = StringPrintf("Unable to start agent %s: No Agent_OnLoad function found",
+ AgentOnLoadFunction callback = attaching ? onattach_ : onload_;
+ if (callback == nullptr) {
+ *error_msg = StringPrintf("Unable to start agent %s: No %s callback found",
+ (attaching ? "attach" : "load"),
name_.c_str());
VLOG(agents) << "err: " << *error_msg;
return kLoadingError;
@@ -59,9 +54,9 @@
std::unique_ptr<char[]> copied_args(new char[args_.size() + 1]);
strcpy(copied_args.get(), args_.c_str());
// TODO Need to do some checks that we are at a good spot etc.
- *call_res = onload_(static_cast<JavaVM*>(Runtime::Current()->GetJavaVM()),
- copied_args.get(),
- nullptr);
+ *call_res = callback(Runtime::Current()->GetJavaVM(),
+ copied_args.get(),
+ nullptr);
if (*call_res != 0) {
*error_msg = StringPrintf("Initialization of %s returned non-zero value of %d",
name_.c_str(), *call_res);
@@ -74,6 +69,12 @@
Agent::LoadError Agent::DoDlOpen(/*out*/std::string* error_msg) {
DCHECK(error_msg != nullptr);
+
+ DCHECK(dlopen_handle_ == nullptr);
+ DCHECK(onload_ == nullptr);
+ DCHECK(onattach_ == nullptr);
+ DCHECK(onunload_ == 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());
@@ -85,7 +86,7 @@
if (onload_ == nullptr) {
VLOG(agents) << "Unable to find 'Agent_OnLoad' symbol in " << this;
}
- onattach_ = reinterpret_cast<AgentOnAttachFunction>(dlsym(dlopen_handle_,
+ onattach_ = reinterpret_cast<AgentOnLoadFunction>(dlsym(dlopen_handle_,
AGENT_ON_ATTACH_FUNCTION_NAME));
if (onattach_ == nullptr) {
VLOG(agents) << "Unable to find 'Agent_OnAttach' symbol in " << this;
@@ -106,23 +107,93 @@
}
dlclose(dlopen_handle_);
dlopen_handle_ = nullptr;
+ onload_ = nullptr;
+ onattach_ = nullptr;
+ onunload_ = 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(std::string arg)
+ : dlopen_handle_(nullptr),
+ onload_(nullptr),
+ onattach_(nullptr),
+ onunload_(nullptr) {
+ size_t eq = arg.find_first_of('=');
+ if (eq == std::string::npos) {
+ name_ = arg;
+ } else {
+ name_ = arg.substr(0, eq);
+ args_ = arg.substr(eq + 1, arg.length());
}
}
+Agent::Agent(const Agent& other)
+ : dlopen_handle_(nullptr),
+ onload_(nullptr),
+ onattach_(nullptr),
+ onunload_(nullptr) {
+ *this = other;
+}
+
+// Attempting to copy to/from loaded/started agents is a fatal error
+Agent& Agent::operator=(const Agent& other) {
+ if (this != &other) {
+ if (other.dlopen_handle_ != nullptr) {
+ LOG(FATAL) << "Attempting to copy a loaded agent!";
+ }
+
+ if (dlopen_handle_ != nullptr) {
+ LOG(FATAL) << "Attempting to assign into a loaded agent!";
+ }
+
+ DCHECK(other.onload_ == nullptr);
+ DCHECK(other.onattach_ == nullptr);
+ DCHECK(other.onunload_ == nullptr);
+
+ DCHECK(onload_ == nullptr);
+ DCHECK(onattach_ == nullptr);
+ DCHECK(onunload_ == nullptr);
+
+ name_ = other.name_;
+ args_ = other.args_;
+
+ dlopen_handle_ = nullptr;
+ onload_ = nullptr;
+ onattach_ = nullptr;
+ onunload_ = nullptr;
+ }
+ return *this;
+}
+
+Agent::Agent(Agent&& other)
+ : dlopen_handle_(nullptr),
+ onload_(nullptr),
+ onattach_(nullptr),
+ onunload_(nullptr) {
+ *this = std::move(other);
+}
+
+Agent& Agent::operator=(Agent&& other) {
+ if (this != &other) {
+ if (dlopen_handle_ != nullptr) {
+ dlclose(dlopen_handle_);
+ }
+ name_ = std::move(other.name_);
+ args_ = std::move(other.args_);
+ dlopen_handle_ = other.dlopen_handle_;
+ onload_ = other.onload_;
+ onattach_ = other.onattach_;
+ onunload_ = other.onunload_;
+ other.dlopen_handle_ = nullptr;
+ other.onload_ = nullptr;
+ other.onattach_ = nullptr;
+ other.onunload_ = nullptr;
+ }
+ return *this;
+}
+
Agent::~Agent() {
if (dlopen_handle_ != nullptr) {
dlclose(dlopen_handle_);
diff --git a/runtime/ti/agent.h b/runtime/ti/agent.h
index 521e21e..6561756 100644
--- a/runtime/ti/agent.h
+++ b/runtime/ti/agent.h
@@ -28,9 +28,10 @@
namespace ti {
using AgentOnLoadFunction = jint (*)(JavaVM*, const char*, void*);
-using AgentOnAttachFunction = jint (*)(JavaVM*, const char*, void*);
using AgentOnUnloadFunction = void (*)(JavaVM*);
+// TODO: consider splitting ti::Agent into command line, agent and shared library handler classes
+
class Agent {
public:
enum LoadError {
@@ -56,65 +57,44 @@
return !GetArgs().empty();
}
- // TODO We need to acquire some locks probably.
- LoadError Load(/*out*/jint* call_res, /*out*/std::string* error_msg);
+ LoadError Load(/*out*/jint* call_res, /*out*/std::string* error_msg) {
+ VLOG(agents) << "Loading agent: " << name_ << " " << args_;
+ return DoLoadHelper(false, call_res, 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;
+ LoadError Attach(/*out*/jint* call_res, /*out*/std::string* error_msg) {
+ VLOG(agents) << "Attaching agent: " << name_ << " " << args_;
+ return DoLoadHelper(true, call_res, error_msg);
}
- static Agent Create(std::string arg);
+ explicit Agent(std::string arg);
- static Agent Create(std::string name, std::string args) {
- return Agent(name, args);
- }
+ Agent(const Agent& other);
+ Agent& operator=(const Agent& other);
+
+ Agent(Agent&& other);
+ Agent& operator=(Agent&& other);
~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_;
+ LoadError DoLoadHelper(bool attaching,
+ /*out*/jint* call_res,
+ /*out*/std::string* error_msg);
+
+ std::string name_;
+ std::string args_;
void* dlopen_handle_;
// The entrypoints.
AgentOnLoadFunction onload_;
- AgentOnAttachFunction onattach_;
+ AgentOnLoadFunction onattach_;
AgentOnUnloadFunction onunload_;
friend std::ostream& operator<<(std::ostream &os, Agent const& m);