请选择 进入手机版 | 继续访问电脑版

技术控

    今日:14| 主题:54680
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] 字节码操纵技术探秘

[复制链接]
橘虞 发表于 2016-12-2 06:19:37
303 6

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
大家可能已经非常熟悉下面的处理流程:将一个“.java”文件输入到Java编译器中(可能会使用javac,也可能像ANT、Maven或Gradle这样的构建工具),编译器对其进行分析,最终生成一个或多个“.class”文件。
   

字节码操纵技术探秘

字节码操纵技术探秘-1-技术控-example,machine,虚拟机,编译器,java

  图1:什么是Java字节码?
  如果从命令行中运行构建,并启用verbose的话,我们能够看到解析文件直到生成“.class”文件这一过程的输出。
  1. javac -verbose src/com/example/spring2gx/BankTransactions.java
复制代码
  

字节码操纵技术探秘

字节码操纵技术探秘-2-技术控-example,machine,虚拟机,编译器,java

  所生成的“.class”文件包含了字节码,本质上来讲它就是Java虚拟机(Java virtual machine,JVM)所使用的指令,当程序运行时,它会由Java运行时类加载器进行加载。
  在本文中,我们将会研究Java字节码以及如何对其进行操纵,并探讨人们为何想要这样做。
  字节码操纵框架

  最为流行的字节码操纵框架包括:
  
       
  • ASM   
  • AspectJ   
  • BCEL   
  • Byte Buddy   
  • CGLIB   
  • Cojen   
  • Javassist   
  • Serp  
  本文将会主要关注Javassist和ASM。
  我们为什么应该关注字节码操纵呢?

  很多常用的Java库,如Spring和Hibernate,以及大多数的JVM语言甚至我们的IDE,都用到了字节码操纵框架。另外,它也确实非常有趣,所以这是一项很有价值的技术,掌握它之后,我们就能完成一些靠其他技术很难实现或无法完成的任务。一旦学会之后,我们的发挥空间将是无限的!
  一个很重要的使用场景就是程序分析。例如,流行的bug定位工具FindBugs在底层就使用了ASM来分析字节码并定位bug。有一些软件商店会有一定的代码复杂性规则,比如方法中if/else语句的最大数量以及方法的最大长度。静态分析工具会分析我们的字节码来确定代码的复杂性。
  另外一个常见的使用场景就是类生成功能。例如,ORM框架一般都会基于我们的类定义使用代理的机制。或者,在考虑实现应用的安全性时,可能会提供一种语法来添加授权的注解。在这样的场景下,都能很好地运用字节码操纵技术。
  像Scala、Groovy和Grails这样的JVM语言都使用了字节码操纵框架。
  考虑这样一种场景,我们需要转换库中的类,这些类我们并没有源码,这样的任务通常会由Java profiler来执行。例如,在New Relic,采用了字节码instrumentation技术实现了对方法执行的计时。
  借助字节码操纵,我们可以优化或混淆代码,甚至可以引入一些功能,比如为应用添加重要的日志。本文将会关注一个日志样例,这个样例提供使用这些字节码操纵框架的基本工具。
  我们的样例

  Sue负责一家银行的ATM编程,她有了一项新的需求:针对一些指定的重要操作,在日志中添加关键的数据。
  如下是一个简化的银行交易类,它允许用户通过用户名和密码进行登录、进行一些处理、提取一些钱,然后打印“交易完成”。这里的重要操作就是登录和提款。
  1. public void login(String password, String accountId, String userName) {
  2. // 登录逻辑
  3. }
  4. public void withdraw(String accountId, Double moneyToRemove) {
  5. // 交易逻辑
  6. }
