912 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			912 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  * 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.
 | |
|  */
 | |
| 
 | |
| #include <cstdio>
 | |
| #include <fstream>
 | |
| #include <iomanip>
 | |
| #include <iostream>
 | |
| #include <memory>
 | |
| #include <sstream>
 | |
| #include <strstream>
 | |
| 
 | |
| #include <jni.h>
 | |
| 
 | |
| #include "base/utils.h"
 | |
| #include "jvmti.h"
 | |
| 
 | |
| #pragma clang diagnostic push
 | |
| 
 | |
| // Slicer's headers have code that triggers these warnings. b/65298177
 | |
| #pragma clang diagnostic ignored "-Wunused-parameter"
 | |
| #pragma clang diagnostic ignored "-Wsign-compare"
 | |
| 
 | |
| #include "slicer/code_ir.h"
 | |
| #include "slicer/control_flow_graph.h"
 | |
| #include "slicer/dex_ir.h"
 | |
| #include "slicer/dex_ir_builder.h"
 | |
| #include "slicer/instrumentation.h"
 | |
| #include "slicer/reader.h"
 | |
| #include "slicer/writer.h"
 | |
| 
 | |
| #pragma clang diagnostic pop
 | |
| 
 | |
| namespace art {
 | |
| 
 | |
| // Should we do a 'full_rewrite' with this test?
 | |
| static constexpr bool kDoFullRewrite = true;
 | |
| 
 | |
| struct StressData {
 | |
|   bool vm_class_loader_initialized;
 | |
|   bool trace_stress;
 | |
|   bool redefine_stress;
 | |
|   bool field_stress;
 | |
|   bool step_stress;
 | |
| };
 | |
| 
 | |
| static void DeleteLocalRef(JNIEnv* env, jobject obj) {
 | |
|   if (obj != nullptr) {
 | |
|     env->DeleteLocalRef(obj);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool DoExtractClassFromData(jvmtiEnv* env,
 | |
|                                    const std::string& descriptor,
 | |
|                                    jint in_len,
 | |
|                                    const unsigned char* in_data,
 | |
|                                    /*out*/jint* out_len,
 | |
|                                    /*out*/unsigned char** out_data) {
 | |
|   dex::Reader reader(in_data, in_len);
 | |
|   dex::u4 class_idx = reader.FindClassIndex(descriptor.c_str());
 | |
|   if (class_idx != dex::kNoIndex) {
 | |
|     reader.CreateClassIr(class_idx);
 | |
|   } else {
 | |
|     LOG(ERROR) << "ERROR: Can't find class " << descriptor;
 | |
|     return false;
 | |
|   }
 | |
|   auto dex_ir = reader.GetIr();
 | |
| 
 | |
|   if (kDoFullRewrite) {
 | |
|     for (auto& ir_method : dex_ir->encoded_methods) {
 | |
|       if (ir_method->code != nullptr) {
 | |
|         lir::CodeIr code_ir(ir_method.get(), dex_ir);
 | |
|         lir::ControlFlowGraph cfg_compact(&code_ir, false);
 | |
|         lir::ControlFlowGraph cfg_verbose(&code_ir, true);
 | |
|         code_ir.Assemble();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   dex::Writer writer(dex_ir);
 | |
| 
 | |
|   struct Allocator : public dex::Writer::Allocator {
 | |
|     explicit Allocator(jvmtiEnv* jvmti_env) : jvmti_env_(jvmti_env) {}
 | |
|     void* Allocate(size_t size) override {
 | |
|       unsigned char* out = nullptr;
 | |
|       if (JVMTI_ERROR_NONE != jvmti_env_->Allocate(size, &out)) {
 | |
|         return nullptr;
 | |
|       } else {
 | |
|         return out;
 | |
|       }
 | |
|     }
 | |
|     void Free(void* ptr) override {
 | |
|       jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
 | |
|     }
 | |
|    private:
 | |
|     jvmtiEnv* jvmti_env_;
 | |
|   };
 | |
|   Allocator alloc(env);
 | |
|   size_t res_len;
 | |
|   unsigned char* res = writer.CreateImage(&alloc, &res_len);
 | |
|   if (res != nullptr) {
 | |
|     *out_data = res;
 | |
|     *out_len = res_len;
 | |
|     return true;
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class ScopedThreadInfo {
 | |
|  public:
 | |
|   ScopedThreadInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jthread thread)
 | |
|       : jvmtienv_(jvmtienv), env_(env), free_name_(false) {
 | |
|     memset(&info_, 0, sizeof(info_));
 | |
|     if (thread == nullptr) {
 | |
|       info_.name = const_cast<char*>("<NULLPTR>");
 | |
|     } else if (jvmtienv->GetThreadInfo(thread, &info_) != JVMTI_ERROR_NONE) {
 | |
|       info_.name = const_cast<char*>("<UNKNOWN THREAD>");
 | |
|     } else {
 | |
|       free_name_ = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ~ScopedThreadInfo() {
 | |
|     if (free_name_) {
 | |
|       jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(info_.name));
 | |
|     }
 | |
|     DeleteLocalRef(env_, info_.thread_group);
 | |
|     DeleteLocalRef(env_, info_.context_class_loader);
 | |
|   }
 | |
| 
 | |
|   const char* GetName() const {
 | |
|     return info_.name;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   jvmtiEnv* jvmtienv_;
 | |
|   JNIEnv* env_;
 | |
|   bool free_name_;
 | |
|   jvmtiThreadInfo info_;
 | |
| };
 | |
| 
 | |
| class ScopedClassInfo {
 | |
|  public:
 | |
|   ScopedClassInfo(jvmtiEnv* jvmtienv, jclass c)
 | |
|       : jvmtienv_(jvmtienv),
 | |
|         class_(c),
 | |
|         name_(nullptr),
 | |
|         file_(nullptr),
 | |
|         debug_ext_(nullptr) {}
 | |
| 
 | |
|   ~ScopedClassInfo() {
 | |
|     if (class_ != nullptr) {
 | |
|       jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
 | |
|       jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_));
 | |
|       jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool Init() {
 | |
|     if (class_ == nullptr) {
 | |
|       name_ = const_cast<char*>("<NONE>");
 | |
|       return true;
 | |
|     } else {
 | |
|       jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_);
 | |
|       jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_);
 | |
|       return jvmtienv_->GetClassSignature(class_, &name_, nullptr) == JVMTI_ERROR_NONE &&
 | |
|           ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY &&
 | |
|           ret1 != JVMTI_ERROR_INVALID_CLASS &&
 | |
|           ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY &&
 | |
|           ret2 != JVMTI_ERROR_INVALID_CLASS;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   jclass GetClass() const {
 | |
|     return class_;
 | |
|   }
 | |
|   const char* GetName() const {
 | |
|     return name_;
 | |
|   }
 | |
|   const char* GetSourceDebugExtension() const {
 | |
|     if (debug_ext_ == nullptr) {
 | |
|       return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>";
 | |
|     } else {
 | |
|       return debug_ext_;
 | |
|     }
 | |
|   }
 | |
|   const char* GetSourceFileName() const {
 | |
|     if (file_ == nullptr) {
 | |
|       return "<UNKNOWN_FILE>";
 | |
|     } else {
 | |
|       return file_;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   jvmtiEnv* jvmtienv_;
 | |
|   jclass class_;
 | |
|   char* name_;
 | |
|   char* file_;
 | |
|   char* debug_ext_;
 | |
| };
 | |
| 
 | |
| class ScopedMethodInfo {
 | |
|  public:
 | |
|   ScopedMethodInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m)
 | |
|       : jvmtienv_(jvmtienv),
 | |
|         env_(env),
 | |
|         method_(m),
 | |
|         declaring_class_(nullptr),
 | |
|         class_info_(nullptr),
 | |
|         name_(nullptr),
 | |
|         signature_(nullptr),
 | |
|         first_line_(-1) {}
 | |
| 
 | |
|   ~ScopedMethodInfo() {
 | |
|     DeleteLocalRef(env_, declaring_class_);
 | |
|     jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
 | |
|     jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(signature_));
 | |
|   }
 | |
| 
 | |
|   bool Init() {
 | |
|     if (jvmtienv_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) {
 | |
|       return false;
 | |
|     }
 | |
|     class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_));
 | |
|     jint nlines;
 | |
|     jvmtiLineNumberEntry* lines;
 | |
|     jvmtiError err = jvmtienv_->GetLineNumberTable(method_, &nlines, &lines);
 | |
|     if (err == JVMTI_ERROR_NONE) {
 | |
|       if (nlines > 0) {
 | |
|         first_line_ = lines[0].line_number;
 | |
|       }
 | |
|       jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(lines));
 | |
|     } else if (err != JVMTI_ERROR_ABSENT_INFORMATION &&
 | |
|                err != JVMTI_ERROR_NATIVE_METHOD) {
 | |
|       return false;
 | |
|     }
 | |
|     return class_info_->Init() &&
 | |
|         (jvmtienv_->GetMethodName(method_, &name_, &signature_, nullptr) == JVMTI_ERROR_NONE);
 | |
|   }
 | |
| 
 | |
|   const ScopedClassInfo& GetDeclaringClassInfo() const {
 | |
|     return *class_info_;
 | |
|   }
 | |
| 
 | |
|   jclass GetDeclaringClass() const {
 | |
|     return declaring_class_;
 | |
|   }
 | |
| 
 | |
|   const char* GetName() const {
 | |
|     return name_;
 | |
|   }
 | |
| 
 | |
|   const char* GetSignature() const {
 | |
|     return signature_;
 | |
|   }
 | |
| 
 | |
|   jint GetFirstLine() const {
 | |
|     return first_line_;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   jvmtiEnv* jvmtienv_;
 | |
|   JNIEnv* env_;
 | |
|   jmethodID method_;
 | |
|   jclass declaring_class_;
 | |
|   std::unique_ptr<ScopedClassInfo> class_info_;
 | |
|   char* name_;
 | |
|   char* signature_;
 | |
|   jint first_line_;
 | |
| 
 | |
|   friend std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m);
 | |
| };
 | |
| 
 | |
| class ScopedFieldInfo {
 | |
|  public:
 | |
|   ScopedFieldInfo(jvmtiEnv* jvmtienv, jclass field_klass, jfieldID field)
 | |
|       : jvmtienv_(jvmtienv),
 | |
|         declaring_class_(field_klass),
 | |
|         field_(field),
 | |
|         class_info_(nullptr),
 | |
|         name_(nullptr),
 | |
|         type_(nullptr) {}
 | |
| 
 | |
|   ~ScopedFieldInfo() {
 | |
|     jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
 | |
|     jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(type_));
 | |
|   }
 | |
| 
 | |
|   bool Init() {
 | |
|     class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_));
 | |
|     return class_info_->Init() &&
 | |
|         (jvmtienv_->GetFieldName(
 | |
|             declaring_class_, field_, &name_, &type_, nullptr) == JVMTI_ERROR_NONE);
 | |
|   }
 | |
| 
 | |
|   const ScopedClassInfo& GetDeclaringClassInfo() const {
 | |
|     return *class_info_;
 | |
|   }
 | |
| 
 | |
|   jclass GetDeclaringClass() const {
 | |
|     return declaring_class_;
 | |
|   }
 | |
| 
 | |
|   const char* GetName() const {
 | |
|     return name_;
 | |
|   }
 | |
| 
 | |
|   const char* GetType() const {
 | |
|     return type_;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   jvmtiEnv* jvmtienv_;
 | |
|   jclass declaring_class_;
 | |
|   jfieldID field_;
 | |
|   std::unique_ptr<ScopedClassInfo> class_info_;
 | |
|   char* name_;
 | |
|   char* type_;
 | |
| 
 | |
|   friend std::ostream& operator<<(std::ostream &os, ScopedFieldInfo const& m);
 | |
| };
 | |
| 
 | |
| std::ostream& operator<<(std::ostream &os, const ScopedFieldInfo* m) {
 | |
|   return os << *m;
 | |
| }
 | |
| 
 | |
| std::ostream& operator<<(std::ostream &os, ScopedFieldInfo const& m) {
 | |
|   return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName()
 | |
|             << ":" << m.GetType();
 | |
| }
 | |
| 
 | |
| std::ostream& operator<<(std::ostream &os, const ScopedMethodInfo* m) {
 | |
|   return os << *m;
 | |
| }
 | |
| 
 | |
| std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m) {
 | |
|   return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature()
 | |
|             << " (source: " << m.GetDeclaringClassInfo().GetSourceFileName() << ":"
 | |
|             << m.GetFirstLine() << ")";
 | |
| }
 | |
| 
 | |
| static void doJvmtiMethodBind(jvmtiEnv* jvmtienv,
 | |
|                               JNIEnv* env,
 | |
|                               jthread thread,
 | |
|                               jmethodID m,
 | |
|                               void* address,
 | |
|                               /*out*/void** out_address) {
 | |
|   *out_address = address;
 | |
|   ScopedThreadInfo thread_info(jvmtienv, env, thread);
 | |
|   ScopedMethodInfo method_info(jvmtienv, env, m);
 | |
|   if (!method_info.Init()) {
 | |
|     LOG(ERROR) << "Unable to get method info!";
 | |
|     return;
 | |
|   }
 | |
|   LOG(INFO) << "Loading native method \"" << method_info << "\". Thread is "
 | |
|             << thread_info.GetName();
 | |
| }
 | |
| 
 | |
| static std::string GetName(jvmtiEnv* jvmtienv, JNIEnv* jnienv, jobject obj) {
 | |
|   jclass klass = jnienv->GetObjectClass(obj);
 | |
|   char *cname, *cgen;
 | |
|   if (jvmtienv->GetClassSignature(klass, &cname, &cgen) != JVMTI_ERROR_NONE) {
 | |
|     LOG(ERROR) << "Unable to get class name!";
 | |
|     DeleteLocalRef(jnienv, klass);
 | |
|     return "<UNKNOWN>";
 | |
|   }
 | |
|   std::string name(cname);
 | |
|   if (name == "Ljava/lang/String;") {
 | |
|     jstring str = reinterpret_cast<jstring>(obj);
 | |
|     const char* val = jnienv->GetStringUTFChars(str, nullptr);
 | |
|     if (val == nullptr) {
 | |
|       name += " (unable to get value)";
 | |
|     } else {
 | |
|       std::ostringstream oss;
 | |
|       oss << name << " (value: \"" << val << "\")";
 | |
|       name = oss.str();
 | |
|       jnienv->ReleaseStringUTFChars(str, val);
 | |
|     }
 | |
|   }
 | |
|   jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname));
 | |
|   jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen));
 | |
