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