注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

gcj如何实现java的反射功能  

2017-03-28 20:59:14|  分类: Gcc源代码分析 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
零、为什么考虑这个问题
    最近需要看下C#相关的代码,虽然C#编译器有开源的mono实现,不过项目较大,而且mono本身很多内容也是用CSharp实现,看起来比较别扭。好在java有不少和C#类似的功能,而且gcc的java实现代码量不算太大,所以还是从gcc对于java的实现来看下这个流程。当然,linux下的java是一个相对非主流的语言,甚至有传言说对该语言的支持将会从后续版本中删除,不过这并不影响对于问题的理解,因为相同功能在不同平台下的实现差别一般不会太大(毕竟幸福的家庭都是相似的,同样,优秀的实现方法也都大抵类似)。通常的linux发布版本默认都是不带java编译器的,所以如果需要的话可以自己安装,例如我使用的相对比较早期的redhat版本中安装包中java的安装包为java-1.5.0-gcj-1.5.0.0-29.1.el6.i686.rpm。由于要了解编译器的实现免不了调试,所以这里使用的编译器自己编译生成。
    相对于C++这种静态类型语言,java提供的一个比较有意思的功能就是"反射"(reflect、这个名字初看起来有些莫名其妙,不过据说是代表了“照镜子”的意义,通过反射可以 自省、矫正,也就是有“以铜为鉴 可以正衣冠 ”的意思),它可以让代码在运行的时候找到一个类的定义、方法、成员,这是C++所不具备的,这样的功能的典型应用场景还没有直观认识(毕竟没有用java做过项目),但是一个直观的考虑是可以将一些信息做成配置,比方说配置文件中可以配置接口的名称,代码中通过这些配置文件来找到这些接口的代码从而动态运行特定功能。
一、启动信息的生成
下面是非常简单的测试代码,
tsecer@harry: ll
total 4
-rw-r--r--. 1 root root 209 Mar 28 12:16 epic.java
tsecer@harry: cat epic.java 
class harry
{
int yoyo()
{
return 0x11111111;
}
int x;
};

class tsecer
{
public static void main(String argv[])
{
harry h = new harry();
Class c = h.getClass();
int i =  argv.length * 10;
}
};
tsecer@harry: /home/tsecer/Download/gccobj/gcc/gcj -C epic.java 
tsecer@harry: ll
total 12
-rw-r--r--. 1 root root 209 Mar 28 12:16 epic.java
-rw-r--r--. 1 root root 246 Mar 28 12:19 harry.class
-rw-r--r--. 1 root root 329 Mar 28 12:19 tsecer.class
tsecer@harry: 
可以看到,使用-C选项的时候为每个类生成了一个对应的class文件,为了作一个备忘,日志的最后附加上这两个文件的内容。但是这些class的内容我们倒不是很关心,它们只是一些中间的形式,通常java的库文件使用的比较多。gcj添加-v选项,可以看到它编译的时候是通过下面的命令来生成汇编指令的
tsecer@harry:  /home/tsecer/Download/gccobj/gcc/../libexec/gcc/i686-pc-linux-gnu/4.1.0/jc1 epic.java -fhash-synchronization -fno-use-divide-subroutine -fuse-boehm-gc -fnon-call-exceptions -fkeep-inline-functions -quiet -dumpbase epic.java -mtune=pentiumpro -auxbase epic -g1 -version -o epic.s
GNU Java version 4.1.0 (i686-pc-linux-gnu)
compiled by GNU C version 4.4.7 20120313 (Red Hat 4.4.7-11).
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Class path starts here:
    ./
    /home/tsecer/Download/gccobj/share/java/libgcj-4.1.0.jar/ (system) (zip)