复制代码
为了简化编码,[email protected][email protected]参数索引。借助这一点,她就可以为login和withdraw方法添加注解了。
  1. /**
  2. * 方法注解,用于识别
  3. * 重要的方法,这些方法的调用需要进行日志记录。
  4. */
  5. public @interface ImportantLog {
  6. /**
  7. * 需要进行日志记录的方法参数索引。
  8. * 例如,如果有名为
  9. * hello(int paramA, int paramB, int paramC)的方法,我们
  10. * 希望以日志的形式记录paramA和paramC的值,那么fields
  11. * 应该是["0","2"]。如果我们只想记录
  12. * paramB的值,那么fields将会是["1"]。
  13. */
  14. String[] fields();
  15. }
复制代码
对于login方法,Sue希望记录账户Id和用户名,那么她的fields应该设置为“1”和“2”(她不希望将密码展现出来!)。对于withdraw方法,她的fields应该设置为“0”和“1”,因为她希望输出前两个域:账户ID以及要提取的金额,其审计日志理想情况下应该包含如下的内容:
   

字节码操纵技术探秘

字节码操纵技术探秘-3-技术控-example,machine,虚拟机,编译器,java

  要实现该功能,Sue将会使用Java agent技术。Java agent是在JDK 1.5中引入的,它允许我们在处于运行状态的JVM中,修改组成类的字节,在这个过程中,并不需要这些类的源码。
  在没有agent的时候,Sue的程序的正常执行流程是这样的:
  
       
  • 在某个主类上运行Java,这个类会由一个类加载器进行加载;   
  • 调用该类的main方法,它会调用预先定义好的处理过程;   
  • 打印“交易完成”。  
  在引入Java agent之后,会发生几件额外的事情——但是,在此之前,我们先看一下创建agent都需要些什么。agent必须要包含一个类,这个类要具有一个名为premain的方法。这个类必须要打包为JAR文件,这个包中还需要包含一个正确的manifest文件,在manifest文件中要有一个名为Premain-Class的条目。在启动的时候,必须要设置一个启动项,指向该JAR文件的路径,这样的话,JVM才能知道这个agent:
  1. java -javaagent:/to/agent.jar com/example/spring2gx/BankTransactions
复制代码
在premain方法中,我们可以注册一个Transformer,它会在每个类加载的时候,捕获它的字节,进行所需的修改,然后返回修改后的字节。在Sue的样例中,Transformer会捕获BankTransaction,在这里她会作出修改并返回修改后的字节,这也就是类加载器所加载的字节,main方法将会执行原有的功能,除此之外还会增加Sue所需的日志增强。
  当agent类加载后,它的premain方法会在应用程序的main方法之前被调用。
   

字节码操纵技术探秘

字节码操纵技术探秘-4-技术控-example,machine,虚拟机,编译器,java

  图2:使用Java agent的过程。
  我们最好来看一个样例。
  Agent类不需要实现任何接口,但是它必须要包含一个premain方法,如下所示:
   

字节码操纵技术探秘

字节码操纵技术探秘-5-技术控-example,machine,虚拟机,编译器,java

  Transformer类包含了一个transform方法,它的签名会接受ClassLoader、类名、要重定义的类所对应的Class对象、定义权限的ProtectionDomain以及这个类的原始字节。如果从transform方法中返回null的话,将会告诉运行时环境我们并没有对这个类进行变更。
   

字节码操纵技术探秘

字节码操纵技术探秘-6-技术控-example,machine,虚拟机,编译器,java

  如果要修改类的字节的话,我们需要在transform中提供字节码操纵的逻辑并返回修改后的字节。
   

字节码操纵技术探秘

