Add simple-force-redefine agent

This adds as an agent 'libforceredefine' that will redefine classes
for us on demand. This can be used to test the behavior of
class-redefinition on apps in various compilation states.

This agent takes an argument a file that contains a new-line separated
list of fully qualified class names (see jni FindClass documentation).
These classes will be redefined to add a NOP before every function.
This transformation will be reapplied if any additional transforms are
performed.

Test: adb shell am attach-agent $(adb shell pidof com.antonioleiva.bandhookkotlin) /data/local/tmp/libforceredefine.so=/data/local/tmp/classlist; \
      adb shell am attach-agent $(adb shell pidof com.antonioleiva.bandhookkotlin) /data/local/tmp/libforceredefine.so=/data/local/tmp/classlist; \
      adb shell am attach-agent $(adb shell pidof com.antonioleiva.bandhookkotlin) /data/local/tmp/libforceredefine.so=/data/local/tmp/classlist
Bug: 127477438

Change-Id: I0af60ab31970a68abf8e08e6036cc48e2e4438ac
diff --git a/tools/simple-force-redefine/Android.bp b/tools/simple-force-redefine/Android.bp
new file mode 100644
index 0000000..871f210
--- /dev/null
+++ b/tools/simple-force-redefine/Android.bp
@@ -0,0 +1,83 @@
+//
+// Copyright (C) 2019 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: "forceredefine-defaults",
+    host_supported: true,
+    srcs: ["forceredefine.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: [
+      "libz",
+      "liblog",
+    ],
+    header_libs: [
+        "libopenjdkjvmti_headers",
+        // Annoyingly you aren't allowed to include even header-only non-ndk libs into an ndk build.
+        // Instead we put the directories this would bring in below in 'include_dirs'
+        // "libnativehelper_header_only",
+    ],
+    include_dirs: [
+        // NDK headers aren't available in platform NDK builds.
+        "libnativehelper/include_jni",
+        "libnativehelper/header_only_include",
+    ],
+    sdk_version: "current",
+    stl: "libc++_static",
+    target: {
+      android: {
+        static_libs: [
+          "slicer_ndk_no_rtti",
+          "libbase_ndk",
+        ],
+      },
+      host: {
+        static_libs: [
+          "slicer_no_rtti",
+        ],
+        shared_libs: [
+          "libbase",
+        ],
+      },
+    },
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    symlink_preferred_arch: true,
+}
+
+art_cc_library {
+    name: "libforceredefine",
+    defaults: ["forceredefine-defaults"],
+}
+
+art_cc_library {
+    name: "libforceredefined",
+    defaults: [
+        "art_debug_defaults",
+        "forceredefine-defaults",
+    ],
+}
diff --git a/tools/simple-force-redefine/README.md b/tools/simple-force-redefine/README.md
new file mode 100644
index 0000000..362c704
--- /dev/null
+++ b/tools/simple-force-redefine/README.md
@@ -0,0 +1,33 @@
+# forceredfine
+
+ForceRedefine is a JVMTI agent designed for testing how redefiniton affects running processes. It
+allows one to force classes to be redefined by writing to a fifo or give a process a list of
+classes to try redefining. Currently the redefinition is limited to adding (or removing) a single
+NOP at the beginning of every function in the class.
+
+# Usage
+### Build
+>    `make libforceredefine`
+
+The libraries will be built for 32-bit, 64-bit, host and target. Below examples
+assume you want to use the 64-bit version.
+
+#### ART
+>    `adb shell setenforce 0`
+>
+>    `adb push $ANDROID_PRODUCT_OUT/system/lib64/libforceredefine.so /data/local/tmp/`
+>
+>    `echo java/util/ArrayList > /tmp/classlist`
+>    `echo java/util/Arrays >> /tmp/classlist`
+>    `adb push /tmp/classlist /data/local/tmp/`
+>
+>    `adb shell am attach-agent $(adb shell pidof some.deubggable.app) /data/local/tmp/libforceredefine.so=/data/local/tmp/classlist`
+
+Since the agent has no static state it can be attached multiple times to the same process.
+
+>    `adb shell am attach-agent $(adb shell pidof some.deubggable.app) /data/local/tmp/libforceredefine.so=/data/local/tmp/classlist`
+>    `adb shell am attach-agent $(adb shell pidof some.deubggable.app) /data/local/tmp/libforceredefine.so=/data/local/tmp/classlist2`
+>    `adb shell am attach-agent $(adb shell pidof some.deubggable.app) /data/local/tmp/libforceredefine.so=/data/local/tmp/classlist`
+
+One can also use fifos to send classes interactively to the process. (TODO: Have the agent
+continue reading from the fifo even after it gets an EOF.)
\ No newline at end of file
diff --git a/tools/simple-force-redefine/forceredefine.cc b/tools/simple-force-redefine/forceredefine.cc
new file mode 100644
index 0000000..f96626f
--- /dev/null
+++ b/tools/simple-force-redefine/forceredefine.cc
@@ -0,0 +1,281 @@
+// Copyright (C) 2019 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 "__mutex_base"
+#include <cstddef>
+#include <fcntl.h>
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <unistd.h>
+#include <unordered_set>
+
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+
+#include <nativehelper/scoped_local_ref.h>
+
+#include <jni.h>
+#include <jvmti.h>
+
+// Slicer's headers have code that triggers these warnings. b/65298177
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#pragma clang diagnostic ignored "-Wsign-compare"
+#include <slicer/code_ir.h>
+#include <slicer/dex_bytecode.h>
+#include <slicer/dex_ir.h>
+#include <slicer/dex_ir_builder.h>
+#include <slicer/reader.h>
+#include <slicer/writer.h>
+#pragma clang diagnostic pop
+
+namespace forceredefine {
+
+namespace {
+
+struct AgentInfo {
+  std::fstream stream;
+  std::unordered_set<std::string> classes;
+  std::mutex mutex;
+};
+
+// Converts a class name to a type descriptor
+// (ex. "java.lang.String" to "Ljava/lang/String;")
+std::string classNameToDescriptor(const char* className) {
+  std::stringstream ss;
+  ss << "L";
+  for (auto p = className; *p != '\0'; ++p) {
+    ss << (*p == '.' ? '/' : *p);
+  }
+  ss << ";";
+  return ss.str();
+}
+
+// Converts a descriptor (Lthis/style/of/name;) to a jni-FindClass style Fully-qualified class name
+// (this/style/of/name).
+std::string DescriptorToFQCN(const std::string& descriptor) {
+  return descriptor.substr(1, descriptor.size() - 2);
+}
+
+static AgentInfo* GetAgentInfo(jvmtiEnv* jvmti) {
+  AgentInfo* ai = nullptr;
+  CHECK_EQ(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&ai)), JVMTI_ERROR_NONE);
+  CHECK(ai != nullptr);
+  return ai;
+}
+
+class JvmtiAllocator : public dex::Writer::Allocator {
+ public:
+  explicit JvmtiAllocator(jvmtiEnv* jvmti) : jvmti_(jvmti) {}
+  void* Allocate(size_t size) override {
+    unsigned char* res = nullptr;
+    jvmti_->Allocate(size, &res);
+    return res;
+  }
+  void Free(void* ptr) override {
+    jvmti_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+  }
+
+ private:
+  jvmtiEnv* jvmti_;
+};
+
+static void Transform(std::shared_ptr<ir::DexFile> ir) {
+  std::unique_ptr<ir::Builder> builder;
+  for (auto& method : ir->encoded_methods) {
+    // Do not look into abstract/bridge/native/synthetic methods.
+    if ((method->access_flags &
+         (dex::kAccAbstract | dex::kAccBridge | dex::kAccNative | dex::kAccSynthetic)) != 0) {
+      continue;
+    }
+
+    struct AddNopVisitor : public lir::Visitor {
+      explicit AddNopVisitor(lir::CodeIr* cir) : cir_(cir) {}
+
+      bool Visit(lir::Bytecode* bc) override {
+        if (seen_first_inst) {
+          return false;
+        }
+        seen_first_inst = true;
+        auto new_inst = cir_->Alloc<lir::Bytecode>();
+        new_inst->opcode = dex::OP_NOP;
+        cir_->instructions.InsertBefore(bc, new_inst);
+        return true;
+      }
+
+      lir::CodeIr* cir_;
+      bool seen_first_inst = false;
+    };
+
+    lir::CodeIr c(method.get(), ir);
+    AddNopVisitor visitor(&c);
+    for (auto it = c.instructions.begin(); it != c.instructions.end(); ++it) {
+      lir::Instruction* fi = *it;
+      if (fi->Accept(&visitor)) {
+        break;
+      }
+    }
+    c.Assemble();
+  }
+}
+
+static void CbClassFileLoadHook(jvmtiEnv* jvmti,
+                                JNIEnv* env ATTRIBUTE_UNUSED,
+                                jclass classBeingRedefined ATTRIBUTE_UNUSED,
+                                jobject loader ATTRIBUTE_UNUSED,
+                                const char* name,
+                                jobject protectionDomain ATTRIBUTE_UNUSED,
+                                jint classDataLen,
+                                const unsigned char* classData,
+                                jint* newClassDataLen,
+                                unsigned char** newClassData) {
+  std::string desc(classNameToDescriptor(name));
+  std::string fqcn(DescriptorToFQCN(desc));
+  AgentInfo* ai = GetAgentInfo(jvmti);
+  {
+    std::lock_guard<std::mutex> mu(ai->mutex);
+    if (ai->classes.find(fqcn) == ai->classes.end()) {
+      return;
+    }
+  }
+  LOG(INFO) << "Got CFLH for " << name << " on env " << static_cast<void*>(jvmti);
+  JvmtiAllocator allocator(jvmti);
+  dex::Reader reader(classData, classDataLen);
+  dex::u4 index = reader.FindClassIndex(desc.c_str());
+  reader.CreateClassIr(index);
+  std::shared_ptr<ir::DexFile> ir(reader.GetIr());
+  Transform(ir);
+  dex::Writer writer(ir);
+  size_t new_size;
+  *newClassData = writer.CreateImage(&allocator, &new_size);
+  *newClassDataLen = new_size;
+}
+
+static void RedefineClass(jvmtiEnv* jvmti, JNIEnv* env, const std::string& klass_name) {
+  jclass klass = nullptr;
+  if ((klass = env->FindClass(klass_name.c_str())) == nullptr || env->ExceptionCheck()) {
+    LOG(WARNING) << "Failed to find class for " << klass_name;
+    env->ExceptionDescribe();
+    env->ExceptionClear();
+    return;
+  }
+  jvmti->RetransformClasses(1, &klass);
+}
+
+static void AgentMain(jvmtiEnv* jvmti, JNIEnv* jni, void* arg ATTRIBUTE_UNUSED) {
+  AgentInfo* ai = GetAgentInfo(jvmti);
+  std::string klass_name;
+  jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr);
+  // TODO Replace this with something that can read from a fifo and ignore the 'EOF's.
+  while (std::getline(ai->stream, klass_name, '\n')) {
+    LOG(INFO) << "Redefining class " << klass_name << " with " << static_cast<void*>(jvmti);
+    {
+      std::lock_guard<std::mutex> mu(ai->mutex);
+      ai->classes.insert(klass_name);
+    }
+    RedefineClass(jvmti, jni, klass_name);
+  }
+}
+
+static void CbVmInit(jvmtiEnv* jvmti, JNIEnv* env, jthread thr ATTRIBUTE_UNUSED) {
+  // Create a Thread object.
+  ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF("Agent Thread"));
+  if (thread_name.get() == nullptr) {
+    env->ExceptionDescribe();
+    env->ExceptionClear();
+    return;
+  }
+  ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread"));
+  if (thread_klass.get() == nullptr) {
+    env->ExceptionDescribe();
+    env->ExceptionClear();
+    return;
+  }
+  ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get()));
+  if (thread.get() == nullptr) {
+    env->ExceptionDescribe();
+    env->ExceptionClear();
+    return;
+  }
+
+  env->CallNonvirtualVoidMethod(
+      thread.get(),
+      thread_klass.get(),
+      env->GetMethodID(thread_klass.get(), "<init>", "(Ljava/lang/String;)V"),
+      thread_name.get());
+  env->CallVoidMethod(thread.get(), env->GetMethodID(thread_klass.get(), "setPriority", "(I)V"), 1);
+  env->CallVoidMethod(
+      thread.get(), env->GetMethodID(thread_klass.get(), "setDaemon", "(Z)V"), JNI_TRUE);
+
+  jvmti->RunAgentThread(thread.get(), AgentMain, nullptr, JVMTI_THREAD_MIN_PRIORITY);
+}
+
+}  // namespace
+
+template <bool kIsOnLoad>
+static jint AgentStart(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
+  jvmtiEnv* jvmti = nullptr;
+
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_1) != JNI_OK ||
+      jvmti == nullptr) {
+    LOG(ERROR) << "unable to obtain JVMTI env.";
+    return JNI_ERR;
+  }
+  std::string sopts(options);
+  AgentInfo* ai = new AgentInfo;
+  ai->stream.open(options, std::ios_base::in);
+  if (!ai->stream.is_open()) {
+    PLOG(ERROR) << "Could not open file " << options << " for triggering class-reload";
+    return JNI_ERR;
+  }
+
+  jvmtiCapabilities caps{
+    .can_retransform_classes = 1,
+  };
+  if (jvmti->AddCapabilities(&caps) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Unable to get retransform_classes capability!";
+    return JNI_ERR;
+  }
+  jvmtiEventCallbacks cb{
+    .ClassFileLoadHook = CbClassFileLoadHook,
+    .VMInit = CbVmInit,
+  };
+  jvmti->SetEventCallbacks(&cb, sizeof(cb));
+  jvmti->SetEnvironmentLocalStorage(reinterpret_cast<void*>(ai));
+  if (kIsOnLoad) {
+    jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, nullptr);
+  } else {
+    JNIEnv* jni = nullptr;
+    vm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_2);
+    jthread thr;
+    jvmti->GetCurrentThread(&thr);
+    CbVmInit(jvmti, jni, thr);
+  }
+  return JNI_OK;
+}
+
+// Late attachment (e.g. 'am attach-agent').
+extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
+  return AgentStart<false>(vm, options, reserved);
+}
+
+// Early attachment
+extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
+  return AgentStart<true>(jvm, options, reserved);
+}
+
+}  // namespace forceredefine