tsecer@harry: 
为了从汇编代码中看这个内容,我们把生成的汇编代码的内容放到本文件夹下,也就是epic.s文件。
三、编译器生成的类元数据信息
    思考下反射的基本功能,其实是根据一个字符串在运行中找到这个字符串对应内容在内存中的位置,这里涉及到两个最基本的内容:“字符串”、“运行时”位置,字符串的信息在调用的时候可以确定,核心的问题是要有一个从字符串到运行时位置的字典。
    由于我们后面看到的都是直接的机器指令(而不是java虚拟机指令),所以下面都是看java本地化之后的内容。具体来说,对于一个类的元数据信息,在linux的具体实现中通过一个java::lang::Class类的对象来表示(这个类的声明位于gcc-4.1.0\libjava\java\lang\Class.h中),这个类定义的内容相对比较多,所以就不在这里拷贝代码了,并且我们这里首先要关注的是汇编代码传递的信息,由于汇编代码的内容同样比较多,这里也只是抽取这里关心的一部分来说明。下面的内容通过cat epic.s -n |c++filt >epic.c++filt.s生成,也就是将C++规则联编之后的名字还原。
   118 .size harry::harry(), .-harry::harry()
   119 .section .rodata
   120 .align 2
   121 .type _Utf1, @object
   122 .size _Utf1, 4
   123 _Utf1:
   124 .value 120
   125 .value 1
   126 .ascii "x"
   127 .zero 1
   128 .data
   129 .align 4
   130 .type _FL_harry, @object
   131 .size _FL_harry, 16
   132 _FL_harry:
   133 .long _Utf1 //一个_Jv_Field实例,对应_Jv_Utf8Const* name;
   134 .long _Jv_intClass //类型,这里的x是一个int对应jclass type;
   135 .value 0 //_Jv_ushort flags;
   136 .value 4 //_Jv_ushort bsize;
   137 .long 4 //union u
   138 .set .L_ZN5harry4yoyoEv0,harry::yoyo()
   139 .section .rodata
   140 .align 2
   141 .type _Utf2, @object
   142 .size _Utf2, 4
   143 _Utf2:
   144 .value 45228
   145 .value 4
   146 .ascii "yoyo"
   147 .zero 1
   148 .align 2
   149 .type _Utf3, @object
   150 .size _Utf3, 4
   151 _Utf3:
   152 .value 39784
   153 .value 3
   154 .ascii "()I"
   155 .zero 1
   156 .set .L_ZN5harryC1Ev1,harry::harry()
   157 .align 2
   158 .type _Utf4, @object
   159 .size _Utf4, 4
   160 _Utf4:
   161 .value 626
   162 .value 6
   163 .ascii "<init>"
   164 .zero 1
   165 .align 2
   166 .type _Utf5, @object
   167 .size _Utf5, 4
   168 _Utf5:
   169 .value 39797
   170 .value 3
   171 .ascii "()V"
   172 .zero 1
   173 .data
   174 .align 32
   175 .type _MT_harry, @object
   176 .size _MT_harry, 40
   177 _MT_harry:
   178 .long _Utf2 //_Jv_Utf8Const *name;,这个对应"yoyo"接口
   179 .long _Utf3 //_Jv_Utf8Const *signature;
   180 .value 16384 //_Jv_ushort accflags;
   181 .value 6 //_Jv_ushort index;
   182 .long .L_ZN5harry4yoyoEv0 //void *ncode;对应 harry定义的yoyo函数的位置,链接完成之后就指向yoyo函数位置
   183 .long 0 //_Jv_Utf8Const **throws;
   184 .long _Utf4 //下面是另一个_Jv_Method实例,这个对应类的构造函数"init"
   185 .long _Utf5
   186 .value 16384
   187 .value -1
   188 .long .L_ZN5harryC1Ev1
   189 .long 0
   190 .globl vtable for harry
   191 .align 32
   192 .type vtable for harry, @object
   193 .size vtable for harry, 44
   194 vtable for harry:
   195 .long 0
   196 .long 0
   197 .long harry::class$
   198 .long 4
   199 .long java::lang::Object::finalize()
   200 .long java::lang::Object::hashCode()
   201 .long java::lang::Object::equals(java::lang::Object*)
   202 .long java::lang::Object::toString()
   203 .long java::lang::Object::clone()
   204 .long java::lang::Object::throwNoSuchMethodError()
   205 .long harry::yoyo()
   206 .align 4
   207 .type _catch_classes_harry, @object
   208 .size _catch_classes_harry, 24
   209 _catch_classes_harry:
   210 .zero 24
   211 .section .rodata
   212 .align 2
   213 .type _Utf6, @object
   214 .size _Utf6, 4
   215 _Utf6:
   216 .value 24224
   217 .value 5
   218 .ascii "harry"
   219 .zero 1
   220 .globl harry::class$
   221 .data
   222 .align 32
   223 .type harry::class$, @object
   224 .size harry::class$, 140
   225 harry::class$:
   226 .long vtable for java::lang::Class+8
   227 .long 401000
   228 .long _Utf6
   229 .value 0
   230 .zero 2
   231 .long java::lang::Object::class$ //父类java::lang::Object的元数据,对应jclass superclass;字段
   232 .long 0
   233 .long 0
   234 .long 0
   235 .long _MT_harry //该类的方法列表,对应 _Jv_Method *methods;MT应该是Method的意思
   236 .value 2 //jshort method_count;
   237 .value 7 //jshort vtable_method_count;
   238 .long _FL_harry //类各个成员列表,对应_Jv_Field *fields;,FL应该是Field的意思
   239 .long 8
   240 .value 1
   241 .value 0
   242 .long vtable for harry+8
   243 .long 0
   244 .long 0
   245 .long 0
   246 .long 0
   247 .long 0
   248 .long 0
   249 .long _catch_classes_harry
   250 .long 0
   251 .long 0
   252 .value 0
   253 .byte 1
   254 .zero 1
   255 .long 0
   256 .value 0
   257 .zero 2
   258 .long 0
   259 .long 0
   260 .long 0
   261 .long 0
   262 .long 0
   263 .long 0
   264 .long 0
   265 .long 0
   266 .long 0

    这里我们关心内容的起点是harry::class$这个内容,它就是java::lang::Class类的一个实例,或者说是initializer,所以这里的字段和java::lang::Class类的字段逐个对应。这些内容的细节可以不用追究,只要知道编译器为每个类在编译的过程中会生成一个订制的java::lang::Class实例即可。作为备注,这个实例生成的第一现场位于gcc-4.1.0\gcc\java\class.c:
void
make_class_data (tree type)
{
……
  PUSH_FIELD_VALUE (cons, "methods",
   build1 (ADDR_EXPR, method_ptr_type_node, methods_decl));
  PUSH_FIELD_VALUE (cons, "method_count",
   build_int_cst (NULL_TREE, method_count));

  if (flag_indirect_dispatch)
    PUSH_FIELD_VALUE (cons, "vtable_method_count", integer_minus_one_node);
  else
    PUSH_FIELD_VALUE (cons, "vtable_method_count", TYPE_NVIRTUALS (type));
    
  PUSH_FIELD_VALUE (cons, "fields",
   fields_decl == NULL_TREE ? null_pointer_node
   : build1 (ADDR_EXPR, field_ptr_type_node, fields_decl));
……
}

四、类实例的注册
在生成的汇编文件中,还有一段代码
   379 .section .jcr,"aw",@progbits
   380 .align 4
   381 .long harry::class$
   382 .long tsecer::class$
   383 .section .debug_frame,"",@progbits
    注意,该文件中使用的harry::class$和tsecer::class$,两个类的元数据信息的放置位置,也就是在".jcr"节中保存了该文件中定义的所有类(这里是两个)的元数据信息。而在程序启动的时候,在文件gcc-4.1.0\gcc\crtstuff.c中定义了对于这个节(.jcr)中元数据列表的注册,这个注册的实现原理和全局静态变量的初始化调用原理相同。可以看到,gcc的运行时会在进程启动的时候遍历“.jcr”节中所有指针列表,然后逐个调用_Jv_RegisterClasses接口注册,这个注册到什么地方其实可以暂时不用关心,但是不难想象,注册了之后就可以建立一个全局的类元数据的字典,进而通过类名找到这个类的元数据信息,这也就是Class.forName接口的实现基础。
#define JCR_SECTION_NAME ".jcr"

#ifdef JCR_SECTION_NAME
/* Stick a label at the beginning of the java class registration info
   so we can register them properly.  */
STATIC void *__JCR_LIST__[]
  __attribute__ ((unused, section(JCR_SECTION_NAME), aligned(sizeof(void*))))
  = { };