字节码操纵技术探秘-7-技术控-example,machine,虚拟机,编译器,java

  Javassist

   Javassist(“Java Programming Assistant”的缩写形式)是JBoss的子项目,包含了高层级的基于对象的API,同时也包含了低层级更接近字节码的API。基于对象的API社区更为活跃,这也是本文所关注的焦点。读者可以参考 Javassist站点 以获取完整的使用指南。
  在Javassist中,进行类表述的基本单元是CtClass(即“编译时的类”,compile time class)。组成程序的这些类会存储在一个ClassPool中,它本质上就是CtClass实例的一个容器。
  ClassPool的实现使用了一个HashMap,其中key是类的名称,而value是对应的CtClass对象。
  正常的Java类都会包含域、构造器以及方法。在CtClass中,分别与之对应的是CtField、CtConstructor和CtMethod。要定位某个CtClass,我们可以根据名称从ClassPool中获取,然后通过CtClass得到任意的方法,并做出我们的修改。
   

字节码操纵技术探秘

字节码操纵技术探秘-8-技术控-example,machine,虚拟机,编译器,java

  图3
  CtMethod中包含了相关方法的代码行。我们可以借助insertBefore命令在方法开始的地方插入代码。Javassist非常棒的一点在于我们所编写的是纯Java,只不过需要提醒一下:Java代码必须要以引用字符串的形式来实现。但是,大多数的人都会同意这种方式要比处理字节码好得多!(尽管如此,如果你碰巧喜欢直接处理字节码的话,那么可以关注本文ASM相关的内容。)JVM包含了一个字节码验证器(verifier),以防止出现不合法的字节码。如果在你的Javassist代码中,所使用的Java是非法的,那么在运行时字节码验证器会拒绝它。
  与insertBefore类似,还有一个名为insertAfter的方法,借助它我们可以在相关方法的结尾处插入代码。我们还可以使用insertAt方法,从而在相关方法的中间插入代码,或者使用addCatch添加catch语句。
   

字节码操纵技术探秘

