所以我有一些故障代码来调试SOMEthing抛出一个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生成本地信息,就像 使用javac -g
进行编译一样?
或者是否有一个有用的调试器可以做我想要的?
答案 0 :(得分:3)
生成局部变量信息非常简单。在目标方法访问者上发出正确的visitLocalVariable
调用,声明局部变量的名称,类型和范围。这将在类文件中生成LocalVariableTable
attribute。
在源代码级调试方面,工具只需在类上查找SourceFile
attribute即可获取要加载和显示的文本文件的名称。您可以通过调用目标类访问者{ClassWriter
)上的visitSource(fileName, null)
来生成它。可以通过目标方法访问者上的visitLineNumber
调用来声明指定文本文件和字节代码指令之间的关系。对于普通源代码,只需在关联行更改时调用它。但对于字节代码表示,它会针对每条指令进行更改,这可能会导致相当大的类文件,因此您绝对应该生成这些调试信息。
现在,您只需要生成文本文件。您可以将目标ClassWriter
包装在TraceClassVisitor
中,然后再将其传递给代码生成器,以便在生成代码时生成人类可读的表单。但是我们必须扩展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验证了这一点: