【问题标题】:Java ASM CheckClassAdapter on ClassNodeClassNode 上的 Java ASM CheckClassAdapter
【发布时间】:2021-01-15 10:57:45
【问题描述】:

ASM CheckClassAdapter 对于在验证错误的情况下获取有关类失败原因的有用日志输出非常有用,但是它不能用于堆栈图帧无效的情况。 CheckClassAdapter 不能用于这些情况,因为它接受 ClassReader,这意味着要使用它来检查转换后的 ClassNode,我必须执行以下操作:

ClassWriter verifyWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(verifyWriter);
CheckClassAdapter.verify(new ClassReader(verifyWriter.toByteArray()), true, printDumpLogFile);

当 ClassWriter 由于我的 ClassNode 的字节码异常无效而失败时,传递给 CheckClassAdapter 的输出无效并且格式错误,带有许多空帧和 NOP。有什么方法可以将 ClassNode 传递给 CheckClassAdapter,避免使用限制其用处的 ClassWriter?

【问题讨论】:

  • 你能分享一个或几个你有问题的类文件吗?我想尝试一下。
  • 顺便说一句,你如何创建classNode

标签: java java-bytecode-asm


【解决方案1】:

如果你有办法获取输入类的字节码而不是ClassNode,你可以使用这样的方法:

CheckClassAdapter.verify(
  bytes,
  true,
  new PrintWriter(System.out)
);

例如,如果字节码已经存储在类文件中,你可以这样做:

CheckClassAdapter.verify(
  new ClassReader(new FileInputStream(args[0])),
  true,
  new PrintWriter(System.out)
);

但实际上,如果堆栈图帧无效,CheckClassAdapter 可能不会报告任何内容。至少我有它没有的类文件。顺便说一句,这同样适用于org.apache.bcel.verifier.Verifier。尽管如此,JVM 还是会抛出一个VerifyError,即将类加载到实际的 JVM 中是最终的检查。

【讨论】:

  • 我应该更明确一点,正在读取的类文件是在运行时转换的,当转换的类无法写入时添加验证器。验证者可以帮助调试代码问题。我已经让它使用 TraceClassVisitor 转储有问题的生成类的代码,但是使用验证器比依赖 JVM 错误更清楚问题出在哪里。
【解决方案2】:

我找到了一个解决方案,创建了一个扩展 CheckClassAdapter 的类并添加了一个带有 ClassNode 参数的方法。阅读 CheckClassAdapter 的源码,我发现 ClassReader 还是访问了一个类节点。

public static void verify(
  final ClassReader classReader,
  final ClassLoader loader,
  final boolean printResults,
  final PrintWriter printWriter) {
ClassNode classNode = new ClassNode();
classReader.accept(
    new CheckClassAdapter(/*latest*/ Opcodes.ASM10_EXPERIMENTAL, classNode, false) {},
    ClassReader.SKIP_DEBUG);

如您所见,ASM 可以公开一个方法,只在 ClassNode 中输入,但由于某种原因没有。

这是扩展 CheckClassAdapter 的类的代码,添加了此功能:

public class CheckClassAdapterClassNode extends CheckClassAdapter {

public CheckClassAdapterClassNode(ClassVisitor classVisitor) {
    super(classVisitor);
}

/**
 * Checks the given class.
 *
 * @param classNode the class to be checked.
 * @param loader a <code>ClassLoader</code> which will be used to load referenced classes. May be
 *     {@literal null}.
 * @param printResults whether to print the results of the bytecode verification.
 * @param printWriter where the results (or the stack trace in case of error) must be printed.
 */
public static void verify(
        final ClassNode classNode,
        final ClassLoader loader,
        final boolean printResults,
        final PrintWriter printWriter) {

    Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName);
    List<MethodNode> methods = classNode.methods;

    List<Type> interfaces = new ArrayList<>();
    for (String interfaceName : classNode.interfaces) {
        interfaces.add(Type.getObjectType(interfaceName));
    }

    for (MethodNode method : methods) {
        SimpleVerifier verifier =
                new SimpleVerifier(
                        Type.getObjectType(classNode.name),
                        syperType,
                        interfaces,
                        (classNode.access & Opcodes.ACC_INTERFACE) != 0);
        Analyzer<BasicValue> analyzer = new Analyzer<>(verifier);
        if (loader != null) {
            verifier.setClassLoader(loader);
        }
        try {
            analyzer.analyze(classNode.name, method);
        } catch (AnalyzerException e) {
            e.printStackTrace(printWriter);
        }
        if (printResults) {
            printAnalyzerResult(method, analyzer, printWriter);
        }
    }
    printWriter.flush();
}

static void printAnalyzerResult(
        final MethodNode method, final Analyzer<BasicValue> analyzer, final PrintWriter printWriter) {
    Textifier textifier = new Textifier();
    TraceMethodVisitor traceMethodVisitor = new TraceMethodVisitor(textifier);

    printWriter.println(method.name + method.desc);
    for (int i = 0; i < method.instructions.size(); ++i) {
        if (method.instructions.get(i) == null) continue;
        method.instructions.get(i).accept(traceMethodVisitor);

        StringBuilder stringBuilder = new StringBuilder();
        Frame<BasicValue> frame = analyzer.getFrames()[i];
        if (frame == null) {
            stringBuilder.append('?');
        } else {
            for (int j = 0; j < frame.getLocals(); ++j) {
                stringBuilder.append(getUnqualifiedName(frame.getLocal(j).toString())).append(' ');
            }
            stringBuilder.append(" : ");
            for (int j = 0; j < frame.getStackSize(); ++j) {
                stringBuilder.append(getUnqualifiedName(frame.getStack(j).toString())).append(' ');
            }
        }
        while (stringBuilder.length() < method.maxStack + method.maxLocals + 1) {
            stringBuilder.append(' ');
        }
        printWriter.print(Integer.toString(i + 100000).substring(1));
        printWriter.print(
                " " + stringBuilder + " : " + textifier.text.get(textifier.text.size() - 1));
    }
    for (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks) {
        tryCatchBlock.accept(traceMethodVisitor);
        printWriter.print(" " + textifier.text.get(textifier.text.size() - 1));
    }
    printWriter.println();
}

private static String getUnqualifiedName(final String name) {
    int lastSlashIndex = name.lastIndexOf('/');
    if (lastSlashIndex == -1) {
        return name;
    } else {
        int endIndex = name.length();
        if (name.charAt(endIndex - 1) == ';') {
            endIndex--;
        }
        return name.substring(lastSlashIndex + 1, endIndex);
    }
}

}

我可能会在 ASM 存储库上创建一个问题并添加一个拉取请求来添加它。

【讨论】:

  • 我没有分析你的代码,但我很高兴你能够解决你的问题。当您在这里有足够的声望点时,您也可以接受自己的答案以结束问题。还请编辑您的答案,添加指向 ASM 问题和/或 PR 的链接以供进一步参考。谢谢。
  • 我也有兴趣查看MCVE 以及带有伪造堆栈图的类文件的诊断输出。有什么可以识别的吗?在我自己的情况下,我没有看到任何可疑之处,好像一切都很好,但实际上并非如此。不过,除了CheckClassAdapter,我不使用TraceMethodVisitor。我不是 ASM 专业人士,所以我想从这个问题中学到一些东西。
  • 不需要创建子类来复制static 方法。除此之外,对于您要检查的内容,一个简单的classNode.accept(new CheckClassAdapter(null)); 就足够了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多