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