Add property-changing JVMTI agent wrapper.

Adds libwrapagentproperties agent that allows one to change or add to
the system properties observed by a JVMTI agent. This is useful for
compatibility as some JVMTI agents expect to be able to find system
properties that are not present in ART. With this we can run them
without having to modify the runtime itself.

Also adds a --agent-wrapper flag to run-jdwp-tests.sh to facilitate
the use of this agent.

Test: mma -j40 libwrapagentproperties
Test: ./art/tools/run-jdwp-tests.sh \
        --mode=host \
        --variant=x64 \
        -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmtid.so \
        --jdwp-path /usr/lib/jvm/default-java/jre/lib/amd64/libjdwp.so \
        --agent-wrapper $ANDROID_HOST_OUT/lib64/libwrapagentproperties.so=$PWD/art/tools/libjdwp-compat.props

Change-Id: Ic5eef7e9b4b8c54f4b0683dbb4e71768cbf4f97c
diff --git a/Android.bp b/Android.bp
index 8678fd0..1b66e6f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,4 +42,5 @@
     "tools/cpp-define-generator",
     "tools/dmtracedump",
     "tools/titrace",
+    "tools/wrapagentproperties",
 ]
diff --git a/tools/libjdwp-compat.props b/tools/libjdwp-compat.props
new file mode 100644
index 0000000..c573b24
--- /dev/null
+++ b/tools/libjdwp-compat.props
@@ -0,0 +1,18 @@
+# Copyright 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.
+#
+# These are properties that are needed for RI jdwp to run.
+java.vm.info=mixed mode
+sun.boot.class.path=
+sun.boot.library.path=
diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh
index 2e59af9..d0e35ac 100755
--- a/tools/run-jdwp-tests.sh
+++ b/tools/run-jdwp-tests.sh
@@ -45,6 +45,8 @@
 debug="no"
 verbose="no"
 image="-Ximage:/data/art-test/core.art"
+with_jdwp_path=""
+agent_wrapper=""
 vm_args=""
 # By default, we run the whole JDWP test suite.
 test="org.apache.harmony.jpda.tests.share.AllTests"
@@ -89,6 +91,14 @@
     # We don't care about jit with the RI
     use_jit=false
     shift
+  elif [[ $1 == --agent-wrapper ]]; then
+    # Remove the --agent-wrapper from the arguments.
+    args=${args/$1}
+    shift
+    agent_wrapper=${agent_wrapper}${1},
+    # Remove the argument
+    args=${args/$1}
+    shift
   elif [[ $1 == -Ximage:* ]]; then
     image="$1"
     shift
@@ -119,8 +129,7 @@
     # Remove the --jdwp-path from the arguments.
     args=${args/$1}
     shift
-    vm_args="${vm_args} --vm-arg -Djpda.settings.debuggeeAgentArgument=\"-agentpath:\""
-    vm_args="${vm_args} --vm-arg -Djpda.settings.debuggeeAgentName=$1"
+    with_jdwp_path=$1
     # Remove the path from the arguments.
     args=${args/$1}
     shift
@@ -140,6 +149,10 @@
 
 if [[ $mode == "ri" ]]; then
   using_jack="false"
+  if [[ "x$with_jdwp_path" != "x" ]]; then
+    vm_args="${vm_args} --vm-arg -Djpda.settings.debuggeeAgentArgument=-agentpath:${agent_wrapper}"
+    vm_args="${vm_args} --vm-arg -Djpda.settings.debuggeeAgentName=$with_jdwp_path"
+  fi
   if [[ "x$image" != "x" ]]; then
     echo "Cannot use -Ximage: with --mode=jvm"
     exit 1
@@ -148,6 +161,10 @@
     exit 1
   fi
 else
+  if [[ "x$with_jdwp_path" != "x" ]]; then
+    vm_args="${vm_args} --vm-arg -Djpda.settings.debuggeeAgentArgument=-agentpath:${agent_wrapper}"
+    vm_args="${vm_args} --vm-arg -Djpda.settings.debuggeeAgentName=${with_jdwp_path}"
+  fi
   vm_args="$vm_args --vm-arg -Xcompiler-option --vm-arg --debuggable"
   # Make sure the debuggee doesn't clean up what the debugger has generated.
   art_debugee="$art_debugee --no-clean"
diff --git a/tools/wrapagentproperties/Android.bp b/tools/wrapagentproperties/Android.bp
new file mode 100644
index 0000000..c39b81a
--- /dev/null
+++ b/tools/wrapagentproperties/Android.bp
@@ -0,0 +1,66 @@
+//
+// 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.
+//
+
+// Build variants {target,host} x {debug,ndebug} x {32,64}
+
+cc_defaults {
+    name: "wrapagentproperties-defaults",
+    host_supported: true,
+    srcs: ["wrapagentproperties.cc"],
+    defaults: ["art_defaults"],
+
+    // Note that this tool needs to be built for both 32-bit and 64-bit since it requires
+    // to be same ISA as what it is attached to.
+    compile_multilib: "both",
+
+    shared_libs: [
+        "libbase"
+    ],
+    target: {
+        android: {
+        },
+        host: {
+        },
+    },
+    header_libs: [
+        "libopenjdkjvmti_headers",
+    ],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    symlink_preferred_arch: true,
+}
+
+art_cc_library {
+    name: "libwrapagentproperties",
+    defaults: ["wrapagentproperties-defaults"],
+    shared_libs: [
+    ],
+}
+
+art_cc_library {
+    name: "libwrapagentpropertiesd",
+    defaults: [
+        "art_debug_defaults",
+        "wrapagentproperties-defaults",
+    ],
+    shared_libs: [ ],
+}
diff --git a/tools/wrapagentproperties/README.md b/tools/wrapagentproperties/README.md
new file mode 100644
index 0000000..d968087
--- /dev/null
+++ b/tools/wrapagentproperties/README.md
@@ -0,0 +1,30 @@
+# wrapagentproperties
+
+wrapagentproperties is a JVMTI agent that lets one change the returned values of
+an agents GetSystemPropert{y,ies} calls.
+
+# Usage
+### Build
+>    `make libwrapagentproperties`  # or 'make libwrapagentpropertiesd' with debugging checks enabled
+
+The libraries will be built for 32-bit, 64-bit, host and target. Below examples
+assume you want to use the 64-bit version.
+
+### Command Line
+#### ART
+>    `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so -agentpath:$ANDROID_HOST_OUT/lib64/libwrapagentproperties.so=/path/to/prop.file,/path/to/agent=agent-args -cp tmp/java/helloworld.dex -Xint helloworld`
+
+* `-Xplugin` and `-agentpath` need to be used, otherwise libtitrace agent will fail during init.
+* If using `libartd.so`, make sure to use the debug version of jvmti.
+
+### prop file format.
+
+The property file is a text file containing the values of java properties you
+wish to override. The format is property=value on each line. Blank lines and
+lines beginning with "#" are ignored.
+
+#### Example prop file
+
+    # abc.prop
+    abc.def=123
+    def.hij=a big deal
diff --git a/tools/wrapagentproperties/wrapagentproperties.cc b/tools/wrapagentproperties/wrapagentproperties.cc
new file mode 100644
index 0000000..dca6270
--- /dev/null
+++ b/tools/wrapagentproperties/wrapagentproperties.cc
@@ -0,0 +1,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;
+  void* dlclose_handle;
+};
+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)),
+        dlopen_handle
+      });
+    }
+    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));
+    if (funcs->proxy_vm->map->find(prop) != funcs->proxy_vm->map->end()) {
+      std::string str_prop(prop);
+      const std::string& val = funcs->proxy_vm->map->at(str_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;
+    }
+    if (funcs->proxy_vm->map->find(prop) != funcs->proxy_vm->map->end()) {
+      funcs->proxy_vm->map->at(prop) = 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,
+                          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);
+    dlclose(u.dlclose_handle);
+  }
+  unload_functions.clear();
+}
+
+}  // namespace wrapagentproperties
+