#endif /* JCR_SECTION_NAME */
……
void
__do_global_ctors_1(void)
{
#ifdef USE_EH_FRAME_REGISTRY
  static struct object object;
  if (__register_frame_info)
    __register_frame_info (__EH_FRAME_BEGIN__, &object);
#endif
#ifdef JCR_SECTION_NAME
  if (__JCR_LIST__[0])
    {
      void (*register_classes) (void *) = _Jv_RegisterClasses;
      __asm ("" : "+r" (register_classes));
      if (register_classes)
register_classes (__JCR_LIST__);
    }
#endif
}
每个文件的.jcr节内容的生成通过gcc-4.1.0\gcc\java\class.c完成
void
emit_register_classes (tree *list_p)
{
  if (registered_class == NULL)
    return;

  /* TARGET_USE_JCR_SECTION defaults to 1 if SUPPORTS_WEAK and
     TARGET_ASM_NAMED_SECTION, else 0.  Some targets meet those conditions
     but lack suitable crtbegin/end objects or linker support.  These
     targets can overide the default in tm.h to use the fallback mechanism.  */
  if (TARGET_USE_JCR_SECTION)
    {
      tree klass, t;
      int i;

#ifdef JCR_SECTION_NAME
      named_section_flags (JCR_SECTION_NAME, SECTION_WRITE);
#else
      /* A target has defined TARGET_USE_JCR_SECTION,
but doesn't have a JCR_SECTION_NAME.  */
      gcc_unreachable ();
#endif
      assemble_align (POINTER_SIZE);

      for (i = 0; VEC_iterate (tree, registered_class, i, klass); ++i)
{
 t = build_fold_addr_expr (klass);
 output_constant (t, POINTER_SIZE / BITS_PER_UNIT, POINTER_SIZE);
}
    }
……
}
五、对象的创建
    应用中每个对象创建时,也就是new的时候会执行下面的调用链,可以看到,在每个创建对象内存开始会保存一份指向自己元数据的指针,而_Jv_AllocObject参数中的jclass信息可以在编译时获得。
