Merge "ART: Fix os << std::string* errors"
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/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 57fc4976..12fa49e 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -143,8 +143,7 @@
   if (data == nullptr) {
     return false;
   }
-  data->AddMethod(flags, ref.index);
-  return true;
+  return data->AddMethod(flags, ref.index);
 }
 
 bool ProfileCompilationInfo::AddMethodIndex(MethodHotness::Flag flags,
@@ -158,8 +157,7 @@
   if (data == nullptr) {
     return false;
   }
-  data->AddMethod(flags, method_idx);
-  return true;
+  return data->AddMethod(flags, method_idx);
 }
 
 bool ProfileCompilationInfo::AddMethods(const std::vector<ProfileMethodInfo>& methods) {
@@ -592,7 +590,14 @@
   // This should always be the case since since the cache map is managed by ProfileCompilationInfo.
   DCHECK_EQ(profile_key, result->profile_key);
   DCHECK_EQ(profile_index, result->profile_index);
-  DCHECK_EQ(num_method_ids, result->num_method_ids);
+
+  if (num_method_ids != result->num_method_ids) {
+    // This should not happen... added to help investigating b/65812889.
+    LOG(ERROR) << "num_method_ids mismatch for dex " << profile_key
+        << ", expected=" << num_method_ids
+        << ", actual=" << result->num_method_ids;
+    return nullptr;
+  }
 
   return result;
 }
@@ -1342,8 +1347,8 @@
   DexFileData* dex_data = GetOrAddDexFileData(method_ref.dex_file);
   if (dex_data != nullptr) {
     // TODO: Add inline caches.
-    dex_data->AddMethod(static_cast<MethodHotness::Flag>(hotness.GetFlags()), method_ref.index);
-    return true;
+    return dex_data->AddMethod(
+        static_cast<MethodHotness::Flag>(hotness.GetFlags()), method_ref.index);
   }
   return false;
 }
@@ -1727,7 +1732,12 @@
 }
 
 // Mark a method as executed at least once.
-void ProfileCompilationInfo::DexFileData::AddMethod(MethodHotness::Flag flags, size_t index) {
+bool ProfileCompilationInfo::DexFileData::AddMethod(MethodHotness::Flag flags, size_t index) {
+  if (index >= num_method_ids) {
+    LOG(ERROR) << "Invalid method index " << index << ". num_method_ids=" << num_method_ids;
+    return false;
+  }
+
   if ((flags & MethodHotness::kFlagStartup) != 0) {
     method_bitmap.StoreBit(MethodBitIndex(/*startup*/ true, index), /*value*/ true);
   }
@@ -1739,6 +1749,7 @@
         index,
         InlineCacheMap(std::less<uint16_t>(), arena_->Adapter(kArenaAllocProfile)));
   }
+  return true;
 }
 
 ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::DexFileData::GetHotnessInfo(
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index 5c7448f..009554c 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -290,7 +290,9 @@
     }
     for (Iterator it = index_begin; it != index_end; ++it) {
       DCHECK_LT(*it, data->num_method_ids);
-      data->AddMethod(flags, *it);
+      if (!data->AddMethod(flags, *it)) {
+        return false;
+      }
     }
     return true;
   }
@@ -444,7 +446,7 @@
     }
 
     // Mark a method as executed at least once.
-    void AddMethod(MethodHotness::Flag flags, size_t index);
+    bool AddMethod(MethodHotness::Flag flags, size_t index);
 
     void MergeBitmap(const DexFileData& other) {
       DCHECK_EQ(bitmap_storage.size(), other.bitmap_storage.size());
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
+