.class文件中的错误版本号 - 如何查找它是哪个类

时间:2014-03-28 10:25:27

标签: java class

我相信我已经看过这个问题上的所有其他帖子。我认为这是一个不同的问题,因为我正在寻找一种方法来确定导致问题的类。

我使用Maven构建我的jar。

如果我要求它为Java 5构建并且我在Java 6下运行它可以正常工作。

如果我要求它为Java 6构建并且我在Java 6下运行它可以正常工作。

如果我要求它为Java 5构建并且我在Java 5下运行它就会失败:

java.lang.RuntimeException: public static void ....main(java.lang.String[]) failed for arguments (String[]{...})
Caused by: java.lang.reflect.InvocationTargetException: 
...
Caused by: java.lang.UnsupportedClassVersionError: Bad version number in .class file
at java.lang.ClassLoader.defineClass1(Native Method)
    ...

我使用Java Version Check检查了jar,它报告了jar中为Java 5构建的所有类。

我只能得出结论,它是黑客攻击它自己的类路径并连接到我的机器上不是Java 5的其他库/ jar。这很可能但我无法访问该进程的源代码据我所知,我无法单步进入类加载器以找出它正在加载的类。

我可以使用哪些技巧来帮助我找出哪个类导致异常?

2 个答案:

答案 0 :(得分:3)

捕获几乎所有类定义的一种方法是使用Instrumentation API使用 Java代理。此API允许在使用之前转换所有字节代码(某些核心类除外),但当然,您可以使用它来提取版本号并生成报告而不更改字节代码。

类文件转换的一个不错的属性是JVM将在拒绝格式错误/不支持的字节代码之前调用转换器,以使它们有机会将代码转换为可用的代码。因此,我们将在抛出UnsupportedClassVersionError之前看到负责的字节代码。

以下是完成工作的Java代理的代码:

package versioncheck;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class CheckAgent implements ClassFileTransformer
{
  public static void premain(String agentArgs, Instrumentation inst)
  {
    inst.addTransformer(new CheckAgent(), inst.isRetransformClassesSupported());
  }
  public static void agentmain(String agentArgs, Instrumentation inst)
  {
    inst.addTransformer(new CheckAgent(), inst.isRetransformClassesSupported());
  }
  private int supported;
  CheckAgent()
  {
    supported=(int)Double.parseDouble(System.getProperty("java.class.version"));
  }
  public byte[] transform(ClassLoader loader, String className,
      Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
      byte[] classfileBuffer) throws IllegalClassFormatException
  {
    int version=(classfileBuffer[6]&0xff)<<8 | (classfileBuffer[7]&0xff);
    if(version>supported)
      System.out.println(className+" v"+version+" from "
      +protectionDomain.getCodeSource());
    return null;
  }
}

然后你需要将这个类与包含以下行的清单一起放在一个罐子里:

Premain-Class: versioncheck.CheckAgent
Agent-Class: versioncheck.CheckAgent
Can-Redefine-Classes: true

最后要做的是将以下参数添加到启动Java应用程序的命令行:-javaagent:pathtothe.jar

另见http://docs.oracle.com/javase/7/docs/api/java/lang/instrument/package-summary.html

作为旁注,这样的Java代理也可以通过将字节代码转换(向下移植)到较低版本来解决问题。但这远远超出了最初的问题......

答案 1 :(得分:0)

要检查类路径中的JVM版本JAR,我会做类似下面的配方。但正如我在上一篇评论中已经提到的,这个解决方案不包括JAR有一个自定义类加载器修改类路径并做其他邪恶事情的情况。不过,您可以尝试使用此解决方案,看看它是否有帮助。如果不是,则需要更复杂的解决方案......

  1. 检索应用程序的完整类路径。通常我会使用Maven AppAssembler执行此步骤,但肯定还有其他方法。 AppAssembler很方便,因为它构建了一个显式指定类路径的shell脚本,所以你可以简单地复制粘贴它。

  2. 以classpath作为参数运行以下bash脚本。该脚本在内部使用fileunzipbc,因此您应该安装这些工具。

  3. 剧本:

    #!/bin/sh -e
    
    # force numeric to use decimal point
    LC_NUMERIC=C
    export LC_NUMERIC
    
    tempdir=
    
    cleanup() {
        trap - EXIT
        if test -n "$tempdir"; then
            rm -rf "$tempdir" ||:
        fi
        exit "$@"
    }
    
    debug() {
        test -z "$DEBUG" || echo "$@" >&2 ||:
    }
    
    get_jar_version() {
        local ver maxVersion compare
        maxVersion=0.0
        for ver in $(find "$1" -type f -name '*.class' -exec file {} \; | grep 'Java class data, version' | sed -e 's;.*Java class data, version ;;'); do
            debug "Version = '$ver'"
            compare=$(echo "$ver > $maxVersion" | bc)
            if [ "x$compare" = "x1" ]; then
                maxVersion="$ver"
            fi
        done
        debug "maxVersion=$maxVersion"
        echo -n "$maxVersion"
    }
    
    process_classpath_jars() {
        trap cleanup EXIT
        local cp jar oldIFS maxVersion
        oldIFS="$IFS"
        for cp in "$@"; do
            IFS=':'
            for jar in $cp; do
                if [ -z "$jar" -o "${jar%.jar}" = "$jar" ]; then
                    continue
                fi
                debug "processing JAR $jar"
                tempdir=$(mktemp -d jar.XXXXXXX)
                unzip -qq "$jar" -d "$tempdir"
                IFS="$oldIFS"
                maxVersion=$(get_jar_version "$tempdir")
                rm -rf "$tempdir" ||:
                tempdir=
                IFS=':'
                echo "$jar is compiled for $maxVersion JVM"
            done
            IFS="$oldIFS"
        done
    }
    
    process_classpath_jars "$@"