草庐IT

java - ASM 内联字节码方法期间重新映射变量

coder 2024-03-14 原文

我正在使用 ASM 进行在线字节码方法内联优化。我的更改基于示例 3.2.6 Inline Method ( http://asm.ow2.org/current/asm-transformations.pdf )。测试示例(在 Caller::test 处内联被调用者的 calculate(int,int))是:

public class Caller {
    final Callee _callee;

    public Caller(Callee callee){
        _callee = callee;
    }
    public static void main(String[] args) {
        new Caller(new Callee("xu", "shijie")).test(5, 100);
    }

    public void test(int a, int b){
        int t = a;
        int p = b;
        int r = t+p-_callee.calculate(a, b);
        int m = t-p;
        System.out.println(t);
    }
}
public class Callee {

    final String _a;
    final String _b;
    public Callee(String a, String b){
        _a = a;
        _b = b;
    }

    public int calculate(int t, int p){
        int tmp = _a.length()+_b.length();
        tmp+=t+p;
        return tmp;
    }
}

基于 ASM 5.0 版本,我的代码是:
//MainInliner.java
public class MainInliner extends ClassLoader{

    public byte[] generator(String caller, String callee) throws ClassNotFoundException{
        String resource = callee.replace('.', '/') + ".class";
        InputStream is =  getResourceAsStream(resource);
        byte[] buffer;
        // adapts the class on the fly
        try {
            resource = caller.replace('.', '/')+".class";
            is = getResourceAsStream(resource);
            ClassReader cr = new ClassReader(is);
            ClassWriter cw = new ClassWriter(0);

            ClassVisitor visitor = new BCMerge(Opcodes.ASM5, cw, callee);
            cr.accept(visitor, 0);

            buffer= cw.toByteArray();

        } catch (Exception e) {
            throw new ClassNotFoundException(caller, e);
        }

        // optional: stores the adapted class on disk
        try {
            FileOutputStream fos = new FileOutputStream("/tmp/data.class");
            fos.write(buffer);
            fos.close();
        } catch (IOException e) {}
        return buffer;
     }


      @Override
      protected synchronized Class<?> loadClass(final String name,
                final boolean resolve) throws ClassNotFoundException {
            if (name.startsWith("java.")) {
                System.err.println("Adapt: loading class '" + name
                        + "' without on the fly adaptation");
                return super.loadClass(name, resolve);
            } else {
                System.err.println("Adapt: loading class '" + name
                        + "' with on the fly adaptation");
            }
            String caller = "code.sxu.asm.example.Caller";
            String callee = "code.sxu.asm.example.Callee";
            byte[] b = generator(caller, callee);
            // returns the adapted class
            return defineClass(caller, b, 0, b.length);
        }

        public static void main(final String args[]) throws Exception {
            // loads the application class (in args[0]) with an Adapt class loader
            ClassLoader loader = new MainInliner();
            Class<?> c = loader.loadClass(args[0]);
            Method m = c.getMethod("main", new Class<?>[] { String[].class });

        }
}


class BCMerge extends ClassVisitor{

    String _callee;
    String _caller;

    public BCMerge(int api, ClassVisitor cv, String callee) {
        super(api, cv);
        // TODO Auto-generated constructor stub
        _callee = callee.replace('.', '/');
    }

    public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        this._caller = name;
    } 

    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {

        if(!name.equals("test")){
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);

        ClassReader cr = null;
        try {
            cr = new ClassReader(this.getClass().getClassLoader().getResourceAsStream(_callee.replace('.', '/')+".class"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

         ClassNode classNode = new ClassNode();
         cr.accept(classNode, 0);

         MethodNode inlinedMethod = null;
         for(MethodNode node: classNode.methods){
            if(node.name.equals("calculate")){
                inlinedMethod = node;
                break;
            }
        }

        return new MethodCallInliner(access, desc, mv, inlinedMethod, _callee, _caller  );
    }
}

//MethodCallInliner.java
public class MethodCallInliner extends LocalVariablesSorter {

    private final String oldClass;
    private final String newClass;
    private final MethodNode mn;  //Method Visitor wrappers the mv.
    private List blocks = new ArrayList();
    private boolean inlining;

    public MethodCallInliner(int access, String desc, MethodVisitor mv, MethodNode mn, 
            String oldClass, String newClass){
        super(Opcodes.ASM5, access, desc, mv);
        this.oldClass = oldClass;
        this.newClass =  newClass;
        this.mn = mn;
        inlining = false;
    }

    public void visitMethodInsn(int opcode, String owner, String name,
            String desc, boolean itf) {

        System.out.println("opcode:" + opcode + " owner:" + owner + " name:"
                + name + " desc:" + desc);
        if (!canBeInlined(owner, name, desc)) {
            mv.visitMethodInsn(opcode, owner, name, desc, itf);
            return;
        }
        //if it is INVOKEVIRTUAL  ../Callee::calculate(II), then..
        Remapper remapper = new SimpleRemapper(oldClass, newClass);
        Label end = new Label();
        inlining = true;
        mn.instructions.resetLabels();
        mn.accept(new InliningAdapter(this,opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,remapper, end));
        inlining = false;
        super.visitLabel(end);
    }

    private boolean canBeInlined(String owner, String name, String decs){
            if(name.equals("calculate") && owner.equals("code/sxu/asm/example/Callee")){
            return true;
        }
        return false;
    }
}

//InliningAdapter.java
public class InliningAdapter extends RemappingMethodAdapter {

    private final LocalVariablesSorter lvs;
    private final Label end;

    public InliningAdapter(LocalVariablesSorter mv,
            int acc, String desc,Remapper remapper, Label end) {
        super(acc, desc, mv, remapper);
        this.lvs = mv;
        this.end = end;

//      int offset = (acc & Opcodes.ACC_STATIC)!=0 ?0 : 1;
//      Type[] args = Type.getArgumentTypes(desc);
//      for (int i = args.length-1; i >= 0; i--) {
//          super.visitVarInsn(args[i].getOpcode(
//                  Opcodes.ISTORE), i + offset);
//      }
//      if(offset>0) {
//          super.visitVarInsn(Opcodes.ASTORE, 0);
//      }
    }

    public void visitInsn(int opcode) {
        if(opcode==Opcodes.RETURN || opcode == Opcodes.IRETURN) {
            super.visitJumpInsn(Opcodes.GOTO, end);
        } else {
            super.visitInsn(opcode);
        }
    }

    public void visitMaxs(int stack, int locals) {
        System.out.println("visit maxs: "+stack+"  "+locals);
    }

    protected int newLocalMapping(Type type) {
        return lvs.newLocal(type);
    }
}

在代码中,InliningAdapterMethodCallInliner扩展 LocalVariablesSorter ,它重新编号局部变量。以及 Caller::test::invokevirtual(Callee::calculate) 调用站点上 Callee::calculate() 的内联引用处理主体。

Caller::test()、Callee::calculate 和generated::test 的字节码是:
 //Caller::test()
 public void test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=7, args_size=3
         0: iload_1       
         1: istore_3      
         2: iload_2       
         3: istore        4
         5: iload_3       
         6: iload         4
         8: iadd          
         9: aload_0       
        10: getfield      #13                 // Field _callee:Lcode/sxu/asm/example/Callee;
        13: iload_1       
        14: iload_2       
        15: invokevirtual #39                 // Method code/sxu/asm/example/Callee.calculate:(II)I   //Copy calculate's body here
        18: isub          
        19: istore        5
        21: iload_3       
        22: iload         4
        24: isub          
        25: istore        6
        27: getstatic     #43                 // Field java/lang/System.out:Ljava/io/PrintStream;
        30: iload_3       
        31: invokevirtual #49                 // Method java/io/PrintStream.println:(I)V
        34: getstatic     #43                 // Field java/lang/System.out:Ljava/io/PrintStream;
        37: ldc           #55                 // String 1..........
        39: invokevirtual #57                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        42: return        

//Callee::calculate()

 public int calculate(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=4, args_size=3
         0: aload_0       
         1: getfield      #14                 // Field _a:Ljava/lang/String;
         4: invokevirtual #26                 // Method java/lang/String.length:()I
         7: aload_0       
         8: getfield      #16                 // Field _b:Ljava/lang/String;
        11: invokevirtual #26                 // Method java/lang/String.length:()I
        14: iadd          
        15: istore_3      
        16: iload_3       
        17: iload_1       
        18: iload_2       
        19: iadd          
        20: iadd          
        21: istore_3      
        22: iload_3       
        23: ireturn       

 //data.class
  public void test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=8, args_size=3
         0: iload_1       
         1: istore_3      
         2: iload_2       
         3: istore        4
         5: iload_3       
         6: iload         4
         8: iadd          
         9: aload_0       
        10: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
        13: iload_1       
        14: iload_2       
        15: aload_0       
        16: getfield      #40                 // Field _a:Ljava/lang/String;
        19: invokevirtual #46                 // Method java/lang/String.length:()I
        22: aload_0       
        23: getfield      #49                 // Field _b:Ljava/lang/String;
        26: invokevirtual #46                 // Method java/lang/String.length:()I
        29: iadd          
        30: istore        6
        32: iload         6
        34: iload_1       
        35: iload_2       
        36: iadd          
        37: iadd          
        38: istore        6
        40: iload         6
        42: goto          45
        45: isub          
        46: istore        6
        48: iload_3       
        49: iload         4
        51: isub          
        52: istore        7
        54: getstatic     #59                 // Field java/lang/System.out:Ljava/io/PrintStream;
        57: iload_3       
        58: invokevirtual #65                 // Method java/io/PrintStream.println:(I)V
        61: getstatic     #59                 // Field java/lang/System.out:Ljava/io/PrintStream;
        64: ldc           #67                 // String 1..........
        66: invokevirtual #70                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        69: return        

data.class 上的 javap 结果显示 Callee::calculate 的主体已插入到正确的位置(Caller::test line::15)。但是,有两个主要问题:
  • invokevirtual 前的前三个堆栈对象

    被调用者::计算(第 15 行)
    9:aload_0
    10: getfield #14//字段_callee:Lcode/sxu/asm/example/Callee;
    13: iload_1
    14: iload_2

    内联后不应在堆栈上。
  • 复制的主体(Callee::calculate())中的变量编号 0 应该映射到正确的编号
  • 变量编号不正确。首先,data.class 中 Callee::calculate 的复制体的变量编号(从第 15 行到第 42 行)应该从 5 开始(而不是 0)。其次,Callee::calculate() 之后的变量number 应该按照规则重新编号: a) 如果它在(0,4] 之间,则不改变; b) 如果它与Callee 复制的主体中的数字冲突,则重新编号: :计算()

  • 我去查基类LocalVariablesSorter的实现。问题似乎出在它的构造上:
    protected LocalVariablesSorter(final int api, final int access,
                final String desc, final MethodVisitor mv) {
            super(api, mv);
            Type[] args = Type.getArgumentTypes(desc);
            nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
            for (int i = 0; i < args.length; i++) {
                nextLocal += args[i].getSize();
            }
            firstLocal = nextLocal; 
        }
        private int[] mapping = new int[40];
    

    在我看来,firstLocal总是从 1+ args.length() 开始(在这种情况下它是 3)。本类(class)还提供private int remap(final int var, final Type type) ,它创建新的局部变量并将映射(从现有变量号到新索引)保留在映射数组中。

    我的问题是如何使用 LocalVariablesSorter并正确内联字节码方法 (Callee::calculate)。欢迎任何关于高效内联的建议。

    对于内联之前堆栈上的参数(第 15 行之前)。我的想法是将它们存储为新创建的局部变量,这些变量将由 Callee::calculate 的复制体引用。例如,添加:
    astore 5
    10: getfield #14//字段_callee:Lcode/sxu/asm/example/Callee;

    并添加 mapping[0]=5+1LocalVariablesSorter
    但主要问题是用户不允许更新LocalVariablesSorter::mapping (从旧变量号到映射数组中的新变量)因为 mapping数组是私有(private)的,其更新的唯一位置是在方法中:
       private int remap(final int var, final Type type) {
            if (var + type.getSize() <= firstLocal) { 
            //Variable index will never be modified if it is less than firstLocal. 0 < 3. Nothing i can do for ALOAD 0. 
                return var;
            }
            int key = 2 * var + type.getSize() - 1;
            int size = mapping.length;
            if (key >= size) {
                     .....
            }
            int value = mapping[key];
            if (value == 0) {
                value = newLocalMapping(type);
                setLocalType(value, type);
                mapping[key] = value + 1;
            } else {
                value--;
            }
            if (value != var) {
                changed = true;
            }
            return value;
        }
    

    更新 1 :取消注释 InliningAdapter 构造函数后的 data.class:
         0: iload_1       
         1: istore_3      
         2: iload_2       
         3: istore        4
         5: iload_3       
         6: iload         4
         8: iadd          
         9: aload_0       
        10: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
        13: iload_1       
        14: iload_2       
        15: istore_2        //should be istore 5
        16: istore_1        //should be istore 6
        17: astore_0        //should be astore 7
        18: aload_0        
        19: getfield      #40                 // Field _a:Ljava/lang/String;
        22: invokevirtual #46                 // Method java/lang/String.length:()I
        25: aload_0       
        26: getfield      #49                 // Field _b:Ljava/lang/String;
        29: invokevirtual #46                 // Method java/lang/String.length:()I
        32: iadd          
        33: istore        6
        35: iload         6
        37: iload_1       
        38: iload_2       
        39: iadd          
        40: iadd          
        41: istore        6
        43: iload         6
        45: goto          48
        48: isub          
        49: istore        6
        51: iload_3       
        52: iload         4
        54: isub          
        55: istore        7
        57: getstatic     #59 
    

    新存储的三个变量(15,16,17)应该编号为5,6,7,而不是2,1,0,映射在*store/*load在内联代码中应该像
           0 => 7
           1 => 6 
           2 => 5
           3 => 8
           4 => 9  ...
    

    这些映射应该在数组中:LocalVariablesSorter::mapping由方法 LocalVariablesSorter::remap() 更新.但是,我似乎无法将它们插入 mapping大批。

    应该进行两种重新映射:
  • 重新映射内联代码(来自第 18 行 t0 45)和变量
    索引从 5 开始。最大索引是 k
  • 内联代码后重新映射(从第 46 行到最后),如果原始索引大于 5,任何变量索引都应重新映射(新索引从 k+1 开始)
  • 最佳答案

    正如@Holger 已经建议的那样,首先取消注释 InliningAdapter 中的行.

  • 解决您列出的主要问题:LocalVariablesSorter (由 InliningAdapter 扩展)认为参数已经存储在固定位置的局部变量中——这确实是进入方法时的正常情况。所以它根本不映射那些(见 LocalVariablesSorter.remap() - firstLocal 中的第一行是在构造函数中计算的)。然而,在这种情况下,我们获得堆栈上的参数,并且需要手动分配局部变量。解决办法是告诉LocalVariablesSorter没有参数已经存储在局部变量中(make firstLocal = 0)。然后它将对它们的任何引用视为新变量并为它们分配新的局部变量。这我们可以通过愚弄LocalVariablesSorter来实现认为没有参数并且该方法是静态的(即使它确实不是)。所以我们改变InliningAdapter中的第一行从
        super(acc, desc, mv, remapper);
    


        super(acc | Opcodes.ACC_STATIC, "()V", mv, remapper);
    

    现在变量 0,1,2,... 被重新映射到 5,6,7,... 或类似的(它们是什么并不重要,LocalVariablesSorterCaller(即 MethodCallInliner)实例)负责分配它们)。
  • 还有另一个问题是您映射 Callee类到 Caller通过拥有 InliningAdapter延长 RemappingMethodAdaptor - 但是我猜你想保留 _a_b变量存储在 Callee实例。
  • 如果我的猜测是正确的,那么您实际上不应该重新映射来自 Callee 的引用。至 Caller .你可以做 InliningAdapter延长 LocalVariableSorter取而代之的是摆脱重映射器。
  • 如果我的猜测不正确,那么我猜您可能需要嵌入 Callee 的变量进入 Caller同样,在这种情况下,您应该保留 RemappingMethodAdaptor你有。
  • 调试内联代码时,来自 Callee 的行号没有意义,因为代码被内联到 Caller类(class)。所以来自 Callee 的所有行号应该用 Caller 中的行号替换内联调用发生的地方。不幸的是,在 Java 中您不能逐行指定不同的源代码文件(就像在 C 中一样)。所以你会覆盖 visitLineNumber()InliningAdapter使用这样的东西( inlinedLine 将被传递给 InliningAdapter 的构造函数):
    @Override
    public void visitLineNumber(int line, Label start) {
        super.visitLineNumber(inlinedLine, start);
    }
    

    .. 或者可能完全跳过 super 电话,我不是 100% 确定这一点。
  • 关于java - ASM 内联字节码方法期间重新映射变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29802059/

    有关java - ASM 内联字节码方法期间重新映射变量的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

      我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

    5. Ruby 方法() 方法 - 2

      我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

    6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    7. ruby - Highline 询问方法不会使用同一行 - 2

      设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

    8. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

      我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

    9. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

      我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

    10. ruby - 通过 ruby​​ 进程共享变量 - 2

      我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

    随机推荐