|   DeleteLocalRef(jnienv, klass);
 | |
|   return name;
 | |
| }
 | |
| 
 | |
| static std::string GetValOf(jvmtiEnv* env, JNIEnv* jnienv, std::string type, jvalue val) {
 | |
|   std::ostringstream oss;
 | |
|   switch (type[0]) {
 | |
|     case '[':
 | |
|     case 'L':
 | |
|       return val.l != nullptr ? GetName(env, jnienv, val.l) : "null";
 | |
|     case 'Z':
 | |
|       return val.z == JNI_TRUE ? "true" : "false";
 | |
|     case 'B':
 | |
|       oss << val.b;
 | |
|       return oss.str();
 | |
|     case 'C':
 | |
|       oss << val.c;
 | |
|       return oss.str();
 | |
|     case 'S':
 | |
|       oss << val.s;
 | |
|       return oss.str();
 | |
|     case 'I':
 | |
|       oss << val.i;
 | |
|       return oss.str();
 | |
|     case 'J':
 | |
|       oss << val.j;
 | |
|       return oss.str();
 | |
|     case 'F':
 | |
|       oss << val.f;
 | |
|       return oss.str();
 | |
|     case 'D':
 | |
|       oss << val.d;
 | |
|       return oss.str();
 | |
|     case 'V':
 | |
|       return "<void>";
 | |
|     default:
 | |
|       return "<ERROR Found type " + type + ">";
 | |
|   }
 | |
| }
 | |
