/jmtrace

Primary LanguageJava

jmtrace

本项目实现了一个面向Java程序的工具,该工具为每一个共享内存访问输出一条日志信息。

编译、使用

本项目使用Maven构建。在根目录中执行

mvn package
chmod +x jmtrace

后即可使用。编译完成后code/target/jmtrace-1.0-SNAPSHOT-jar-with-dependencies.jar文件即为本工具的核心模块。

在根目录中执行

./jmtrace -jar YOUR_APP.jar

执行流程

  1. 使用java.lang.Instrumentpremain阶段添加一个用于插桩的transformer
  2. transformer中,首先排除由Boostrap Classloader加载的类、java包中的类和sun包中的类,然后运用适配器设计模式将传入ClassReader的对象修改为自定义类ClassAdapter的实例。
  3. ClassAdapter中覆盖visitMethod方法,运用适配器设计模式将返回值修改为自定义类MethodAdapter的实例。
  4. MethodAdapter是插桩的核心模块,它是MethodVisitor的子类,我们主要覆盖了visitFieldInsnvisitInsn两个方法,在处理到需要插桩的opcode时调用打印相关信息的方法。在实现该类的过程中,需要完成“判断目标类是否为库中的类”、“对栈中的元素进行交换”等操作,我们把这些操作抽象为通用的静态方法,放在JavaVirtualMachineUtil类中。
  5. 打印相关信息的方法位于Logger类中,包括accessStaticaccessFieldaccessArray三个方法。

挑战

  • 静态变量的对象标识

    • 对于成员变量、局部变量,我们可以在栈中获得对象实例,进而直接使用System.identityHashCode()生成对象标识符。但是在访问静态变量时,栈中不会提供对象实例,所以我们无法采用上述方法。考虑到同一个类的不同对象的同一静态域实际上是同一个实例,即“类+静态变量名”能够唯一地确定某一静态变量的实例,所以我们用以下内容标识静态变量:

      (((long) System.identityHashCode(Class.forName(owner.replace('/', '.')))) << 32) + name.hashCode()

      即:64位整数的前32位标识类,后32位标识静态变量名(实际上也标识了该实例)。

  • 手动调用visitMethodInsndescriptor参数的生成

    • MethodAdapter类中的两个方法中,我们需要手动调用mv.visitMethodInsn方法以在字节码层级插入“对Logger类中打印信息的方法的调用”。在方法需要我们传入形如

      "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"

      descriptor参数,用于描述被调用方法的参数类型和返回值类型。descriptor比较复杂,手动生成容易出错。因此,我们采用ASMifier,自动生成目标方法的descriptor

  • 多次重复不同类型的swap操作

    • MethodAdapter的实现中,我们多次需要完成栈顶端两个元素的交换操作。由于这些元素的大小不一(64位和64位交换、64位和32位交换、32位和64位交换),需要分别使用DUPPOP系列指令来完成交换过程。为了便于代码维护,我们把这些常用操作抽象为JavaVirtualMachineUtil中的静态方法。例如,依次调用DUP2_X1POP2指令来实现32位和64位的交换(即JavaVirtualMachineUtil.swap32And64方法)。
  • java.lang.Instrument和ASM基本流程的了解,参考了[1][2][3]

  • 对Java字节码和栈状态的了解,参考了[4][5]

  • 对Java数据类型的两种类别,参考了[6]

参考资料

  1. https://asm.ow2.io/faq.html
  2. https://cloud.tencent.com/developer/article/1609739
  3. http://www.dengshenyu.com/java-asm/
  4. https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html
  5. https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
  6. https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.1