我正在处理的遗留项目包括一些二进制jar文件形式的外部库。我们决定对于分析和潜在的修补,我们希望接收这个库的源代码,使用它们来构建新的二进制文件,经过详细和足够长的回归测试后切换到这些二进制文件。
假设我们已经检索并构建了源(我实际上处于计划阶段)。在进行实际测试之前,我想执行一些“兼容性检查”,以排除源代表与“旧”二进制文件中的内容截然不同的可能性。
使用javap
工具,我能够提取用于编译的JDK版本(至少我相信它是JDK的版本)。它说,二进制文件是使用主要版本46和次要版本0构建的。根据this article,它映射到JDK 1.2。
假设相同的JDK将用于源编译。
问题是: 如果这两个二进制文件都是从相同的源构建的,是否有可靠且可能有效的验证方法?我想知道所有方法签名和类定义是否相同,以及大多数或可能所有方法实现是否相同/相似。
库非常大,所以我认为对反编译二进制文件的详细分析可能不是一种选择。
答案 0 :(得分:1)
我建议采用多阶段流程:
应用之前建议的Jardiff或类似内容,看看是否存在任何API差异。如果可能,选择一个具有报告私有方法等选项的工具。实际上,Java中的任何实质性实现更改都可能会更改某些方法和类,即使公共API未更改。
如果您有API匹配,请使用指定的编译器编译一些随机选择的文件,反编译结果和原始类文件,并比较结果。如果它们匹配,则将相同的过程应用于越来越大的代码体,直到找到不匹配或已检查过所有内容。
反编译代码的差异更有可能为您提供有关差异性质的线索,并且比实际的类文件更容易过滤非重要差异。
如果您遇到不匹配,请进行分析。这可能是由于你不关心的事情。如果是这样,尝试构造一个脚本,删除这种形式的差异并恢复编译和比较过程。如果您遇到广泛的不匹配,请尝试使用优化等编译器参数。如果对编译器参数的调整消除了差异,请继续批量比较。此阶段的目标是找到编译器参数和反编译代码过滤器的组合,这些过滤器会对示例文件产生匹配,并将它们应用于库的批量比较。
如果在反编译代码中无法获得相当接近的匹配,则可能没有正确的源代码。即便如此,如果您有API匹配,则可能需要使用编译结果构建系统并运行测试。如果您的测试至少与您从源代码构建的版本运行良好,请继续使用它。
答案 1 :(得分:0)
有各种各样的JAR比较工具。过去相当不错的是Jardiff。我暂时没有使用它,但我确信它仍然可用。在同一空间也有一些商业产品可以满足您的需求。
答案 2 :(得分:0)
Jardiff认为Perception是一个良好的开端,但理论上没有办法100%肯定。这是因为可以使用不同的编译器和不同的编译器配置和优化级别编译相同的源。因此,除了类和方法签名之外,没有办法比较二进制代码(字节码)。
你对方法的“类似实现”是什么意思?让我们假设一个聪明的编译器丢弃else
个案,因为它发现条件可能不是真的。这两个是相似的吗?是和否..: - )
恕我直言的最好方法是设置非常好的回归测试用例,检查你的库的每个关键功能。这可能是一个恐怖,但从长远来看可能比寻找bug更便宜。这完全取决于您在此项目中的未来计划。这不是一个简单易行的决定。
答案 3 :(得分:0)
对于方法签名,请使用jardiff等工具。
为了实现相似性,你必须回到疯狂的猜测。比较操作码级别的字节码可能与编译器有关,并导致大量漏报。如果是这种情况,您可以使用 LineNumberTable 来回溯比较类的方法。
它为您提供了每个方法的行号列表(只要使用调试标志编译类文件,这在很旧的或商业库中经常会丢失)。
如果两个类文件是从相同的源代码编译的,那么至少每个方法的行号应该完全匹配。
您可以使用Apache BCEL等库来检索LineNumberTable:
// import org.apache.bcel.classfile.ClassParser;
JavaClass fooClazz = new ClassParser( "Foo.class" ).parse();
for( Method m : fooClazz.getMethods() )
{
LineNumberTable lnt = m.getLineNumberTable();
LineNumber[] tab = lnt.getLineNumberTable();
for( LineNumber ln : tab )
{
System.out.println( ln.getLineNumber() );
}
}