| 
 | |
| void JNICALL FieldAccessHook(jvmtiEnv* jvmtienv,
 | |
|                              JNIEnv* env,
 | |
|                              jthread thread,
 | |
|                              jmethodID m,
 | |
|                              jlocation location,
 | |
|                              jclass field_klass,
 | |
|                              jobject object,
 | |
|                              jfieldID field) {
 | |
|   ScopedThreadInfo info(jvmtienv, env, thread);
 | |
|   ScopedMethodInfo method_info(jvmtienv, env, m);
 | |
|   ScopedFieldInfo field_info(jvmtienv, field_klass, field);
 | |
|   jclass oklass = (object != nullptr) ? env->GetObjectClass(object) : nullptr;
 | |
|   ScopedClassInfo obj_class_info(jvmtienv, oklass);
 | |
|   if (!method_info.Init() || !field_info.Init() || !obj_class_info.Init()) {
 | |
|     LOG(ERROR) << "Unable to get callback info!";
 | |
|     return;
 | |
|   }
 | |
|   LOG(INFO) << "ACCESS field \"" << field_info << "\" on object of "
 | |
|             << "type \"" << obj_class_info.GetName() << "\" in method \"" << method_info
 | |
|             << "\" at location 0x" << std::hex << location << ". Thread is \""
 | |
|             << info.GetName() << "\".";
 | |
|   DeleteLocalRef(env, oklass);
 | |
| }
 | |
