CFR Java 反编译器:Class/JAR 反编译实战与回归测试体系解析

发布时间:2026/10/15 3:35:16
CFR Java 反编译器:Class/JAR 反编译实战与回归测试体系解析
逆向工程开发工具【免费下载链接】cfrThis is the public repository for the CFR Java decompiler项目地址https://gitcode.com/gh_mirrors/cf/cfr点击查看免费下载CFR 是一个完全用 Java 6 编写、零外部运行时依赖的 Java 字节码反编译器能将.class文件和整个 JAR 还原为可读的 Java 源码并支持 Java 9/12/14 时代的诸多现代特性。本文基于官方仓库文档 README.md 与配套源码完整覆盖 CFR 的获取与命令行用法、从源码构建到运行反编译的全流程以及围绕期望输出比对搭建的反编译回归测试体系——包括 options 文件、期望数据目录结构、decompilation note 注释和批量生成期望数据的系统属性帮助你既会用 CFR 反编译也能为其贡献可靠的测试用例。一、CFR 的定位与设计约束从 README.md 的表述看CFR 的核心能力是decompile modern Java features——它支持反编译 Java 9模块化相关观察、Java 12switch 表达式与 Java 14instanceof模式匹配等现代语言特性生成的 class 文件甚至可以把其他 JVM 语言如 Kotlin、Scala编译出的 class 文件尽量还原成 Java。这一能力与它自身古老的实现形成鲜明对比实现语言刻意停留在 Java 6。contributing.md 中明确写道 Its in Java 6. This is deliberate. This wont change目标之一就是在尽可能多的敌对环境hostile environment中运行包括 Apache Harmony 这类 JVM 6 克隆。这一点在 pom.xml 中有直接佐证javaVersion1.6/javaVersion被同时用于maven.compiler.source与maven.compiler.target。没有任何外部运行时依赖。pom.xml 的dependencies中只声明了两个testscope 的库org.junit.jupiter:junit-jupiter运行测试和io.github.java-diff-utils:java-diff-utils生成测试失败的 unified diff生产代码完全自包含。不接受反射contributing.md 明确 No reflection please这也是反编译器能在受限环境中保持轻量与自足的原因。当前仓库快照的版本号为0.153-SNAPSHOT见 pom.xml 的version标签说明 0.152 之后的开发正在进行中。二、获取 CFR 的四种途径README 的 Getting CFR 一节给出了四个获取渠道按发布时效排列官网发布页CFR 的主站点是 benf.org/other/cfr各版本 release 会附带作者的大量分析随笔GitHub Release 附件自 0.145 起二进制 jar 会随 release tag 一并发布Maven Central构件坐标为org.benf:cfr与 pom.xml 中的groupId/artifactId一致。README 提醒 release 通常比官网晚几天才同步到 Maven Centralto allow for release regretSNAPSHOT 版本可从 GitHub Actions 工作流maven.ymlmaster 分支的 workflow artifacts 下载对应 master 分支持续构建的快照产物。三、命令行使用反编译单个 Class 与整个 JAR3.1 基本用法README 给出的核心操作非常直接运行对应版本的 jar把要反编译的目标作为参数传入——目标可以是class 文件路径也可以是类路径classpath上的全限定类名。完整参数列表通过--help查看java -jar cfr-version.jar com.example.Foo # 按类路径上的 FQN 反编译 java -jar cfr-version.jar /path/to/Foo.class # 按 class 文件路径反编译 java -jar cfr-version.jar --help # 列出全部参数3.2 反编译整个 JAR 并输出到目录要反编译整个 JAR只需把 jar 路径作为目标README 强调如果你想输出文件你大概率会想就追加--outputdirjava -jar cfr-version.jar /path/to/lib.jar --outputdir /tmp/putithere从源码结构看这个选项的底层语义是outputdir未设置时输出走默认 stdout 通道设置后由输出工厂为每个反编译结果创建对应的文件 sink。相关实现分布在 src/org/benf/cfr/reader/Driver.java 与src/org/benf/cfr/reader/util/output/目录下的 sink/dumper 体系。3.3 入口调用链命令行入口是org.benf.cfr.reader.Main其 main 方法 的调用链为GetOptParser.parse(args, OptionsImpl.getFactory())把参数拆分为文件名列表 Options 对象处理--help与--version两个特殊分支new CfrDriver.Builder().withBuiltOptions(options).build()构建驱动器随后cfrDriver.analyse(files)开始分析。Main中保留的doClass/doJar静态方法是历史兼容入口注释写明 too many people use it - left for historical reasons内部委托给 Driver.java。在Driver.doJar中可以看到 JAR 反编译的几个关键细节通过dcCommonState.explicitlyLoadJar(path, analysisType)按 class 文件版本号分组加载getVersionCollisions检测同一类名在不同版本目录下的冲突——这正是 multi-release JARJEP 238支持的基础summaryDumper会在输出前打印 Summary for 及 CFR 版本头CfrVersionInfo.VERSION_INFO这也是反编译输出顶部Decompiled with CFR注释的来源。3.4 常用选项及其默认值完整参数以--help为准选项注册中心 OptionsImpl.java 中每个选项都带有描述文本和默认值。下表摘录若干与日常使用、测试相关的选项名称与默认值均直接取自源码选项默认值说明摘自源码描述outputdir无null输出目录JAR 反编译落盘必用showversiontrue在输出头部显示 CFR 版本回归测试时常关闭usenametabletrue使用 class 文件中的局部变量名表usesignaturestrue在签名明显无误时优先使用 Signature 而非 descriptorsugarboxingtrue尽可能去除无意义的装箱包装sugarassertstrue把字节码还原为assert语句decodefinallytrue还原finally语句结构commentstrue输出描述反编译器状态/回退标志的注释renameillegalidentsfalse重命名非法 Java 标识符会破坏反射访问故默认关闭renamedupmembersfalse重命名歧义/重复字段同上破坏反射lenientfalse在通常抛异常的场景下更宽容dumpexceptionstacktracetrue打印反编译异常的堆栈关闭后可得到一致输出jarfilter无null子串正则仅反编译 FQN 匹配的类针对 JARextraclasspath无null附加类路径需要时用于解析外部类forceclassfilever无null强制按指定版本反编译 class 文件methodname无null只反编译指定方法名值得注意的设计细节rename是renamedupmembersrenameillegalidentsrenameenumidents三个选项的合成开关且后三者通过DefaultChainBooleanDecoder链式继承其默认值——单独设置rename true会同时打开三项。四、从源码构建 CFRREADME 的 Building CFR 部分给出的流程可以完整继承并补充如下安装 Maven此处仅保留 Maven 名称官方文档原链接指向 maven 官网在项目根目录执行mvn compile主类为org.benf.cfr.reader.Main构建完成后从target/classes可直接验证java org.benf.cfr.reader.Main java.lang.Object该命令会让 CFR 反编译 JDK 自带的java.lang.Object是最低成本的冒烟测试。JAVA_HOME 陷阱如果编译时出现maven-compiler-plugin...: Compilation failure通常是JAVA_HOME指向了不再支持source/target 6的新版 JDKJDK 12 已移除对 1.6 的支持。README 的修复建议是把JAVA_HOME指回仍支持编译到 1.6 的 JDK如 JDK 11同时注意 Maven 3.3.1 本身要求 Java 1.7因此最佳方案是PATH与JAVA_HOME都使用 JDK 8、9、10 或 11。构建链上还有两个值得了解的插件配置见 pom.xmltemplating-maven-plugin从src-templates目录过滤生成源码即 CfrVersionInfo.java——版本号、Git 修订号等构建信息在此注入git-commit-id-plugin负责提供 Git 修订信息maven-jar-plugin把mainClass写为org.benf.cfr.reader.Main并附带默认 Implementation/Specification 清单条目Not used by CFR, but maybe useful for other projects depending on CFR。五、反编译回归测试体系decompilation-testREADME 的 Decompilation tests 一节是本文的重点之一。Maven 构建过程中会自动执行反编译测试校验当前 CFR 的反编译输出与既往期望输出一致。理解这套体系需要分清三个目录目录内容归属decompilation-test/test-data测试用 class 文件与 JAR 文件独立 Git 子仓库submoduledecompilation-test/test-data-expected-output期望输出与自定义配置本仓库decompilation-test/test-specs测试配置如 classes.xml本仓库5.1 测试数据来源与克隆方式因为 test-data 是单独的 Git 仓库克隆本仓库时必须带上git clone --recurse-submodules。测试实现 DecompilationTestImplementation.java 中的getTestDataSubDir会检查该目录存在且非空否则抛出提示 make sure that the test data Git submodule has been cloned properly 的ExtensionConfigurationException当前镜像中decompilation-test/test-data为空目录正是未拉取子模块的状态。期望输出与测试配置放在本仓库内的原因README 解释得很清楚to allow altering it without having to modify the corresponding test data——修改期望输出无需改动二进制测试数据的独立仓库。5.2 测试类与驱动方式测试入口是 DecompilationTest.java它只是一个 JUnit 5 参数化测试壳ParameterizedTest(name [{index}] {0}) ClassFileTestDataSource(classes.xml) void classFile(Path classFilePath, MapString, String cfrOptionsDict, Path output, String outputPrefix) throws IOException { DecompilationTestImplementation.assertClassFile(classFilePath, cfrOptionsDict, output, outputPrefix); }参数由自定义扩展ClassFileTestDataProvider从 decompilation-test/test-specs/classes.xml 解析。以其中一条记录为例class !-- 路径相对于 test-datajava_6 指编译该 class 所用的编译器版本而非反编译所用 JVM -- pathprecompiled_tests/java_6/org/benf/cfr/tests/path nameCondJumpTest2c/name !-- label name 需全局唯一 -- label6/label options option keyusenametable valuefalse/ /options /classpathnamelabel三者共同定位一条测试class 文件位于test-data/path/name.class期望输出前缀为name.label。上例期望文件即为 CondJumpTest2c.6.expected.java 与 CondJumpTest2c.10.expected.java分别对应 java_6 与 java_10 编译产物——同一个源文件用不同编译器产出可验证 CFR 对多版本字节码的稳定性。5.3 期望输出不是金标准README 特别强调期望输出并非 gold standard它只描述当前期望的输出。如果反编译结果的改进是合理的完全可以调整期望输出使其与更好、更正确的结果对齐。这一立场在实现中体现为内置的更新期望数据机制见第七节而不是只读比对。失败产物方面测试失败时会生成 unified diff写入target/cfr-test-failures-diff目录源码常量TEST_FAILURE_DIFF_OUTPUT_DIRdiff 由 java-diff-utils 的DiffUtils/UnifiedDiffUtils生成CI 工作流还会把该目录作为名为 decompilation-test-failures-diff 的 workflow artifact 供下载。README 也建议在 IDE 中直接运行DecompilationTest——IDE 对断言失败值自带 diff 视图通常比 Maven 控制台输出更易读。5.4 测试内的反编译实现细节DecompilationTestImplementation.decompile(...)展示了 CFRJava API而非命令行的标准用法通过CfrDriver.Builder().withOptions(options).withOutputSink(sinkFactory).build()构建驱动器driver.analyse(singletonList(path))执行反编译自定义OutputSinkFactory把三类输出分别收进内存JAVAsink 收DecompiledMultiVer含多版本信息、EXCEPTIONsink 收异常消息路径 消息 异常类名、SUMMARYsink 收 summary 文本固定注入两个选项以屏蔽环境差异showversionfalse避免 CFR 版本变化污染输出与dumpexceptionstacktracefalse避免不同启动方式导致堆栈不同summary 文本中的版本号与绝对路径分别被替换为version与path/...占位符保证可复现比对。API 层接口即 CfrDriver.java其 Javadoc 也声明所有 Builder 参数都是可选的裸 Builder 会得到默认 CFR 行为withOptions接收等价于命令行选项的 Map如{sugarboxing - false}但Options接口本身不承诺跨版本稳定。六、options 文件按用例定制反编译每个测试用例可以附带一个 options 文件格式约定来自 README Options file 一节每行一个 CFR 选项key 与 value 用空格分隔空行与以#开头的行被忽略用作注释。示例# Enable identifier renaming renameillegalidents true其中renameillegalidents对应 OptionsImpl.java 中注册的RENAME_ILLEGAL_IDENTS默认falseRename identifiers which are not valid java identifiers. Note - this WILL break reflection based access, so is not automatically enabled.。文件命名与放置位置见下一节的目录结构约定。除 options 文件外classes.xml 里的option key... value.../节点也能注入选项如示例中的usenametablefalse最终两者会汇入DecompilationTestImplementation.createOptionsMap组装成 API 的 options Map。七、期望数据目录结构7.1 Class 文件对 class 文件期望数据与自定义配置放在test-data-expected-output下的同路径位置文件名以 class 文件名为基准。以test-data/classes/subdir/MyClass.class为例可放置test-data-expected-output/classes/subdir/MyClass.expected.java——期望的反编译 Java 输出可含 decompilation note见下节MyClass.options——可选的 options 文件MyClass.expected.summary——期望的 CFR API summary无 summary 时可省略MyClass.expected.exceptions——期望的 CFR API 异常报告无异常时可省略。7.2 JAR 文件对 JAR 文件期望数据放在test-data-expected-output对应路径下、以 JAR 文件名为名的子目录内期望 Java 输出文件的文件名包含包名如mypackage.MyClass.javaoptions 与 summary/exceptions 文件则统一用_作为文件名部分。对 multi-release JAR目录内可含java-version形式的版本子目录存放该版本专属类的期望输出。以test-data/jars/subdir/MyJar.jar为例test-data-expected-output/jars/subdir/MyJar/ ├── mypackage.MyClass.java # mypackage.MyClass 的期望输出 ├── java-11/mypackage.MyClass.java # Java 11 专属类文件的期望输出multi-release JAR ├── _.options # 可选 options 文件 ├── _.expected.summary # 期望 summary可省略 └── _.expected.exceptions # 期望异常可省略这与Driver.doJar中按 class 文件版本分组、处理版本冲突version collisions的逻辑相呼应——CFR 会把 multi-release JAR 里各版本的同名类分别反编译并标注适用版本API 中的DecompiledMultiVer.getRuntimeFrom()即表达从哪个运行时版本起生效。实现侧的文件名扩展名常量定义在 DecompilationTestImplementation.java.expected.java/.expected.summary/.expected.exceptions。以现存样例 TryTest1.10.expected.java 为例可以看到期望输出的真实形态带Decompiled with CFR头注释的完整 Java 类。八、decompilation note 注释在期望输出中记录已知问题期望 Java 输出文件支持两种反编译笔记decompilation note它们在比对实际输出前被剔除可用于标注 CFR 输出中错误或可改进之处行笔记Line以//#开头允许前导空白行内笔记Inline形如/*# ... #*/。README 提醒行笔记要少用尤其在大文件中——因为比对时会整行删除导致 diff 文件的行号偏移阅读起来容易困惑。示例//# Line decompilation note public class MyClass { public static void main(String[] stringArray/*# Inline decompilation note #*/) { ... } }实现对应 DecompilationTestImplementation.java 的stripDecompilationNotes按行扫描trim后以//#开头的行被丢弃其余行用正则/\*#.#\*/删除行内笔记再与实际输出做assertEquals比对。九、批量创建/更新期望数据当一次性加入大量新 class/JAR或某次改动影响大量用例的输出时手工维护期望文件会很痛苦。为此提供两个 JVM 系统属性在跑测试时通过-Dsystem-property传入cfr.decompilation-test.create-expected基于当前 CFR 输出生成所有缺失的期望数据cfr.decompilation-test.update-expected把期望数据更新为CFR 实际产出的数据。注意——若期望 Java 输出中含有 decompilation note更新会因笔记丢失而失败受影响的用例只能手工更新实现中通过originalExpectedCode.equals(expectedCodeWithoutNotes)判断是否含笔记来分流。两个属性共有一个防误用设计源码中以failCreatedMissingExpectedData/failUpdatedExpectedData落地设置了这些系统属性时对应测试仍然会失败但期望数据已被写盘。README 的表述是 to prevent accidentally using them for regular test execution——防止在常规回归中误开开关让更新期望悄悄变成测试通过。十、Issue 提交与贡献边界README 的 Issues 一节只有一条硬规则提交 issue 时不要包含版权材料copyright materials否则会被直接删除。对希望参与开发的人contributing.md 划定了清晰的贡献边界——欢迎提交测试框架改进、测试用例配套 bugfix 必须先有测试证明而不欢迎提交纯输出格式化改动、Java 6 以上语法、外部依赖、反射用法与无实际效果的改动。测试侧还有一个与独立子仓库并列的历史仓库 cfr_tests构建并在多个 Java 版本上比对数百个样例提交前需保证在 java 6、8、13 及硬编码 class 文件上都通过。小结CFR 用 Java 6、零运行时依赖实现了现代 class 文件的反编译org.benf:cfr可从 Maven Central、GitHub Release 或官网获取master 快照走 workflow artifacts命令行核心用法java -jar cfr.jar class路径|FQN、java -jar cfr.jar jar --outputdir dir、--help查看全部选项从源码构建只需mvn compile后运行java org.benf.cfr.reader.Main java.lang.Object验证遇编译失败优先检查JAVA_HOME是否指向过新的 JDK回归测试 独立子仓库的 test-data 本仓库的test-data-expected-output/test-specs由 JUnit 参数化测试驱动 CFR Java API 反编译并与期望文件比对.options文件按每行 key value定制选项期望目录对 className.expected.java等与 JAR目录式 _.optionsjava-version子目录各有约定//#与/*# ... #*/笔记可记录已知输出缺陷-Dcfr.decompilation-test.create-expected与-Dcfr.decompilation-test.update-expected批量生成/刷新期望数据且刷新后测试刻意失败以防误用。赞分享逆向工程开发工具【免费下载链接】cfrThis is the public repository for the CFR Java decompiler项目地址https://gitcode.com/gh_mirrors/cf/cfr点击查看免费下载相关推荐【亲测免费】 CFR强大的Java反编译器CFR强大的Java反编译器 CFR是一个高效的Java反编译器专为处理现代Java特性而设计包括Java 9的大部分功能、Java 12的开关表达式和J逆向工程开发工具CFR Java反编译器终极指南5步掌握高效反编译技术CFR Java反编译器终极指南5步掌握高效反编译技术 CFR Java反编译器是一个功能强大的开源工具专门用于将Java字节码文件反编译回可读的Java源逆向工程开发工具如何快速掌握CFR Java反编译器从安装到高级应用的完整指南如何快速掌握CFR Java反编译器从安装到高级应用的完整指南 CFR Java反编译器是一款功能强大的工具能够将Java字节码文件.class反编译为逆向工程开发工具上一篇5 分钟把只读 NTFS 硬盘变成可读写Free-NTFS-for-Mac 的架构与最短上手路径下一篇Equalizer APO系统级音频均衡器免费为所有程序统一调音创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考