Home | History | Annotate | Download | only in simple-force-redefine
      1 // Copyright (C) 2019 The Android Open Source Project
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //      http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 //
     15 
     16 #include "__mutex_base"
     17 #include <cstddef>
     18 #include <fcntl.h>
     19 #include <fstream>
     20 #include <memory>
     21 #include <sstream>
     22 #include <string>
     23 #include <unistd.h>
     24 #include <unordered_set>
     25 
     26 #include <android-base/logging.h>
     27 #include <android-base/macros.h>
     28 
     29 #include <nativehelper/scoped_local_ref.h>
     30 
     31 #include <jni.h>
     32 #include <jvmti.h>
     33 
     34 // Slicer's headers have code that triggers these warnings. b/65298177
     35 #pragma clang diagnostic push
     36 #pragma clang diagnostic ignored "-Wunused-parameter"
     37 #pragma clang diagnostic ignored "-Wsign-compare"
     38 #include <slicer/code_ir.h>
     39 #include <slicer/dex_bytecode.h>
     40 #include <slicer/dex_ir.h>
     41 #include <slicer/dex_ir_builder.h>
     42 #include <slicer/reader.h>
     43 #include <slicer/writer.h>
     44 #pragma clang diagnostic pop
     45 
     46 namespace forceredefine {
     47 
     48 namespace {
     49 
     50 struct AgentInfo {
     51   std::fstream stream;
     52   std::unordered_set<std::string> classes;
     53   std::mutex mutex;
     54 };
     55 
     56 // Converts a class name to a type descriptor
     57 // (ex. "java.lang.String" to "Ljava/lang/String;")
     58 std::string classNameToDescriptor(const char* className) {
     59   std::stringstream ss;
     60   ss << "L";
     61   for (auto p = className; *p != '\0'; ++p) {
     62     ss << (*p == '.' ? '/' : *p);
     63   }
     64   ss << ";";
     65   return ss.str();
     66 }
     67 
     68 // Converts a descriptor (Lthis/style/of/name;) to a jni-FindClass style Fully-qualified class name
     69 // (this/style/of/name).
     70 std::string DescriptorToFQCN(const std::string& descriptor) {
     71   return descriptor.substr(1, descriptor.size() - 2);
     72 }
     73 
     74 static AgentInfo* GetAgentInfo(jvmtiEnv* jvmti) {
     75   AgentInfo* ai = nullptr;
     76   CHECK_EQ(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&ai)), JVMTI_ERROR_NONE);
     77   CHECK(ai != nullptr);
     78   return ai;
     79 }
     80 
     81 class JvmtiAllocator : public dex::Writer::Allocator {
     82  public:
     83   explicit JvmtiAllocator(jvmtiEnv* jvmti) : jvmti_(jvmti) {}
     84   void* Allocate(size_t size) override {
     85     unsigned char* res = nullptr;
     86     jvmti_->Allocate(size, &res);
     87     return res;
     88   }
     89   void Free(void* ptr) override {
     90     jvmti_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
     91   }
     92 
     93  private:
     94   jvmtiEnv* jvmti_;
     95 };
     96 
     97 static void Transform(std::shared_ptr<ir::DexFile> ir) {
     98   std::unique_ptr<ir::Builder> builder;
     99   for (auto& method : ir->encoded_methods) {
    100     // Do not look into abstract/bridge/native/synthetic methods.
    101     if ((method->access_flags &
    102          (dex::kAccAbstract | dex::kAccBridge | dex::kAccNative | dex::kAccSynthetic)) != 0) {
    103       continue;
    104     }
    105 
    106     struct AddNopVisitor : public lir::Visitor {
    107       explicit AddNopVisitor(lir::CodeIr* cir) : cir_(cir) {}
    108 
    109       bool Visit(lir::Bytecode* bc) override {
    110         if (seen_first_inst) {
    111           return false;
    112         }
    113         seen_first_inst = true;
    114         auto new_inst = cir_->Alloc<lir::Bytecode>();
    115         new_inst->opcode = dex::OP_NOP;
    116         cir_->instructions.InsertBefore(bc, new_inst);
    117         return true;
    118       }
    119 
    120       lir::CodeIr* cir_;
    121       bool seen_first_inst = false;
    122     };
    123 
    124     lir::CodeIr c(method.get(), ir);
    125     AddNopVisitor visitor(&c);
    126     for (auto it = c.instructions.begin(); it != c.instructions.end(); ++it) {
    127       lir::Instruction* fi = *it;
    128       if (fi->Accept(&visitor)) {
    129         break;
    130       }
    131     }
    132     c.Assemble();
    133   }
    134 }
    135 
    136 static void CbClassFileLoadHook(jvmtiEnv* jvmti,
    137                                 JNIEnv* env ATTRIBUTE_UNUSED,
    138                                 jclass classBeingRedefined ATTRIBUTE_UNUSED,
    139                                 jobject loader ATTRIBUTE_UNUSED,
    140                                 const char* name,
    141                                 jobject protectionDomain ATTRIBUTE_UNUSED,
    142                                 jint classDataLen,
    143                                 const unsigned char* classData,
    144                                 jint* newClassDataLen,
    145                                 unsigned char** newClassData) {
    146   std::string desc(classNameToDescriptor(name));
    147   std::string fqcn(DescriptorToFQCN(desc));
    148   AgentInfo* ai = GetAgentInfo(jvmti);
    149   {
    150     std::lock_guard<std::mutex> mu(ai->mutex);
    151     if (ai->classes.find(fqcn) == ai->classes.end()) {
    152       return;
    153     }
    154   }
    155   LOG(INFO) << "Got CFLH for " << name << " on env " << static_cast<void*>(jvmti);
    156   JvmtiAllocator allocator(jvmti);
    157   dex::Reader reader(classData, classDataLen);
    158   dex::u4 index = reader.FindClassIndex(desc.c_str());
    159   reader.CreateClassIr(index);
    160   std::shared_ptr<ir::DexFile> ir(reader.GetIr());
    161   Transform(ir);
    162   dex::Writer writer(ir);
    163   size_t new_size;
    164   *newClassData = writer.CreateImage(&allocator, &new_size);
    165   *newClassDataLen = new_size;
    166 }
    167 
    168 static jclass FindClass(jvmtiEnv* jvmti, JNIEnv* env, const std::string& name) {
    169   jclass res = env->FindClass(name.c_str());
    170   if (res != nullptr) {
    171     return res;
    172   }
    173   ScopedLocalRef<jthrowable> exc(env, env->ExceptionOccurred());
    174   env->ExceptionClear();
    175   // Try to find it in other classloaders.
    176   env->PushLocalFrame(1 << 18);
    177   do {
    178     jint cnt;
    179     jclass* klasses;
    180     if (jvmti->GetLoadedClasses(&cnt, &klasses) != JVMTI_ERROR_NONE) {
    181       LOG(ERROR) << "Unable to get loaded classes!";
    182       break;
    183     }
    184     for (jint i = 0; i < cnt; i++) {
    185       char* sig;
    186       if (jvmti->GetClassSignature(klasses[i], &sig, nullptr) != JVMTI_ERROR_NONE) {
    187         continue;
    188       }
    189       if (sig[0] == 'L' && DescriptorToFQCN(sig) == name) {
    190         res = klasses[i];
    191         break;
    192       }
    193     }
    194     jvmti->Deallocate(reinterpret_cast<unsigned char*>(klasses));
    195   } while (false);
    196   res = reinterpret_cast<jclass>(env->PopLocalFrame(res));
    197   if (res == nullptr && exc.get() != nullptr) {
    198     env->Throw(exc.get());
    199   }
    200   return res;
    201 }
    202 
    203 static void RedefineClass(jvmtiEnv* jvmti, JNIEnv* env, const std::string& klass_name) {
    204   jclass klass = nullptr;
    205   if ((klass = FindClass(jvmti, env, klass_name)) == nullptr) {
    206     LOG(WARNING) << "Failed to find class for " << klass_name;
    207     env->ExceptionDescribe();
    208     env->ExceptionClear();
    209     return;
    210   }
    211   jvmti->RetransformClasses(1, &klass);
    212   env->DeleteLocalRef(klass);
    213 }
    214 
    215 static void AgentMain(jvmtiEnv* jvmti, JNIEnv* jni, void* arg ATTRIBUTE_UNUSED) {
    216   AgentInfo* ai = GetAgentInfo(jvmti);
    217   std::string klass_name;
    218   jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr);
    219   // TODO Replace this with something that can read from a fifo and ignore the 'EOF's.
    220   while (std::getline(ai->stream, klass_name, '\n')) {
    221     LOG(INFO) << "Redefining class " << klass_name << " with " << static_cast<void*>(jvmti);
    222     {
    223       std::lock_guard<std::mutex> mu(ai->mutex);
    224       ai->classes.insert(klass_name);
    225     }
    226     RedefineClass(jvmti, jni, klass_name);
    227   }
    228 }
    229 
    230 static void CbVmInit(jvmtiEnv* jvmti, JNIEnv* env, jthread thr ATTRIBUTE_UNUSED) {
    231   // Create a Thread object.
    232   ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF("Agent Thread"));
    233   if (thread_name.get() == nullptr) {
    234     env->ExceptionDescribe();
    235     env->ExceptionClear();
    236     return;
    237   }
    238   ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread"));
    239   if (thread_klass.get() == nullptr) {
    240     env->ExceptionDescribe();
    241     env->ExceptionClear();
    242     return;
    243   }
    244   ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get()));
    245   if (thread.get() == nullptr) {
    246     env->ExceptionDescribe();
    247     env->ExceptionClear();
    248     return;
    249   }
    250 
    251   env->CallNonvirtualVoidMethod(
    252       thread.get(),
    253       thread_klass.get(),
    254       env->GetMethodID(thread_klass.get(), "<init>", "(Ljava/lang/String;)V"),
    255       thread_name.get());
    256   env->CallVoidMethod(thread.get(), env->GetMethodID(thread_klass.get(), "setPriority", "(I)V"), 1);
    257   env->CallVoidMethod(
    258       thread.get(), env->GetMethodID(thread_klass.get(), "setDaemon", "(Z)V"), JNI_TRUE);
    259 
    260   jvmti->RunAgentThread(thread.get(), AgentMain, nullptr, JVMTI_THREAD_MIN_PRIORITY);
    261 }
    262 
    263 }  // namespace
    264 
    265 template <bool kIsOnLoad>
    266 static jint AgentStart(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
    267   jvmtiEnv* jvmti = nullptr;
    268 
    269   if (vm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_1) != JNI_OK ||
    270       jvmti == nullptr) {
    271     LOG(ERROR) << "unable to obtain JVMTI env.";
    272     return JNI_ERR;
    273   }
    274   std::string sopts(options);
    275   AgentInfo* ai = new AgentInfo;
    276   ai->stream.open(options, std::ios_base::in);
    277   if (!ai->stream.is_open()) {
    278     PLOG(ERROR) << "Could not open file " << options << " for triggering class-reload";
    279     return JNI_ERR;
    280   }
    281 
    282   jvmtiCapabilities caps{
    283     .can_retransform_classes = 1,
    284   };
    285   if (jvmti->AddCapabilities(&caps) != JVMTI_ERROR_NONE) {
    286     LOG(ERROR) << "Unable to get retransform_classes capability!";
    287     return JNI_ERR;
    288   }
    289   jvmtiEventCallbacks cb{
    290     .ClassFileLoadHook = CbClassFileLoadHook,
    291     .VMInit = CbVmInit,
    292   };
    293   jvmti->SetEventCallbacks(&cb, sizeof(cb));
    294   jvmti->SetEnvironmentLocalStorage(reinterpret_cast<void*>(ai));
    295   if (kIsOnLoad) {
    296     jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, nullptr);
    297   } else {
    298     JNIEnv* jni = nullptr;
    299     vm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_2);
    300     jthread thr;
    301     jvmti->GetCurrentThread(&thr);
    302     CbVmInit(jvmti, jni, thr);
    303   }
    304   return JNI_OK;
    305 }
    306 
    307 // Late attachment (e.g. 'am attach-agent').
    308 extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
    309   return AgentStart<false>(vm, options, reserved);
    310 }
    311 
    312 // Early attachment
    313 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) {
    314   return AgentStart<true>(jvm, options, reserved);
    315 }
    316 
    317 }  // namespace forceredefine
    318