| 
 | |
| static std::string PrintJValue(jvmtiEnv* jvmtienv, JNIEnv* env, char type, jvalue new_value) {
 | |
|   std::ostringstream oss;
 | |
|   switch (type) {
 | |
|     case 'L': {
 | |
|       jobject nv = new_value.l;
 | |
|       if (nv == nullptr) {
 | |
|         oss << "\"null\"";
 | |
|       } else {
 | |
|         jclass nv_klass = env->GetObjectClass(nv);
 | |
|         ScopedClassInfo nv_class_info(jvmtienv, nv_klass);
 | |
|         if (!nv_class_info.Init()) {
 | |
|           oss << "with unknown type";
 | |
|         } else {
 | |
|           oss << "of type \"" << nv_class_info.GetName() << "\"";
 | |
|         }
 | |
|         DeleteLocalRef(env, nv_klass);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 'Z': {
 | |
|       if (new_value.z) {
 | |
|         oss << "true";
 | |
|       } else {
 | |
|         oss << "false";
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
| #define SEND_VALUE(chr, sym, type) \
 | |
|     case chr: { \
 | |
|       oss << static_cast<type>(new_value.sym); \
 | |
|       break; \
 | |
|     }
 | |
|     SEND_VALUE('B', b, int8_t);
 | |
|     SEND_VALUE('C', c, uint16_t);
 | |
|     SEND_VALUE('S', s, int16_t);
 | |
|     SEND_VALUE('I', i, int32_t);
 | |
|     SEND_VALUE('J', j, int64_t);
 | |
|     SEND_VALUE('F', f, float);
 | |
|     SEND_VALUE('D', d, double);
 | |
| #undef SEND_VALUE
 | |
|   }
 | |
|   return oss.str();
 | |
| }
 | |
| 
 | |
| void JNICALL FieldModificationHook(jvmtiEnv* jvmtienv,
 | |
|                                    JNIEnv* env,
 | |
|                                    jthread thread,
 | |
|                                    jmethodID m,
 | |
|                                    jlocation location,
 | |
|                                    jclass field_klass,
 | |
|                                    jobject object,
 | |
|                                    jfieldID field,
 | |
|                                    char type,
 | |
|                                    jvalue new_value) {
 | |
|   ScopedThreadInfo info(jvmtienv, env, thread);
 | |
|   ScopedMethodInfo method_info(jvmtienv, env, m);
 | |
|   ScopedFieldInfo field_info(jvmtienv, field_klass, field);
 | |
|   jclass oklass = (object != nullptr) ? env->GetObjectClass(object) : nullptr;
 | |
|   ScopedClassInfo obj_class_info(jvmtienv, oklass);
 | |
|   if (!method_info.Init() || !field_info.Init() || !obj_class_info.Init()) {
 | |
|     LOG(ERROR) << "Unable to get callback info!";
 | |
|     return;
 | |
|   }
 | |
|   LOG(INFO) << "MODIFY field \"" << field_info << "\" on object of "
 | |
|             << "type \"" << obj_class_info.GetName() << "\" in method \"" << method_info
 | |
|             << "\" at location 0x" << std::hex << location << std::dec << ". New value is "
 | |
|             << PrintJValue(jvmtienv, env, type, new_value) << ". Thread is \""
 | |
|             << info.GetName() << "\".";
 | |
|   DeleteLocalRef(env, oklass);
 | |
| }
 | |
| void JNICALL MethodExitHook(jvmtiEnv* jvmtienv,
 | |
|                             JNIEnv* env,
 | |
|                             jthread thread,
 | |
|                             jmethodID m,
 | |
|                             jboolean was_popped_by_exception,
 | |
|                             jvalue val) {
 | |
|   ScopedThreadInfo info(jvmtienv, env, thread);
 | |
|   ScopedMethodInfo method_info(jvmtienv, env, m);
 | |
|   if (!method_info.Init()) {
 | |
|     LOG(ERROR) << "Unable to get method info!";
 | |
|     return;
 | |
|   }
 | |
|   std::string type(method_info.GetSignature());
 | |
|   type = type.substr(type.find(')') + 1);
 | |
|   std::string out_val(was_popped_by_exception ? "" : GetValOf(jvmtienv, env, type, val));
 | |
|   LOG(INFO) << "Leaving method \"" << method_info << "\". Thread is \"" << info.GetName() << "\"."
 | |
|             << std::endl
 | |
|             << "    Cause: " << (was_popped_by_exception ? "exception" : "return ")
 | |
|             << out_val << ".";
 | |
| }
 | |
| 
 | |
| void JNICALL MethodEntryHook(jvmtiEnv* jvmtienv,
 | |
|                              JNIEnv* env,
 | |
|                              jthread thread,
 | |
|                              jmethodID m) {
 | |
|   ScopedThreadInfo info(jvmtienv, env, thread);
 | |
|   ScopedMethodInfo method_info(jvmtienv, env, m);
 | |
|   if (!method_info.Init()) {
 | |
|     LOG(ERROR) << "Unable to get method info!";
 | |
|     return;
 | |
|   }
 | |
|   LOG(INFO) << "Entering method \"" << method_info << "\". Thread is \"" << info.GetName() << "\"";
 | |
| }
 | |
| 
 | |
| void JNICALL ClassPrepareHook(jvmtiEnv* jvmtienv,
 | |
|                               JNIEnv* env,
 | |
|                               jthread thread,
 | |
|                               jclass klass) {
 | |
|   StressData* data = nullptr;
 | |
|   CHECK_EQ(jvmtienv->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
 | |
|            JVMTI_ERROR_NONE);
 | |
|   if (data->field_stress) {
 | |
|     jint nfields;
 | |
|     jfieldID* fields;
 | |
|     if (jvmtienv->GetClassFields(klass, &nfields, &fields) != JVMTI_ERROR_NONE) {
 | |
|       LOG(ERROR) << "Unable to get a classes fields!";
 | |
|       return;
 | |
|     }
 | |
|     for (jint i = 0; i < nfields; i++) {
 | |
|       jfieldID f = fields[i];
 | |
|       // Ignore errors
 | |
|       jvmtienv->SetFieldAccessWatch(klass, f);
 | |
|       jvmtienv->SetFieldModificationWatch(klass, f);
 | |
|     }
 | |
|     jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fields));
 | |
|   }
 | |
|   if (data->trace_stress) {
 | |
|     ScopedThreadInfo info(jvmtienv, env, thread);
 | |
|     ScopedClassInfo class_info(jvmtienv, klass);
 | |
|     if (!class_info.Init()) {
 | |
|       LOG(ERROR) << "Unable to get class info!";
 | |
|       return;
 | |
|     }
 | |
|     LOG(INFO) << "Prepared class \"" << class_info.GetName() << "\". Thread is \""
 | |
|               << info.GetName() << "\"";
 | |
|   }
 | |
| }
 | |
| 
 | |
| void JNICALL SingleStepHook(jvmtiEnv* jvmtienv,
 | |
|                             JNIEnv* env,
 | |
|                             jthread thread,
 | |
|                             jmethodID method,
 | |
|                             jlocation location) {
 | |
|   ScopedThreadInfo info(jvmtienv, env, thread);
 | |
|   ScopedMethodInfo method_info(jvmtienv, env, method);
 | |
|   if (!method_info.Init()) {
 | |
|     LOG(ERROR) << "Unable to get method info!";
 | |
|     return;
 | |
|   }
 | |
|   LOG(INFO) << "Single step at location: 0x" << std::setw(8) << std::setfill('0') << std::hex
 | |
|             << location << " in method " << method_info << " thread: " << info.GetName();
 | |
| }
 | |
| 
 | |
| // The hook we are using.
 | |
| void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti,
 | |
|                                          JNIEnv* jni_env ATTRIBUTE_UNUSED,
 | |
|                                          jclass class_being_redefined ATTRIBUTE_UNUSED,
 | |
|                                          jobject loader ATTRIBUTE_UNUSED,
 | |
|                                          const char* name,
 | |
|                                          jobject protection_domain ATTRIBUTE_UNUSED,
 | |
|                                          jint class_data_len,
 | |
|                                          const unsigned char* class_data,
 | |
|                                          jint* new_class_data_len,
 | |
|                                          unsigned char** new_class_data) {
 | |
|   std::vector<unsigned char> out;
 | |
|   // Make the jvmti semi-descriptor into the full descriptor.
 | |
|   std::string name_str("L");
 | |
|   name_str += name;
 | |
|   name_str += ";";
 | |
|   StressData* data = nullptr;
 | |
|   CHECK_EQ(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
 | |
|            JVMTI_ERROR_NONE);
 | |
|   if (!data->vm_class_loader_initialized) {
 | |
|     LOG(WARNING) << "Ignoring load of class " << name << " because VMClassLoader is not yet "
 | |
|                  << "initialized. Transforming this class could cause spurious test failures.";
 | |
|     return;
 | |
|   } else if (DoExtractClassFromData(jvmti, name_str, class_data_len, class_data,
 | |
|                                     /*out*/ new_class_data_len, /*out*/ new_class_data)) {
 | |
|     LOG(INFO) << "Extracted class: " << name;
 | |
|   } else {
 | |
|     std::cerr << "Unable to extract class " << name << std::endl;
 | |
|     *new_class_data_len = 0;
 | |
|     *new_class_data = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static std::string AdvanceOption(const std::string& ops) {
 | |
|   return ops.substr(ops.find(',') + 1);
 | |
| }
 | |
| 
 | |
| static bool HasNextOption(const std::string& ops) {
 | |
|   return ops.find(',') != std::string::npos;
 | |
| }
 | |
| 
 | |
| static std::string GetOption(const std::string& in) {
 | |
|   return in.substr(0, in.find(','));
 | |
| }
 | |
| 
 | |
| // Options are
 | |
| // jvmti-stress,[redefine,][trace,][field]
 | |
| static void ReadOptions(StressData* data, char* options) {
 | |
|   std::string ops(options);
 | |
|   CHECK_EQ(GetOption(ops), "jvmti-stress") << "Options should start with jvmti-stress";
 | |
|   do {
 | |
|     ops = AdvanceOption(ops);
 | |
|     std::string cur = GetOption(ops);
 | |
|     if (cur == "trace") {
 | |
|       data->trace_stress = true;
 | |
|     } else if (cur == "step") {
 | |
|       data->step_stress = true;
 | |
|     } else if (cur == "field") {
 | |
|       data->field_stress = true;
 | |
|     } else if (cur == "redefine") {
 | |
|       data->redefine_stress = true;
 | |
|     } else {
 | |
|       LOG(FATAL) << "Unknown option: " << GetOption(ops);
 | |
|     }
 | |
|   } while (HasNextOption(ops));
 | |
| }
 | |
| 
 | |
| // Do final setup during the VMInit callback. By this time most things are all setup.
 | |
| static void JNICALL PerformFinalSetupVMInit(jvmtiEnv *jvmti_env,
 | |
|                                             JNIEnv* jni_env,
 | |
|                                             jthread thread ATTRIBUTE_UNUSED) {
 | |
|   // Load the VMClassLoader class. We will get a ClassNotFound exception because we don't have
 | |
|   // visibility but the class will be loaded behind the scenes.
 | |
|   LOG(INFO) << "manual load & initialization of class java/lang/VMClassLoader!";
 | |
|   jclass klass = jni_env->FindClass("java/lang/VMClassLoader");
 | |
|   StressData* data = nullptr;
 | |
|   CHECK_EQ(jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
 | |
|            JVMTI_ERROR_NONE);
 | |
|   // We need to make sure that VMClassLoader is initialized before we start redefining anything
 | |
|   // since it can give (non-fatal) error messages if it's initialized after we've redefined BCP
 | |
|   // classes. These error messages are expected and no problem but they will mess up our testing
 | |
|   // infrastructure.
 | |
|   if (klass == nullptr) {
 | |
|     // Probably on RI. Clear the exception so we can continue but don't mark vmclassloader as
 | |
|     // initialized.
 | |
|     LOG(WARNING) << "Unable to find VMClassLoader class!";
 | |
|     jni_env->ExceptionClear();
 | |
|   } else {
 | |
|     // GetMethodID is spec'd to cause the class to be initialized.
 | |
|     jni_env->GetMethodID(klass, "hashCode", "()I");
 | |
|     DeleteLocalRef(jni_env, klass);
 | |
|     data->vm_class_loader_initialized = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool WatchAllFields(JavaVM* vm, jvmtiEnv* jvmti) {
 | |
|   if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                       JVMTI_EVENT_CLASS_PREPARE,
 | |
|                                       nullptr) != JVMTI_ERROR_NONE) {
 | |
|     LOG(ERROR) << "Couldn't set prepare event!";
 | |
|     return false;
 | |
|   }
 | |
|   // TODO We really shouldn't need to do this step here.
 | |
|   jint nklass;
 | |
|   jclass* klasses;
 | |
|   if (jvmti->GetLoadedClasses(&nklass, &klasses) != JVMTI_ERROR_NONE) {
 | |
|     LOG(WARNING) << "Couldn't get loaded classes! Ignoring.";
 | |
|     return true;
 | |
|   }
 | |
|   JNIEnv* jni = nullptr;
 | |
|   if (vm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6)) {
 | |
|     LOG(ERROR) << "Unable to get jni env. Ignoring and potentially leaking jobjects.";
 | |
|     return false;
 | |
|   }
 | |
|   for (jint i = 0; i < nklass; i++) {
 | |
|     jclass k = klasses[i];
 | |
|     ScopedClassInfo sci(jvmti, k);
 | |
|     if (sci.Init()) {
 | |
|       LOG(INFO) << "NOTE: class " << sci.GetName() << " already loaded.";
 | |
|     }
 | |
|     jint nfields;
 | |
|     jfieldID* fields;
 | |
|     jvmtiError err = jvmti->GetClassFields(k, &nfields, &fields);
 | |
|     if (err == JVMTI_ERROR_NONE) {
 | |
|       for (jint j = 0; j < nfields; j++) {
 | |
|         jfieldID f = fields[j];
 | |
|         if (jvmti->SetFieldModificationWatch(k, f) != JVMTI_ERROR_NONE ||
 | |
|             jvmti->SetFieldAccessWatch(k, f) != JVMTI_ERROR_NONE) {
 | |
|           LOG(ERROR) << "Unable to set watches on a field.";
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|     } else if (err != JVMTI_ERROR_CLASS_NOT_PREPARED) {
 | |
|       LOG(ERROR) << "Unexpected error getting class fields!";
 | |
|       return false;
 | |
|     }
 | |
|     jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields));
 | |
|     DeleteLocalRef(jni, k);
 | |
|   }
 | |
|   jvmti->Deallocate(reinterpret_cast<unsigned char*>(klasses));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm,
 | |
|                                                char* options,
 | |
|                                                void* reserved ATTRIBUTE_UNUSED) {
 | |
|   jvmtiEnv* jvmti = nullptr;
 | |
|   if (vm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_0)) {
 | |
|     LOG(ERROR) << "Unable to get jvmti env.";
 | |
|     return 1;
 | |
|   }
 | |
|   StressData* data = nullptr;
 | |
|   if (JVMTI_ERROR_NONE != jvmti->Allocate(sizeof(StressData),
 | |
|                                           reinterpret_cast<unsigned char**>(&data))) {
 | |
|     LOG(ERROR) << "Unable to allocate data for stress test.";
 | |
|     return 1;
 | |
|   }
 | |
|   memset(data, 0, sizeof(StressData));
 | |
|   // Read the options into the static variables that hold them.
 | |
|   ReadOptions(data, options);
 | |
|   // Save the data
 | |
|   if (JVMTI_ERROR_NONE != jvmti->SetEnvironmentLocalStorage(data)) {
 | |
|     LOG(ERROR) << "Unable to save stress test data.";
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   // Just get all capabilities.
 | |
|   jvmtiCapabilities caps = {
 | |
|     .can_tag_objects                                 = 0,
 | |
|     .can_generate_field_modification_events          = 1,
 | |
|     .can_generate_field_access_events                = 1,
 | |
|     .can_get_bytecodes                               = 0,
 | |
|     .can_get_synthetic_attribute                     = 0,
 | |
|     .can_get_owned_monitor_info                      = 0,
 | |
|     .can_get_current_contended_monitor               = 0,
 | |
|     .can_get_monitor_info                            = 0,
 | |
|     .can_pop_frame                                   = 0,
 | |
|     .can_redefine_classes                            = 1,
 | |
|     .can_signal_thread                               = 0,
 | |
|     .can_get_source_file_name                        = 1,
 | |
|     .can_get_line_numbers                            = 1,
 | |
|     .can_get_source_debug_extension                  = 1,
 | |
|     .can_access_local_variables                      = 0,
 | |
|     .can_maintain_original_method_order              = 0,
 | |
|     .can_generate_single_step_events                 = 1,
 | |
|     .can_generate_exception_events                   = 0,
 | |
|     .can_generate_frame_pop_events                   = 0,
 | |
|     .can_generate_breakpoint_events                  = 0,
 | |
|     .can_suspend                                     = 0,
 | |
|     .can_redefine_any_class                          = 0,
 | |
|     .can_get_current_thread_cpu_time                 = 0,
 | |
|     .can_get_thread_cpu_time                         = 0,
 | |
|     .can_generate_method_entry_events                = 1,
 | |
|     .can_generate_method_exit_events                 = 1,
 | |
|     .can_generate_all_class_hook_events              = 0,
 | |
|     .can_generate_compiled_method_load_events        = 0,
 | |
|     .can_generate_monitor_events                     = 0,
 | |
|     .can_generate_vm_object_alloc_events             = 0,
 | |
|     .can_generate_native_method_bind_events          = 1,
 | |
|     .can_generate_garbage_collection_events          = 0,
 | |
|     .can_generate_object_free_events                 = 0,
 | |
|     .can_force_early_return                          = 0,
 | |
|     .can_get_owned_monitor_stack_depth_info          = 0,
 | |
|     .can_get_constant_pool                           = 0,
 | |
|     .can_set_native_method_prefix                    = 0,
 | |
|     .can_retransform_classes                         = 1,
 | |
|     .can_retransform_any_class                       = 0,
 | |
|     .can_generate_resource_exhaustion_heap_events    = 0,
 | |
|     .can_generate_resource_exhaustion_threads_events = 0,
 | |
|   };
 | |
|   jvmti->AddCapabilities(&caps);
 | |
| 
 | |
|   // Set callbacks.
 | |
|   jvmtiEventCallbacks cb;
 | |
|   memset(&cb, 0, sizeof(cb));
 | |
|   cb.ClassFileLoadHook = ClassFileLoadHookSecretNoOp;
 | |
|   cb.NativeMethodBind = doJvmtiMethodBind;
 | |
|   cb.VMInit = PerformFinalSetupVMInit;
 | |
|   cb.MethodEntry = MethodEntryHook;
 | |
|   cb.MethodExit = MethodExitHook;
 | |
|   cb.FieldAccess = FieldAccessHook;
 | |
|   cb.FieldModification = FieldModificationHook;
 | |
|   cb.ClassPrepare = ClassPrepareHook;
 | |
|   cb.SingleStep = SingleStepHook;
 | |
|   if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
 | |
|     LOG(ERROR) << "Unable to set class file load hook cb!";
 | |
|     return 1;
 | |
|   }
 | |
|   if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                       JVMTI_EVENT_VM_INIT,
 | |
|                                       nullptr) != JVMTI_ERROR_NONE) {
 | |
|     LOG(ERROR) << "Unable to enable JVMTI_EVENT_VM_INIT event!";
 | |
|     return 1;
 | |
|   }
 | |
