Home | History | Annotate | Download | only in wrapagentproperties
      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