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 61 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 80 static jint WrapDestroyJavaVM(ProxyJavaVM* vm) { 81 return vm->real_vm->DestroyJavaVM(); 82 } 83 static jint WrapAttachCurrentThread(ProxyJavaVM* vm, JNIEnv** env, void* res) { 84 return vm->real_vm->AttachCurrentThread(env, res); 85 } 86 static jint WrapDetachCurrentThread(ProxyJavaVM* vm) { 87 return vm->real_vm->DetachCurrentThread(); 88 } 89 static jint WrapAttachCurrentThreadAsDaemon(ProxyJavaVM* vm, JNIEnv** env, void* res) { 90 return vm->real_vm->AttachCurrentThreadAsDaemon(env, res); 91 } 92 93 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 110 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 129 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 139 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 156 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 194 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. 209 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 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 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 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 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 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 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'). 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]'). 330 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { 331 return AgentStart(StartType::OnLoad, jvm, options, reserved); 332 } 333 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