|   if (data->redefine_stress) {
 | |
|     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                         JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
 | |
|                                         nullptr) != JVMTI_ERROR_NONE) {
 | |
|       LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!";
 | |
|       return 1;
 | |
|     }
 | |
|   }
 | |
|   if (data->trace_stress) {
 | |
|     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                         JVMTI_EVENT_CLASS_PREPARE,
 | |
|                                         nullptr) != JVMTI_ERROR_NONE) {
 | |
|       LOG(ERROR) << "Unable to enable CLASS_PREPARE event!";
 | |
|       return 1;
 | |
|     }
 | |
|     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                         JVMTI_EVENT_NATIVE_METHOD_BIND,
 | |
|                                         nullptr) != JVMTI_ERROR_NONE) {
 | |
|       LOG(ERROR) << "Unable to enable JVMTI_EVENT_NATIVE_METHOD_BIND event!";
 | |
|       return 1;
 | |
|     }
 | |
|     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                         JVMTI_EVENT_METHOD_ENTRY,
 | |
|                                         nullptr) != JVMTI_ERROR_NONE) {
 | |
|       LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!";
 | |
|       return 1;
 | |
|     }
 | |
|     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                         JVMTI_EVENT_METHOD_EXIT,
 | |
|                                         nullptr) != JVMTI_ERROR_NONE) {
 | |
|       LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!";
 | |
|       return 1;
 | |
|     }
 | |
|   }
 | |
|   if (data->field_stress) {
 | |
|     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                         JVMTI_EVENT_FIELD_MODIFICATION,
 | |
|                                         nullptr) != JVMTI_ERROR_NONE) {
 | |
|       LOG(ERROR) << "Unable to enable FIELD_MODIFICATION event!";
 | |
|       return 1;
 | |
|     }
 | |
|     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                         JVMTI_EVENT_FIELD_ACCESS,
 | |
|                                         nullptr) != JVMTI_ERROR_NONE) {
 | |
|       LOG(ERROR) << "Unable to enable FIELD_ACCESS event!";
 | |
|       return 1;
 | |
|     }
 | |
|     if (!WatchAllFields(vm, jvmti)) {
 | |
|       return 1;
 | |
|     }
 | |
|   }
 | |
|   if (data->step_stress) {
 | |
|     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
 | |
|                                         JVMTI_EVENT_SINGLE_STEP,
 | |
|                                         nullptr) != JVMTI_ERROR_NONE) {
 | |
|       return 1;
 | |
|     }
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
 | |
|   return Agent_OnLoad(vm, options, reserved);
 | |
| }
 | |
| 
 | |
| }  // namespace art
 |