字节码操纵技术探秘-9-技术控-example,machine,虚拟机,编译器,java

  现在,让我们打开IDE,然后编码实现这个日志特性,首先从Agent(包含premain)和ClassTransformer开始。
  1. package com.example.spring2gx.agent;
  2. public class Agent {
  3. public static void premain(String args, Instrumentation inst) {
  4.    System.out.println("Starting the agent");
  5.    inst.addTransformer(new ImportantLogClassTransformer());
  6. }
  7. }

  8. package com.example.spring2gx.agent;
  9. import java.lang.instrument.ClassFileTransformer;
  10. import java.lang.instrument.IllegalClassFormatException;
  11. import java.security.ProtectionDomain;

  12. public class ImportantLogClassTransformer
  13.    implements ClassFileTransformer {

  14. public byte[] transform(ClassLoader loader, String className,
  15.                 Class classBeingRedefined, ProtectionDomain protectionDomain,
  16.                 byte[] classfileBuffer) throws IllegalClassFormatException {
  17. // 在这里操纵字节
  18.      return modified_bytes;
  19. }
复制代码
为了添加审计日志,首先要实现transform,将类的字节转换为CtClass对象。然后,我们可以迭代它的方法,[email protected],获取要进行日志记录的输入参数索引,并将相关的代码插入到方法开头的位置上。
  1. public byte[] transform(ClassLoader loader, String className,
  2.                         Class classBeingRedefined,
  3.                         ProtectionDomain protectionDomain,
  4.                         byte[] cfbuffer) throws IllegalClassFormatException {
  5. // 将字节数组转换为CtClass对象
  6.     pool.insertClassPath(new ByteArrayClassPath(className,classfileBuffer));
  7. // 将路径斜线转换为点号
  8.     CtClass cclass = pool.get(className.replaceAll("/", "."));
  9.     if (!cclass.isFrozen()) {
  10. // [email protected]
  11.       for (CtMethod currentMethod : cclass.getDeclaredMethods()) {
  12. // [email protected]
  13.         Annotation annotation = getAnnotation(currentMethod);
  14.         if (annotation != null) {
  15. // [email protected],那么
  16. // 获得重要方法的参数索引
  17.           List parameterIndexes = getParamIndexes(annotation);
  18. // 在方法的开头位置添加日志语句
  19.           currentMethod.insertBefore(
  20.                  createJavaString(currentMethod, className, parameterIndexes));
  21.         }
  22.       }
  23.       return cclass.toBytecode();
  24.     }
  25.     return null;
  26.   }
复制代码
Javassist注解可以声明为“不可见的(invisible)”和“可见的(visible)”。不可见的注解只会在类加载和编译期可见,它们在声明时需要将RententionPolicy.CLASS参数传递到注解中。可见注解(RententionPolicy.RUNTIME)在运行期会加载,并且是可见的。对于本例来说,我们只在编译期需要这些属性,因此将其设为不可见的。
  [email protected],如果找不到注解的话,将会返回null。
  1. private Annotation getAnnotation(CtMethod method) {
  2.     MethodInfo methodInfo = method.getMethodInfo();
  3.     AnnotationsAttribute attInfo = (AnnotationsAttribute) methodInfo
  4.       .getAttribute(AnnotationsAttribute.invisibleTag);
  5.     if (attInfo != null) {
  6.       return attInfo.getAnnotation("com.example.spring.mains.ImportantLog");
  7.     }
  8.     return null;
  9.   }
复制代码
在得到注解之后,我们就可以检索参数索引了。通过使用Javassist的ArrayMemberValue,会以字符串数组的形式返回成员field的值,然后我们就可以对其进行遍历,从而获取在注解中所嵌入的field索引。
  1. private List getParamIndexes(Annotation annotation) {
  2.     ArrayMemberValue fields =
  3.                     (ArrayMemberValue) annotation.getMemberValue(“fields”);
  4.     if (fields != null) {
  5.       MemberValue[] values = (MemberValue[]) fields.getValue();
  6.       List parameterIndexes = new ArrayList();
  7.       for (MemberValue val : values) {
  8.         parameterIndexes.add(((StringMemberValue) val).getValue());
  9.       }
  10.       return parameterIndexes;
  11.     }
  12.     return Collections.emptyList();
  13.   }
复制代码
最后,我们可以借助createJavaString在某个位置插入日志语句。
  1. 1     private String createJavaString(CtMethod currentMethod,
  2. 2                  String className, List indexParameters) {
  3. 3       StringBuilder sb = new StringBuilder();
  4. 4       sb.append("{StringBuilder sb = new StringBuilder");
  5. 5       sb.append("("A call was made to method '");");
  6. 6       sb.append("sb.append("");
  7. 7       sb.append(currentMethod.getName());
  8. 8       sb.append("");sb.append("' on class '");");
  9. 9       sb.append("sb.append("");
  10. 10      sb.append(className);
  11. 11      sb.append("");sb.append("'.");");
  12. 12      sb.append("sb.append("\\n Important params:");");
  13. 13   
  14. 14      for (String index : indexParameters) {
  15. 15        try {
  16. 16          int localVar = Integer.parseInt(index) + 1;
  17. 17          sb.append("sb.append("\\n Index: ");");
  18. 18          sb.append("sb.append("");
  19. 19          sb.append(index);
  20. 20          sb.append("");sb.append(" value: ");");
  21. 21          sb.append("sb.append($" + localVar + ");");
  22. 22        } catch (NumberFormatException e) {
  23. 23          e.printStackTrace();
  24. 24        }
  25. 25      }
  26. 26      sb.append("System.out.println(sb.toString());}");
  27. 27      return sb.toString();
  28. 28    }
  29. 29  }
复制代码
在我们的实现中,创建了一个StringBuilder,它首先拼接了一些报头信息,紧接着是方法名和类名。需要注意的一件事情是,如果我们插入多行Java语句的话,需要将其用大括号括起来(参见第4行和第26行)。
  (如果只有一条语句的话,那没有必要使用括号。)
  前文基本上已经全部涵盖了使用Javassist添加审计日志的代码。回顾一下,它的优势在于:
  
       
  • 因为它使用我们所熟悉的Java语法,所以不需要学习字节码;   
  • 没有太多的编码工作要做;   
  • Javassist已经有了很棒的文档。  
  它的不足之处在于:
  
       
  • 不使用字节码会限制它的功能;   
  • Javassist会比其他的字节码操纵框架更慢一些。  
  ASM

   ASM最初是一个博士研究项目,在2002年开源。它的更新非常活跃,从5.x版本开始支持Java 8。ASM包含了一个基于事件的库和一个基于对象的库,分别类似于SAX和DOM XML解析器。本文将会关注事件驱动的库,我们可以在 这里 找到完整的文档。
  一个Java类是由很多组件组成的,包括超类、接口、属性、域和方法。在使用ASM时,我们可以将其均视为事件。我们会提供一个ClassVisitor实现,通过它来解析类,当解析器遇到每个组件时,ClassVisitor上对应的“visitor”事件处理器方法会被调用(始终按照上述的顺序)。
  1. package com.sun.xml.internal.ws.org.objectweb.asm;
  2.    public interface ClassVisitor {
  3.        void visit(int version, int access, String name, String signature,
  4.                                   String superName, String[] interfaces);
  5.        void visitSource(String source, String debug);
  6.        void visitOuterClass(String owner, String name, String desc);
  7.        AnnotationVisitor visitAnnotation(String desc, boolean visible);
  8.        void visitAttribute(Attribute attr);
  9.        void visitInnerClass(String name, String outerName,
  10.                             String innerName, int access);
  11.        FieldVisitor visitField(int access, String name, String desc,
  12.                                String signature, Object value);
  13.        MethodVisitor visitMethod(int access, String name, String desc,
  14.                                  String signature, String[] exceptions);
  15.        void visitEnd();
  16.    }
复制代码
接下来,我们将Sue的BankTransaction(在本文的开头中进行了定义)传递到一个ClassReader中进行解析,以便对这种处理方式有个直观的感觉。
  同样,我们需要从Agent premain开始:
  1. public void login(String password, String accountId, String userName) {
  2. // 登录逻辑
  3. }
  4. public void withdraw(String accountId, Double moneyToRemove) {
  5. // 交易逻辑
  6. }0
复制代码
然后,将输出的字节传递给不进行任何操作(no-op)的ClassWriter,将解析得到的字节全部写回到字节数组中,得到一个重新生成的BankTransaction,它的行为与原始类的行为是完全一致的。
   

字节码操纵技术探秘

字节码操纵技术探秘-10-技术控-example,machine,虚拟机,编译器,java

  图4
  1. public void login(String password, String accountId, String userName) {
  2. // 登录逻辑
  3. }
  4. public void withdraw(String accountId, Double moneyToRemove) {
  5. // 交易逻辑
  6. }1
复制代码
现在,我们修改一下ClassWriter,让它做一些更有用的事情,这需要添加一个ClassVisitor(名为LogMethodClassVisitor)来调用我们的事件处理方法,如visitField或visitMethod,在解析时遇到相关组件时就会调用这些方法。
   

字节码操纵技术探秘

字节码操纵技术探秘-11-技术控-example,machine,虚拟机,编译器,java

  图5
  1. public void login(String password, String accountId, String userName) {
  2. // 登录逻辑
  3. }
  4. public void withdraw(String accountId, Double moneyToRemove) {
  5. // 交易逻辑
  6. }2
复制代码
对于日志记录的需求,我们想要检查每个方法是否具有标示注解并添加特定的日志。我们只需要重写ClassVisitor的visitMethod方法,让它返回一个MethodVisitor,从而提供我们自己的实现。就像类是由多个组件组成的一样,方法也是由多个组件组成的,对应着方法属性、注解以及编译后的代码。ASM的MethodVisitor提供了一种钩子(hook)机制,以便访问方法中的每个操作码(opcode),这样我们就能以很细的粒度来进行修改。
  1. public void login(String password, String accountId, String userName) {
  2. // 登录逻辑
  3. }
  4. public void withdraw(String accountId, Double moneyToRemove) {
  5. // 交易逻辑
  6. }3
复制代码
  同样的,事件处理器会始终按照预先定义的相同顺序来调用,所以在实际 访问(visit) 代码的时候,我们就已经得知了方法的所有属性和注解。(顺便说一下,我们还可以将多个MethodVisitor链接在一起,就像我们可以链接多个ClassVisitor实例一样。)所以,在visitMethod方法中,我们将会通过PrintMessageMethodVisitor添加钩子,重载visitAnnotation方法来获取注解并插入任意所需的日志代码。
   

字节码操纵技术探秘

字节码操纵技术探秘-12-技术控-example,machine,虚拟机,编译器,java

  我们的PrintMessageMethodVisitor重载了两个方法。首先是visitAnnotation,[email protected]注解的话,我们需要提取field属性上的索引。当visitCode执行的时候,是否存在注解已经确定了,所以我们就可以添加特定的日志了。visitAnnotation的代码以钩子的形式添加到了AnnotationVisitor中,[email protected] 注解上的field参数。
  1. public void login(String password, String accountId, String userName) {
  2. // 登录逻辑
  3. }
  4. public void withdraw(String accountId, Double moneyToRemove) {
  5. // 交易逻辑
  6. }4
复制代码
现在,我们来看一下visitCode方法。首先,它必须要检查AnnotationVisitor是否有注解存在的标记。如果有的话,那么添加我们自己的字节码。
  1. public void login(String password, String accountId, String userName) {
  2. // 登录逻辑
  3. }
  4. public void withdraw(String accountId, Double moneyToRemove) {
  5. // 交易逻辑
  6. }5
复制代码
这是ASM非常恐怖的一点——我们必须要编写字节码,所以要学习新的东西。这是一门相当简单的语言,但是如果你只是想随便了解一下的话,那么可以非常容易地借助javap得到现有的字节码:
  1. public void login(String password, String accountId, String userName) {
  2. // 登录逻辑
  3. }
  4. public void withdraw(String accountId, Double moneyToRemove) {
  5. // 交易逻辑
  6. }6
复制代码
  

字节码操纵技术探秘

字节码操纵技术探秘-13-技术控-example,machine,虚拟机,编译器,java

  我推荐你在一个Java测试类中编写你所需的代码、对其进行编译,然后通过javap -c来运行它,以便查看精确的字节码。在上面的样例代码中,以蓝字显示的所有内容都是字节码。其中的每一行都是一个单字节的操作码,后面跟着零个或更多的参数。我们需要为目标代码确定这些参数,它们通常可以通过对原始类执行javap-c -v进行抽取(这里-v代表的意思是verbose,将会展示常量池)。
   我鼓励读者查阅一下 JVM规范 ,该规范定义了所有的字节码。有一些操作,比如load和store(会将数据在操作栈和本地变量之间进行转移),针对每种参数形式都进行了重载。例如,ILOAD会将一个整型值的本地变量推送到栈中,而LLOAD会对长整型执行相同的操作。
  除此之外,还有像invokeVirtual、invokeSpecial、invokeStatic这样的操作,以及最近新添加的invokeDynamic,它们分别用来调用标准的实例方法、构造器、静态方法以及动态类型的JVM语言中的动态方法。另外,还有创建新对象的new操作符,以及复制栈顶操作数的指令。
  总结起来,ASM的优势在于:
  
       
  • 它的内存占用很小;   
  • 它的运行通常会非常快;   
  • 在网上,它的文档很丰富;   
  • 所有的操作码都是可用的,所以可以通过它做很多的事情;   
  • 有很多的社区支持。  
  它只有一个不足之处,但这是很大的不足:我们编写的是字节码,所以需要理解在幕后是如何运行的,这样的话,开发人员所需要的时间就会增加。
  我们学到了什么

  
       
  • 当我们处理字节码操纵时,很重要的一点就是步子不要太大。不要编写大量的字节码并希望它们能够立即通过验证并且可以执行。每次只编写一行,考虑一下在你的栈中会有什么,考虑一下局部变量,然后再写下一行。如果它不能通过验证器的校验,那么每次只对一个地方进行修改,否则的话,你永远无法让其正常运行起来。另外,还需要记住,除了JVM的验证器以外,ASM还维护了一个单独的字节码验证器,所以我们最好运行这两个验证器,检查你的字节码是否能够通过它们两者的校验。   
  • 在修改类的时候,很重要的一点就是要考虑类加载机制。当我们使用Java agent的时候,它的transformer会在类加载进JVM时接触到每个类,并不关心是哪个类加载器加载的它。所以,我们需要确保类加载器也能看到对应的对象,否则的话,就会遇到麻烦。   
  • 如果你将Javassist与应用服务器组合使用时,应用服务器会有多个类加载器,注意类池(class pool)需要能够访问到你的类对象。我们可能需要为类池注册一个新的类路径,以确保它能访问类对象。你可以将类池链接起来,就像Java将类加载器链接起来一样,这样,如果在类池中无法找到CTClass对象的话,它能够访问它的父类池进行查找。   
  • 最后,还有很重要的一点需要注意,那就是JDK本身也有转换类的功能,JDK已经转换过的类会有一些特定的限制。我们可以修改方法的实现,但是与原始的转换不同,此时,重新转换就不允许改变类的结构了,例如添加新的域或方法,或者修改签名。  
  字节码操纵能够让我们的生活更加轻松。我们可以查找缺陷、添加日志(就像之前所讨论的)、混淆源码、执行像Spring或Hibernate这样的预处理,甚至编写自己的语言编译器。我们还可以限制API调用、通过分析代码来查看是否有多个线程访问同一个集合、从数据库中懒加载数据,并且还可以通过探测JAR包,寻找这些包的差异。
  所以,我鼓励你选择某个字节码操纵框架为友,也许有一天,它就能拯救你的工作。
   本文根据来自New Relic的Ashley Puls在Spring One上的演讲整理而来,完整的演讲可以在该地址观看和下载。
  关于作者

   

字节码操纵技术探秘

字节码操纵技术探秘-14-技术控-example,machine,虚拟机,编译器,java
Victor Grazi 是InfoQ的Java板块主编。在2012年,他曾成为Oracle Java Champion,Victor就职于Nomura Securities,从事核心平台工具相关的工作,同时还担任技术咨询和Java布道师。在技术会议上,他经常做一些演讲。Victor还负责着名为“Java Concurrent Animated”和“Bytecode Explorer”开源项目。
   查看英文原文: Living in the Matrix with Bytecode Manipulation



上一篇:BitUnmap: Attacking Android Ashmem
下一篇:10 key security terms devops ninjas need to know
柔安 发表于 2016-12-2 12:17:49
加关注!
回复 支持 反对

使用道具 举报

1551 发表于 2016-12-2 14:30:13
说不通啊,楼下的你说咋办?
回复 支持 反对

使用道具 举报

gk7896330 发表于 2016-12-3 03:00:16
泥泞路上的奔驰,永远跑不过高速路上的夏利。说明:”平台很重要!”
回复 支持 反对

使用道具 举报

chaijunbo 发表于 2016-12-4 03:39:53
钓鱼岛是中国的,沙发是我的!
回复 支持 反对

使用道具 举报

BG8EHZ 发表于 2016-12-9 03:05:53
佩服佩服!
回复 支持 反对

使用道具 举报

曼文 发表于 2016-12-16 20:41:25
不想当厨子的裁缝,不是好司机.
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读


回页顶回复上一篇下一篇回列表
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )

© 2001-2017 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表