_Jv_AllocObject (jclass klass)===>>>_Jv_AllocObjectNoFinalizer===>>>_Jv_AllocObj===>>>GC_local_gcj_malloc===>>>GC_gcj_malloc
void * GC_gcj_malloc(size_t lb, void * ptr_to_struct_containing_descr)
{
……
*(void **)op = ptr_to_struct_containing_descr;
UNLOCK();
    }
    return((GC_PTR) op);
}
六、字符串到内存地址的运行时转换
gcc-4.1.0\libjava\java\lang\natClass.cc
java::lang::reflect::Method *
java::lang::Class::_getMethod (jstring name, JArray<jclass> *param_types)
{
  jstring partial_sig = getSignature (param_types, false);
  jint p_len = partial_sig->length();
  _Jv_Utf8Const *utf_name = _Jv_makeUtf8Const (name);

   for (Class *klass = this; klass; klass = klass->getSuperclass())
    {
      int i = klass->isPrimitive () ? 0 : klass->method_count;
      while (--i >= 0)
{
 if (_Jv_equalUtf8Consts (klass->methods[i].name, utf_name)
     && _Jv_equaln (klass->methods[i].signature, partial_sig, p_len)
     && (klass->methods[i].accflags
 & java::lang::reflect::Modifier::INVISIBLE) == 0)
   {
……
}
有了类的元数据信息,问题就已经比较简单了,就是遍历一个类元数据中所有method列表,通过比较其中保存的字符串名称和签名(或者还有是否是public属性等),找到对应的位置即可。
七、方法调用(Method invoke)的实现
gcc-4.1.0\libjava\java\lang\reflect\natMethod.cc
void
_Jv_CallAnyMethodA (jobject obj,
   jclass return_type,
   jmethodID meth,
   jboolean is_constructor,
   jboolean is_virtual_call,
   JArray<jclass> *parameter_types,
   jvalue *args,
   jvalue *result,
   jboolean is_jni_call,
   jclass iface)
{
……
  // FIXME: If a vtable index is -1 at this point it is invalid, so we
  // have to use the ncode.  
  //
  // This can happen because methods in final classes don't have
  // vtable entries, but _Jv_isVirtualMethod() doesn't know that.  We
  // could solve this problem by allocating a vtable index for methods
  // in final classes.
  if (is_virtual_call 
      && ! Modifier::isFinal (meth->accflags)
      && (_Jv_ushort)-1 != meth->index)
    {
……
      ffi_call (&cif, (void (*)()) ncode, &ffi_result, values);
……
}
八、实现总结
其中关键的内容包括一下部分
1、gcc会在编译的时候为每个类生成一份定制的、类型为java::lang::Class的对象实例,例如我们例子中以'$'结束的变量。
2、链接时链接器解析、重定位(relocation)元数据中的引用信息,这样链接之后的文件中方法的位置已经确定。
3、把文件中所有类的元数据地址放在约定好的".jcr"节中,crtstuff在启动的时候遍历该section中所有指针,将所有的元数据注册给java运行时。
4、Class.forName遍历所有注册的类信息,找到该类的元数据结构。
5、Class.getField和Class._getMethod分别遍历自己实例的_Jv_Field *fields;和 _Jv_Method *methods;链表,通过字符串匹配来获得它们的运行时位置。
九、一个周边问题:如何让所有类都派生自java.lang.Object?
    这其实是一个和这里讨论问题不想关的问题,虽然实现并不复杂,但是也是java的一个基本问题,单独列出来又不太合适,就在这里做个备忘,代码比较简单,只看注释即可,代码位于gcc-4.1.0\gcc\java\parse.y:
static tree
create_class (int flags, tree id, tree super, tree interfaces)
{
……
  /* If SUPER exists, use it, otherwise use Object */
  if (super)
    {
      /* java.lang.Object can't extend anything.  */
      if (TREE_TYPE (IDENTIFIER_CLASS_VALUE (class_id)) == object_type_node)
{
 parse_error_context (id, "%<java.lang.Object%> can't extend anything");
 return NULL_TREE;
}

      super_decl_type =
register_incomplete_type (JDEP_SUPER, super, decl, NULL_TREE);
    }
  else if (TREE_TYPE (decl) != object_type_node)
    super_decl_type = object_type_node;
  /* We're defining java.lang.Object */
  else
    super_decl_type = NULL_TREE;
……
}
附录:测试代码中类内容备忘:
tsecer@harry: /home/tsecer/Download/gccobj/gcc/jcf-dump -c tsecer.class 
Reading .class from tsecer.class.
Magic number: 0xcafebabe, minor_version: 3, major_version: 45.

Access flags: 0x20 super
This class: tsecer, super: java.lang.Object
Interfaces (count: 0):

Fields (count: 0):

Methods (count: 2):

Method name:"main" public static Signature: (java.lang.String[])void
Attribute "Code", length:52, max_stack:2, max_locals:4, code_length:20
  0: new <Class harry>
  3: dup
  4: invokespecial <Method harry.<init> ()void>
  7: astore_1
  8: aload_1
  9: invokevirtual <Method java.lang.Object.getClass ()java.lang.Class>
 12: astore_2
 13: aload_0
 14: arraylength
 15: bipush 10
 17: imul
 18: istore_3
 19: return
Attribute "LineNumberTable", length:14, count: 3
  line: 14 at pc: 0
  line: 15 at pc: 8
  line: 16 at pc: 13

Method name:"<init>" Signature: ()void
Attribute "Code", length:17, max_stack:1, max_locals:1, code_length:5
  0: aload_0
  1: invokespecial <Method java.lang.Object.<init> ()void>
  4: return

Attributes (count: 1):
Attribute "SourceFile", length:2, #21="epic.java"
tsecer@harry: /home/tsecer/Download/gccobj/gcc/jcf-dump -c harry.class 
Reading .class from harry.class.
Magic number: 0xcafebabe, minor_version: 3, major_version: 45.

Access flags: 0x20 super
This class: harry, super: java.lang.Object
Interfaces (count: 0):

Fields (count: 1):
Field name:"x" Signature: int

Methods (count: 2):

Method name:"yoyo" Signature: ()int
Attribute "Code", length:27, max_stack:1, max_locals:1, code_length:3
  0: ldc <Integer 286331153>
  2: ireturn
Attribute "LineNumberTable", length:6, count: 1
  line: 5 at pc: 0

Method name:"<init>" Signature: ()void
Attribute "Code", length:17, max_stack:1, max_locals:1, code_length:5
  0: aload_0
  1: invokespecial <Method java.lang.Object.<init> ()void>
  4: return

Attributes (count: 1):
Attribute "SourceFile", length:2, #17="epic.java"
tsecer@harry: 

  评论这张
 
阅读(40)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017