• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <atomic>
18 #include <dlfcn.h>
19 #include <iostream>
20 #include <fstream>
21 #include <iomanip>
22 #include <jni.h>
23 #include <jvmti.h>
24 #include <unordered_map>
25 #include <unordered_set>
26 #include <memory>
27 #include <mutex>
28 #include <sstream>
29 #include <string>
30 #include <vector>
31 #include <cstring>
32 
33 namespace wrapagentproperties {
34 
35 using PropMap = std::unordered_map<std::string, std::string>;
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 struct ProxyJavaVM;
40 using AgentLoadFunction = jint (*)(ProxyJavaVM*, const char*, void*);
41 using AgentUnloadFunction = jint (*)(JavaVM*);
42 
43 // Global namespace. Shared by every usage of this wrapper unfortunately.
44 // We need to keep track of them to call Agent_OnUnload.
45 static std::mutex unload_mutex;
46 
47 struct Unloader {
48   AgentUnloadFunction unload;
49 };
50 static std::vector<Unloader> unload_functions;
51 
52 static jint CreateJvmtiEnv(ProxyJavaVM* vm, void** out_env, jint version);
53 
54 struct ProxyJavaVM {
55   const struct JNIInvokeInterface* functions;
56   JavaVM* real_vm;
57   PropMap* map;
58   void* dlopen_handle;
59   AgentLoadFunction load;
60   AgentLoadFunction attach;
61 
ProxyJavaVMwrapagentproperties::ProxyJavaVM62   ProxyJavaVM(JavaVM* vm, const std::string& agent_lib, PropMap* map)
63       : functions(CreateInvokeInterface()),
64         real_vm(vm),
65         map(map),
66         dlopen_handle(dlopen(agent_lib.c_str(), RTLD_LAZY)),
67         load(nullptr),
68         attach(nullptr) {
69     CHECK(dlopen_handle != nullptr) << "unable to open " << agent_lib;
70     {
71       std::lock_guard<std::mutex> lk(unload_mutex);
72       unload_functions.push_back({
73         reinterpret_cast<AgentUnloadFunction>(dlsym(dlopen_handle, kOnUnload)),
74       });
75     }
76     attach = reinterpret_cast<AgentLoadFunction>(dlsym(dlopen_handle, kOnAttach));
77     load = reinterpret_cast<AgentLoadFunction>(dlsym(dlopen_handle, kOnLoad));
78   }
79 
80   // TODO Use this to cleanup
WrapDestroyJavaVMwrapagentproperties::ProxyJavaVM81   static jint WrapDestroyJavaVM(ProxyJavaVM* vm) {
82     return vm->real_vm->DestroyJavaVM();
83   }
WrapAttachCurrentThreadwrapagentproperties::ProxyJavaVM84   static jint WrapAttachCurrentThread(ProxyJavaVM* vm, JNIEnv** env, void* res) {
85     return vm->real_vm->AttachCurrentThread(env, res);
86   }
WrapDetachCurrentThreadwrapagentproperties::ProxyJavaVM87   static jint WrapDetachCurrentThread(ProxyJavaVM* vm) {
88     return vm->real_vm->DetachCurrentThread();
89   }
WrapAttachCurrentThreadAsDaemonwrapagentproperties::ProxyJavaVM90   static jint WrapAttachCurrentThreadAsDaemon(ProxyJavaVM* vm, JNIEnv** env, void* res) {
91     return vm->real_vm->AttachCurrentThreadAsDaemon(env, res);
92   }
93 
WrapGetEnvwrapagentproperties::ProxyJavaVM94   static jint WrapGetEnv(ProxyJavaVM* vm, void** out_env, jint version) {
95     switch (version) {
96       case JVMTI_VERSION:
97       case JVMTI_VERSION_1:
98       case JVMTI_VERSION_1_1:
99       case JVMTI_VERSION_1_2:
100         return CreateJvmtiEnv(vm, out_env, version);
101       default:
102         if ((version & 0x30000000) == 0x30000000) {
103           LOG(ERROR) << "Version number 0x" << std::hex << version << " looks like a JVMTI "
104                      << "version but it is not one that is recognized. The wrapper might not "
105                      << "function correctly! Continuing anyway.";
106         }
107         return vm->real_vm->GetEnv(out_env, version);
108     }
109   }
110 
CreateInvokeInterfacewrapagentproperties::ProxyJavaVM111   static JNIInvokeInterface* CreateInvokeInterface() {
112     JNIInvokeInterface* out = new JNIInvokeInterface;
113     memset(out, 0, sizeof(JNIInvokeInterface));
114     out->DestroyJavaVM = reinterpret_cast<jint (*)(JavaVM*)>(WrapDestroyJavaVM);
115     out->AttachCurrentThread =
116         reinterpret_cast<jint(*)(JavaVM*, JNIEnv**, void*)>(WrapAttachCurrentThread);
117     out->DetachCurrentThread = reinterpret_cast<jint(*)(JavaVM*)>(WrapDetachCurrentThread);
118     out->GetEnv = reinterpret_cast<jint(*)(JavaVM*, void**, jint)>(WrapGetEnv);
119     out->AttachCurrentThreadAsDaemon =
120         reinterpret_cast<jint(*)(JavaVM*, JNIEnv**, void*)>(WrapAttachCurrentThreadAsDaemon);
121     return out;
122   }
123 };
124 
125 
126 struct ExtraJvmtiInterface : public jvmtiInterface_1_ {
127   ProxyJavaVM* proxy_vm;
128   jvmtiInterface_1_ const* original_interface;
129 
WrapDisposeEnvironmentwrapagentproperties::ExtraJvmtiInterface130   static jvmtiError WrapDisposeEnvironment(jvmtiEnv* env) {
131     ExtraJvmtiInterface* funcs = reinterpret_cast<ExtraJvmtiInterface*>(
132         const_cast<jvmtiInterface_1_*>(env->functions));
133     jvmtiInterface_1_** out_iface = const_cast<jvmtiInterface_1_**>(&env->functions);
134     *out_iface = const_cast<jvmtiInterface_1_*>(funcs->original_interface);
135     funcs->original_interface->Deallocate(env, reinterpret_cast<unsigned char*>(funcs));
136     jvmtiError res = (*out_iface)->DisposeEnvironment(env);
137     return res;
138   }
139 
WrapGetSystemPropertywrapagentproperties::ExtraJvmtiInterface140   static jvmtiError WrapGetSystemProperty(jvmtiEnv* env, const char* prop, char** out) {
141     ExtraJvmtiInterface* funcs = reinterpret_cast<ExtraJvmtiInterface*>(
142         const_cast<jvmtiInterface_1_*>(env->functions));
143     auto it = funcs->proxy_vm->map->find(prop);
144     if (it != funcs->proxy_vm->map->end()) {
145       const std::string& val = it->second;
146       std::string str_prop(prop);
147       jvmtiError res = env->Allocate(val.size() + 1, reinterpret_cast<unsigned char**>(out));
148       if (res != JVMTI_ERROR_NONE) {
149         return res;
150       }
151       strcpy(*out, val.c_str());
152       return JVMTI_ERROR_NONE;
153     } else {
154       return funcs->original_interface->GetSystemProperty(env, prop, out);
155     }
156   }
157 
WrapGetSystemPropertieswrapagentproperties::ExtraJvmtiInterface158   static jvmtiError WrapGetSystemProperties(jvmtiEnv* env, jint* cnt, char*** prop_ptr) {
159     ExtraJvmtiInterface* funcs = reinterpret_cast<ExtraJvmtiInterface*>(
160         const_cast<jvmtiInterface_1_*>(env->functions));
161     jint init_cnt;
162     char** init_prop_ptr;
163     jvmtiError res = funcs->original_interface->GetSystemProperties(env, &init_cnt, &init_prop_ptr);
164     if (res != JVMTI_ERROR_NONE) {
165       return res;
166     }
167     std::unordered_set<std::string> all_props;
168     for (const auto& p : *funcs->proxy_vm->map) {
169       all_props.insert(p.first);
170     }
171     for (jint i = 0; i < init_cnt; i++) {
172       all_props.insert(init_prop_ptr[i]);
173       env->Deallocate(reinterpret_cast<unsigned char*>(init_prop_ptr[i]));
174     }
175     env->Deallocate(reinterpret_cast<unsigned char*>(init_prop_ptr));
176     *cnt = all_props.size();
177     res = env->Allocate(all_props.size() * sizeof(char*),
178                         reinterpret_cast<unsigned char**>(prop_ptr));
179     if (res != JVMTI_ERROR_NONE) {
180       return res;
181     }
182     char** out_prop_ptr = *prop_ptr;
183     jint i = 0;
184     for (const std::string& p : all_props) {
185       res = env->Allocate(p.size() + 1, reinterpret_cast<unsigned char**>(&out_prop_ptr[i]));
186       if (res != JVMTI_ERROR_NONE) {
187         return res;
188       }
189       memcpy(out_prop_ptr[i], p.c_str(), p.size() + 1);
190       i++;
191     }
192     CHECK_EQ(i, *cnt);
193     return JVMTI_ERROR_NONE;
194   }
195 
WrapSetSystemPropertywrapagentproperties::ExtraJvmtiInterface196   static jvmtiError WrapSetSystemProperty(jvmtiEnv* env, const char* prop, const char* val) {
197     ExtraJvmtiInterface* funcs = reinterpret_cast<ExtraJvmtiInterface*>(
198         const_cast<jvmtiInterface_1_*>(env->functions));
199     jvmtiError res = funcs->original_interface->SetSystemProperty(env, prop, val);
200     if (res != JVMTI_ERROR_NONE) {
201       return res;
202     }
203     auto it = funcs->proxy_vm->map->find(prop);
204     if (it != funcs->proxy_vm->map->end()) {
205       it->second = val;
206     }
207     return JVMTI_ERROR_NONE;
208   }
209 
210   // TODO It would be way better to actually set up a full proxy like we did for JavaVM but the
211   // number of functions makes it not worth it.
SetupProxyJvmtiEnvwrapagentproperties::ExtraJvmtiInterface212   static jint SetupProxyJvmtiEnv(ProxyJavaVM* vm, jvmtiEnv* real_env) {
213     ExtraJvmtiInterface* new_iface = nullptr;
214     if (JVMTI_ERROR_NONE != real_env->Allocate(sizeof(ExtraJvmtiInterface),
215                                               reinterpret_cast<unsigned char**>(&new_iface))) {
216       LOG(ERROR) << "Could not allocate extra space for new jvmti interface struct";
217       return JNI_ERR;
218     }
219     memcpy(new_iface, real_env->functions, sizeof(jvmtiInterface_1_));
220     new_iface->proxy_vm = vm;
221     new_iface->original_interface = real_env->functions;
222 
223     // Replace these functions with the new ones.
224     new_iface->DisposeEnvironment = WrapDisposeEnvironment;
225     new_iface->GetSystemProperty = WrapGetSystemProperty;
226     new_iface->GetSystemProperties = WrapGetSystemProperties;
227     new_iface->SetSystemProperty = WrapSetSystemProperty;
228 
229     // Replace the functions table with our new one with replaced functions.
230     jvmtiInterface_1_** out_iface = const_cast<jvmtiInterface_1_**>(&real_env->functions);
231     *out_iface = new_iface;
232     return JNI_OK;
233   }
234 };
235 
CreateJvmtiEnv(ProxyJavaVM * vm,void ** out_env,jint version)236 static jint CreateJvmtiEnv(ProxyJavaVM* vm, void** out_env, jint version) {
237   jint res = vm->real_vm->GetEnv(out_env, version);
238   if (res != JNI_OK) {
239     LOG(WARNING) << "Could not create jvmtiEnv to proxy!";
240     return res;
241   }
242   return ExtraJvmtiInterface::SetupProxyJvmtiEnv(vm, reinterpret_cast<jvmtiEnv*>(*out_env));
243 }
244 
245 enum class StartType {
246   OnAttach, OnLoad,
247 };
248 
CallNextAgent(StartType start,ProxyJavaVM * vm,const std::string & options,void * reserved)249 static jint CallNextAgent(StartType start,
250                           ProxyJavaVM* vm,
251                           const std::string& options,
252                           void* reserved) {
253   // TODO It might be good to set it up so that the library is unloaded even if no jvmtiEnv's are
254   // created but this isn't expected to be common so we will just not bother.
255   return ((start == StartType::OnLoad) ? vm->load : vm->attach)(vm, options.c_str(), reserved);
256 }
257 
substrOf(const std::string & s,size_t start,size_t end)258 static std::string substrOf(const std::string& s, size_t start, size_t end) {
259   if (end == start) {
260     return "";
261   } else if (end == std::string::npos) {
262     end = s.size();
263   }
264   return s.substr(start, end - start);
265 }
266 
ReadPropMap(const std::string & file)267 static PropMap* ReadPropMap(const std::string& file) {
268   std::unique_ptr<PropMap> map(new PropMap);
269   std::ifstream prop_file(file, std::ios::in);
270   std::string line;
271   while (std::getline(prop_file, line)) {
272     if (line.size() == 0 || line[0] == '#') {
273       continue;
274     }
275     if (line.find('=') == std::string::npos) {
276       LOG(INFO) << "line: " << line << " didn't have a '='";
277       return nullptr;
278     }
279     std::string prop = substrOf(line, 0, line.find('='));
280     std::string val = substrOf(line, line.find('=') + 1, std::string::npos);
281     LOG(INFO) << "Overriding property " << std::quoted(prop) << " new value is "
282               << std::quoted(val);
283     map->insert({prop, val});
284   }
285   return map.release();
286 }
287 
ParseArgs(const std::string & options,std::string * prop_file,std::string * agent_lib,std::string * agent_options)288 static bool ParseArgs(const std::string& options,
289                       /*out*/std::string* prop_file,
290                       /*out*/std::string* agent_lib,
291                       /*out*/std::string* agent_options) {
292   if (options.find(',') == std::string::npos) {
293     LOG(ERROR) << "No agent lib in " << options;
294     return false;
295   }
296   *prop_file = substrOf(options, 0, options.find(','));
297   *agent_lib = substrOf(options, options.find(',') + 1, options.find('='));
298   if (options.find('=') != std::string::npos) {
299     *agent_options = substrOf(options, options.find('=') + 1, std::string::npos);
300   } else {
301     *agent_options = "";
302   }
303   return true;
304 }
305 
AgentStart(StartType start,JavaVM * vm,char * options,void * reserved)306 static jint AgentStart(StartType start, JavaVM* vm, char* options, void* reserved) {
307   std::string agent_lib;
308   std::string agent_options;
309   std::string prop_file;
310   if (!ParseArgs(options, /*out*/ &prop_file, /*out*/ &agent_lib, /*out*/ &agent_options)) {
311     return JNI_ERR;
312   }
313   // It would be good to not leak these but since they will live for almost the whole program run
314   // anyway it isn't a huge deal.
315   PropMap* map = ReadPropMap(prop_file);
316   if (map == nullptr) {
317     LOG(ERROR) << "unable to read property file at " << std::quoted(prop_file) << "!";
318     return JNI_ERR;
319   }
320   ProxyJavaVM* proxy = new ProxyJavaVM(vm, agent_lib, map);
321   LOG(INFO) << "Chaining to next agent[" << std::quoted(agent_lib) << "] options=["
322             << std::quoted(agent_options) << "]";
323   return CallNextAgent(start, proxy, agent_options, reserved);
324 }
325 
326 // Late attachment (e.g. 'am attach-agent').
Agent_OnAttach(JavaVM * vm,char * options,void * reserved)327 extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char* options, void* reserved) {
328   return AgentStart(StartType::OnAttach, vm, options, reserved);
329 }
330 
331 // Early attachment
332 // (e.g. 'java -agentpath:/path/to/libwrapagentproperties.so=/path/to/propfile,/path/to/wrapped.so=[ops]').
Agent_OnLoad(JavaVM * jvm,char * options,void * reserved)333 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
334   return AgentStart(StartType::OnLoad, jvm, options, reserved);
335 }
336 
Agent_OnUnload(JavaVM * jvm)337 extern "C" JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* jvm) {
338   std::lock_guard<std::mutex> lk(unload_mutex);
339   for (const Unloader& u : unload_functions) {
340     u.unload(jvm);
341     // Don't dlclose since some agents expect to still have code loaded after this.
342   }
343   unload_functions.clear();
344 }
345 
346 }  // namespace wrapagentproperties
347 
348