有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java调试使用JDB(或类似工具)生成的字节码

因此,我有一些出错的代码要调试,其中有东西抛出了一个NPE,我想逐步通过一些生成的方法来尝试找出原因

除了盲目地走没有什么用处

Thread-4[1] list
Source file not found: Foo.java
Thread-4[1] locals
Local variable information not available.  Compile with -g to generate variable information

代码是生成的,因此当然没有可用于JDB的.java文件

因为我没有用javac编译它,所以也没有指定任何-g标志

我能告诉JDB给我看字节码吗(他显然有,否则java就没有什么可执行的)

我是否可以告诉ASM生成局部信息,就像它是用^{编译的一样

或者是否有一个有用的调试器可以完成我正在寻找的任务


共 (1) 个答案

  1. # 1 楼答案

    生成局部变量信息相当容易。在目标方法访问者上发出正确的^{}调用,声明局部变量的名称、类型和范围。这将在类文件中生成^{} attribute

    当涉及到源代码级调试时,这些工具只需查找类上的^{} attribute即可获得要加载和显示的文本文件的名称。您可以通过在目标类访问者(ClassWriter)上调用^{}来生成它。指定的文本文件和字节码指令之间的关系可以通过调用目标方法visitor上的^{}来声明。对于普通源代码,只需在相关行发生更改时调用它。但是对于字节码表示,它会因每个指令而改变,这可能会导致一个相当大的类文件,因此您应该明确地使这些调试信息的生成成为可选的

    现在,您只需要生成文本文件。在将目标ClassWriter传递给代码生成器之前,可以将其包装在^{}中,以便在生成代码时生成人类可读的形式。但我们必须扩展ASM提供的Textifier,因为我们需要跟踪缓冲文本的行号,还希望抑制行号信息本身的输出,这将使源代码变得混乱,每个指令增加两行

    public class LineNumberTextifier extends Textifier {
        private final LineNumberTextifier root;
        private boolean selfCall;
        public LineNumberTextifier() { super(ASM5); root = this; }
        private LineNumberTextifier(LineNumberTextifier root) { super(ASM5); this.root = root; }
        int currentLineNumber() { return count(super.text)+1; }
        private static int count(List<?> text) {
            int no = 0;
            for(Object o: text)
                if(o instanceof List) no+=count((List<?>)o);
                else {
                    String s = (String)o;
                    for(int ix=s.indexOf('\n'); ix>=0; ix=s.indexOf('\n', ix+1)) no++;
                }
            return no;
        }
        void updateLineInfo(MethodVisitor target) {
            selfCall = true;
            Label l = new Label();
            target.visitLabel(l);
            target.visitLineNumber(currentLineNumber(), l);
            selfCall = false;
        }
        // do not generate source for our own artifacts
        @Override public void visitLabel(Label label) {
            if(!root.selfCall) super.visitLabel(label);
        }
        @Override public void visitLineNumber(int line, Label start) {}
        @Override public void visitSource(String file, String debug) {}
        @Override protected Textifier createTextifier() {
            return new LineNumberTextifier(root);
        }
    }
    

    然后,您可以像这样一起生成类文件和源文件:

    Path targetPath = …
    String clName = "TestClass", srcName = clName+".jasm", binName = clName+".class";
    Path srcFile = targetPath.resolve(srcName), binFile = targetPath.resolve(binName);
    ClassWriter actualCW = new ClassWriter(0);
    try(PrintWriter sourceWriter = new PrintWriter(Files.newBufferedWriter(srcFile))) {
        LineNumberTextifier lno = new LineNumberTextifier();
        TraceClassVisitor classWriter = new TraceClassVisitor(actualCW, lno, sourceWriter);
        classWriter.visit(V1_8, ACC_PUBLIC, clName, null, "java/lang/Object", null);
        MethodVisitor constructor
            = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
        constructor.visitVarInsn(ALOAD, 0);
        constructor.visitMethodInsn(
            INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        constructor.visitInsn(RETURN);
        constructor.visitMaxs(1, 1);
        constructor.visitEnd();
        MethodVisitor main = classWriter.visitMethod(
            ACC_PUBLIC|ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
        Label start = new Label(), end = new Label();
        main.visitLabel(start);
        lno.updateLineInfo(main);
        main.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        lno.updateLineInfo(main);
        main.visitLdcInsn("hello world");
        lno.updateLineInfo(main);
        main.visitMethodInsn(
            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        lno.updateLineInfo(main);
        main.visitInsn(RETURN);
        main.visitLabel(end);
        main.visitLocalVariable("arg", "[Ljava/lang/String;", null, start, end, 0);
        main.visitMaxs(2, 1);
        main.visitEnd();
        classWriter.visitSource(srcName, null);
        classWriter.visitEnd(); // writes the buffered text
    }
    Files.write(binFile, actualCW.toByteArray());
    

    它生成的“源”文件如下所示

    // class version 52.0 (52)
    // access flags 0x1
    public class TestClass {
    
    
      // access flags 0x2
      private <init>()V
        ALOAD 0
        INVOKESPECIAL java/lang/Object.<init> ()V
        RETURN
        MAXSTACK = 1
        MAXLOCALS = 1
    
      // access flags 0x9
      public static main([Ljava/lang/String;)V
       L0
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        LDC "hello world"
        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
        RETURN
       L1
        LOCALVARIABLE arg [Ljava/lang/String; L0 L1 0
        MAXSTACK = 2
        MAXLOCALS = 1
    }
    

    javap报告

      Compiled from "TestClass.jasm"
    public class TestClass
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC
    {
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #18                 // String hello world
             5: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0   arg   [Ljava/lang/String;
          LineNumberTable:
            line 17: 0
            line 18: 3
            line 19: 5
            line 20: 8
    }
    SourceFile: "TestClass.jasm"
    

    示例生成器将两个文件放在同一个目录中,该目录已经足够jdb使用它了。当您将文件放入类路径resp时,它还应该与IDE调试器一起工作。项目的源路径

    Initializing jdb ...
    > stop in TestClass.main
    Deferring breakpoint TestClass.main.
    It will be set after the class is loaded.
    > run TestClass
    run  TestClass
    Set uncaught java.lang.Throwable
    Set deferred uncaught java.lang.Throwable
    >
    VM Started: Set deferred breakpoint TestClass.main
    
    Breakpoint hit: "thread=main", TestClass.main(), line=17 bci=0
    17        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    
    main[1] locals
    Method arguments:
    arg = instance of java.lang.String[0] (id=433)
    Local variables:
    main[1] step
    >
    Step completed: "thread=main", TestClass.main(), line=18 bci=3
    18        LDC "hello world"
    
    main[1] step
    >
    Step completed: "thread=main", TestClass.main(), line=19 bci=5
    19        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    
    main[1] step
    > hello world
    
    Step completed: "thread=main", TestClass.main(), line=20 bci=8
    20        RETURN
    
    main[1] step
    >
    The application exited
    

    如上所述,当您将这两个文件放入项目的类和源路径时,这也适用于IDE。我刚刚用Eclipse验证了这一点:

    Eclipse debugging ASM code