1 // Copyright (C) 2017 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 
16 #include <android-base/logging.h>
17 #include <dlfcn.h>
18 #include <jni.h>
19 #include <jvmti.h>
20 
21 #include <atomic>
22 #include <fstream>
23 #include <iomanip>
24 #include <iostream>
25 #include <memory>
26 #include <mutex>
27 #include <sstream>
28 #include <string>
29 #include <unordered_map>
30 #include <unordered_set>
31 #include <vector>
32 
33 namespace chainagents {
34 
35 static constexpr const char* kChainFile = "chain_agents.txt";
36 static constexpr const char* kOnLoad = "Agent_OnLoad";
37 static constexpr const char* kOnAttach = "Agent_OnAttach";
38 static constexpr const char* kOnUnload = "Agent_OnUnload";
39 using AgentLoadFunction = jint (*)(JavaVM*, const char*, void*);
40 using AgentUnloadFunction = jint (*)(JavaVM*);
41 
42 // Global namespace. Shared by every usage of this wrapper unfortunately.
43 // We need to keep track of them to call Agent_OnUnload.
44 static std::mutex unload_mutex;
45 
46 struct Unloader {
47   AgentUnloadFunction unload;
48 };
49 static std::vector<Unloader> unload_functions;
50 
51 enum class StartType {
52   OnAttach,
53   OnLoad,
54 };
55 
Split(std::string source,char delim)56 static std::pair<std::string, std::string> Split(std::string source, char delim) {
57   std::string first(source.substr(0, source.find(delim)));
58   if (source.find(delim) == std::string::npos) {
59     return std::pair(first, "");
60   } else {
61     return std::pair(first, source.substr(source.find(delim) + 1));
62   }
63 }
64 
Load(StartType start,JavaVM * vm,void * reserved,const std::pair<std::string,std::string> & lib_and_args,std::string * err)65 static jint Load(StartType start,
66                  JavaVM* vm,
67                  void* reserved,
68                  const std::pair<std::string, std::string>& lib_and_args,
69                  /*out*/ std::string* err) {
70   void* handle = dlopen(lib_and_args.first.c_str(), RTLD_LAZY);
71   std::ostringstream oss;
72   if (handle == nullptr) {
73     oss << "Failed to dlopen due to " << dlerror();
74     *err = oss.str();
75     return JNI_ERR;
76   }
77   AgentLoadFunction alf = reinterpret_cast<AgentLoadFunction>(
78       dlsym(handle, start == StartType::OnLoad ? kOnLoad : kOnAttach));
79   if (alf == nullptr) {
80     oss << "Failed to dlsym " << (start == StartType::OnLoad ? kOnLoad : kOnAttach) << " due to "
81         << dlerror();
82     *err = oss.str();
83     return JNI_ERR;
84   }
85   jint res = alf(vm, lib_and_args.second.c_str(), reserved);
86   if (res != JNI_OK) {
87     *err = "load function failed!";
88     return res;
89   }
90   AgentUnloadFunction auf = reinterpret_cast<AgentUnloadFunction>(dlsym(handle, kOnUnload));
91   if (auf != nullptr) {
92     unload_functions.push_back({ auf });
93   }
94   return JNI_OK;
95 }
96 
AgentStart(StartType start,JavaVM * vm,char * options,void * reserved)97 static jint AgentStart(StartType start, JavaVM* vm, char* options, void* reserved) {
98   std::string input_file(options);
99   input_file = input_file + "/" + kChainFile;
100   std::ifstream input(input_file);
101   std::string line;
102   std::lock_guard<std::mutex> mu(unload_mutex);
103   while (std::getline(input, line)) {
104     std::pair<std::string, std::string> lib_and_args(Split(line, '='));
105     std::string err;
106     jint new_res = Load(start, vm, reserved, lib_and_args, &err);
107     if (new_res != JNI_OK) {
108       PLOG(WARNING) << "Failed to load library " << lib_and_args.first
109                     << " (arguments: " << lib_and_args.second << ") due to " << err;
110     }
111   }
112   return JNI_OK;
113 }
114 
115 // Late attachment (e.g. 'am attach-agent').
Agent_OnAttach(JavaVM * vm,char * options,void * reserved)116 extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
117   return AgentStart(StartType::OnAttach, vm, options, reserved);
118 }
119 
120 // Early attachment
121 // (e.g. 'java
122 // -agentpath:/path/to/libwrapagentproperties.so=/path/to/propfile,/path/to/wrapped.so=[ops]').
Agent_OnLoad(JavaVM * jvm,char * options,void * reserved)123 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
124   return AgentStart(StartType::OnLoad, jvm, options, reserved);
125 }
126 
Agent_OnUnload(JavaVM * jvm)127 extern "C" JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* jvm) {
128   std::lock_guard<std::mutex> lk(unload_mutex);
129   for (const Unloader& u : unload_functions) {
130     u.unload(jvm);
131     // Don't dlclose since some agents expect to still have code loaded after this.
132   }
133   unload_functions.clear();
134 }
135 
136 }  // namespace chainagents
137