jvm调用JVMTI_EVENT_CLASS_FILE_LOAD_HOOK进行字节码变换的源码分析

1、首先,如果大家对javaagent不是太了解,可以先阅读寒泉子的博客:

http://www.infoq.com/cn/articles/javaagent-illustrated?utm_source=tuicool&utm_medium=referral

2、调用入口,ClassFileParser::parseClassFile,jvm在将类字节码解析成运行时的类的之前,就会调用agent注册的插桩,进行类字节码变换

if (JvmtiExport::should_post_class_file_load_hook()) { //调用Instrument进行字节码处理
    unsigned char* ptr = cfs->buffer(); //获取字节码
    unsigned char* end_ptr = cfs->buffer() + cfs->length(); //字节码的结尾

    JvmtiExport::post_class_file_load_hook(name, class_loader, protection_domain,//类名(jvm内部表示,形如java/lang/Object);类加载器;保护域
                                           &ptr, &end_ptr, //字节码的起始指针和结束指针
                                           &cached_class_file_bytes, //如果字节码被修改了,会将原来的字节码数据保存在这里
                                           &cached_class_file_length);

3、进一步处理,就是做了一步转发,不详解

void JvmtiExport::post_class_file_load_hook(Symbol* h_name,
                                            Handle class_loader,
                                            Handle h_protection_domain,
                                            unsigned char **data_ptr,
                                            unsigned char **end_ptr,
                                            unsigned char **cached_data_ptr,
                                            jint *cached_length_ptr) {
  JvmtiClassFileLoadHookPoster poster(h_name, class_loader,
                                      h_protection_domain,
                                      data_ptr, end_ptr,
                                      cached_data_ptr,
                                      cached_length_ptr);
  poster.post();
}


4、实际的处理

class JvmtiClassFileLoadHookPoster : public StackObj {
 private:
  Symbol*            _h_name; //类文件名
  Handle               _class_loader; //类加载器
  Handle               _h_protection_domain; //保护域,保存类的加载路径等信息
  unsigned char **     _data_ptr; //类字节码的起始指针
  unsigned char **     _end_ptr; //类字节码的结束指针
  JavaThread *         _thread; //当前线程,用于内存分配(TLAB)
  jint                 _curr_len; 
  unsigned char *      _curr_data; //当前正在处理的类字节码,是插桩链处理的中间结果
  JvmtiEnv *           _curr_env;
  jint *               _cached_length_ptr;
  unsigned char **     _cached_data_ptr;//如果类字节码被修改了,保存被修改前的字节码
  JvmtiThreadState *   _state;
  KlassHandle *        _h_class_being_redefined;
  JvmtiClassLoadKind   _load_kind;

 public:
  inline JvmtiClassFileLoadHookPoster(Symbol* h_name, Handle class_loader,
                                      Handle h_protection_domain, //保护域
                                      unsigned char **data_ptr, unsigned char **end_ptr, //指向实际的数据,初始时,传入原类字节码,转换后,传入处理完的数据
                                      unsigned char **cached_data_ptr, //如果类字节码,被修改了,原类的字节码会保存在这边
                                      jint *cached_length_ptr) {
    _h_name = h_name;
    _class_loader = class_loader;
    _h_protection_domain = h_protection_domain;
    _data_ptr = data_ptr;
    _end_ptr = end_ptr;
    _thread = JavaThread::current();
    _curr_len = *end_ptr - *data_ptr; //类字节码的长度
    _curr_data = *data_ptr; //类字节码的起始指针
    _curr_env = NULL;
    _cached_length_ptr = cached_length_ptr; //
    _cached_data_ptr = cached_data_ptr;
    *_cached_length_ptr = 0;
    *_cached_data_ptr = NULL; 

    _state = _thread->jvmti_thread_state(); //获取当前线程的状态
    if (_state != NULL) {
      _h_class_being_redefined = _state->get_class_being_redefined();
      _load_kind = _state->get_class_load_kind();
      // Clear class_being_redefined flag here. The action
      // from agent handler could generate a new class file load
      // hook event and if it is not cleared the new event generated
      // from regular class file load could have this stale redefined
      // class handle info.
      _state->clear_class_being_redefined();
    } else {
      // redefine and retransform will always set the thread state
      _h_class_being_redefined = (KlassHandle *) NULL;
      _load_kind = jvmti_class_load_kind_load;
    }
  }

  void post() {
//    EVT_TRIG_TRACE(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
//                   ("JVMTI [%s] class file load hook event triggered",
//                    JvmtiTrace::safe_get_thread_name(_thread)));
    post_all_envs(); //调用所有注册的事件的回调函数
    copy_modified_data();
  }

 private:
  void post_all_envs() {
    if (_load_kind != jvmti_class_load_kind_retransform) {
      // for class load and redefine,
      // call the non-retransformable agents
      JvmtiEnvIterator it;
      for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) { //遍历所有的JVMTIENV
        if (!env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) { //如果agent不能够转换类字节码,并且当前的环境运行类加载事件
          // non-retransformable agents cannot retransform back,
          // so no need to cache the original class file bytes
          post_to_env(env, false); //不能转换的agent,不需要缓存原来的字节码
        }
      }
    }
    JvmtiEnvIterator it;
    for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
      // retransformable agents get all events
      if (env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
        // retransformable agents need to cache the original class file
        // bytes if changes are made via the ClassFileLoadHook
        post_to_env(env, true);
      }
    }
  }

  void post_to_env(JvmtiEnv* env, bool caching_needed) {
    unsigned char *new_data = NULL;
    jint new_len = 0;
//    EVT_TRACE(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
//     ("JVMTI [%s] class file load hook event sent %s  data_ptr = %d, data_len = %d",
//               JvmtiTrace::safe_get_thread_name(_thread),
//               _h_name == NULL ? "NULL" : _h_name->as_utf8(),
//               _curr_data, _curr_len ));
    JvmtiClassFileLoadEventMark jem(_thread, _h_name, _class_loader,
                                    _h_protection_domain,
                                    _h_class_being_redefined);
    JvmtiJavaThreadEventTransition jet(_thread);
    JNIEnv* jni_env =  (JvmtiEnv::get_phase() == JVMTI_PHASE_PRIMORDIAL)?
                                                        NULL : jem.jni_env();
    jvmtiEventClassFileLoadHook callback = env->callbacks()->ClassFileLoadHook; //获取当前jvmtiEnv中注册的类文件转换句柄
    if (callback != NULL) {
      (*callback)(env->jvmti_external(), jni_env, //回调这个函数
                  jem.class_being_redefined(),
                  jem.jloader(), jem.class_name(),
                  jem.protection_domain(),
                  _curr_len, _curr_data,
                  &new_len, &new_data);
    }
    if (new_data != NULL) {
      // this agent has modified class data.
      if (caching_needed && *_cached_data_ptr == NULL) { //如果类字节码被修改了,需要缓存原来的类字节码
        // data has been changed by the new retransformable agent
        // and it hasn't already been cached, cache it
        *_cached_data_ptr = (unsigned char *)os::malloc(_curr_len);
        memcpy(*_cached_data_ptr, _curr_data, _curr_len);
        *_cached_length_ptr = _curr_len;
      }

      if (_curr_data != *_data_ptr) { //应该是_cuur_data != new_data吧?
        // curr_data is previous agent modified class data.
        // And this has been changed by the new agent so
        // we can delete it now.
        _curr_env->Deallocate(_curr_data);
      }

      // Class file data has changed by the current agent.
      _curr_data = new_data;
      _curr_len = new_len;
      // Save the current agent env we need this to deallocate the
      // memory allocated by this agent.
      _curr_env = env;
    }
  }

  void copy_modified_data() { //将最新的代码保存到数据指针里,返回给调用方
    // if one of the agent has modified class file data.
    // Copy modified class data to new resources array.
    if (_curr_data != *_data_ptr) { //变换后的数据和传入的数据是不同的,才保存的
      *_data_ptr = NEW_RESOURCE_ARRAY(u1, _curr_len);
      memcpy(*_data_ptr, _curr_data, _curr_len);
      *_end_ptr = *_data_ptr + _curr_len;
      _curr_env->Deallocate(_curr_data);
    }
  }
};


5、简化的流程图

jvm调用JVMTI_EVENT_CLASS_FILE_LOAD_HOOK进行字节码变换的源码分析_第1张图片


你可能感兴趣的:(jvm调用JVMTI_EVENT_CLASS_FILE_LOAD_HOOK进行字节码变换的源码分析)