ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Bazel 仓库中的 ProGuard:收缩、混淆与预校验技术全解析

Bazel 仓库中的 ProGuard:收缩、混淆与预校验技术全解析 Bazel 仓库中的 ProGuard收缩、混淆与预校验技术全解析【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazelProGuard 是广泛使用的 Java 字节码处理工具能在不改变程序功能的前提下移除未使用代码收缩、将类与成员名替换为无意义序列混淆、并为新旧虚拟机补充预校验信息。本文以当前仓库 third_party/java/proguard/proguard6.2.2 内置的官方 FAQ 为主体结合 third_party/java/proguard/BUILD.tools、tools/jdk/proguard_allowlister.py 等仓库源码系统讲解这三类核心能力、可执行的优化清单、构建工具集成方式以及 ProGuard 在 Bazel 生态中的实际接入与配置校验机制帮助读者在 Java/Android 构建链中正确使用 ProGuard。三个核心概念收缩、混淆与预校验什么是收缩ShrinkingJava 源码.java 文件通常被编译为字节码.class 文件。字节码虽然比源码紧凑但仍可能包含大量未使用的代码尤其是当程序中引入了完整的三方库时。收缩类工具如ProGuard通过分析字节码移除未使用的类、字段和方法同时保持程序功能等价——包括异常堆栈中呈现的信息也不受影响。从源码结构看ProGuard 6.2.2 的实现位于 proguard6.2.2/src其收缩逻辑基于全程序的引用分析只有不可达的类、字段、方法才会被裁剪。什么是混淆Obfuscation默认情况下编译后的字节码仍携带大量调试信息源文件名、行号、字段名、方法名、参数名、变量名等。这些信息使得反编译字节码、逆向整个程序变得非常直接。混淆器如ProGuard可以移除这些调试信息并将所有名称替换为无意义的字符序列大幅提高逆向工程的难度顺带让代码体积进一步缩小。混淆后的程序仍保持功能等价唯一的差异是异常堆栈中的类名、方法名和行号发生了变化——这正是需要配合映射文件mapping file与 ReTrace 工具还原堆栈的原因详见下文。什么是预校验Preverification类加载器在加载 class 文件时会对字节码进行复杂的校验分析确保代码无法意外或故意突破虚拟机的沙箱。Java Micro EditionJME与 Java 6 引入了分离校验split verification机制JME 预校验器与 Java 6 编译器会向 class 文件中写入预校验信息分别为 StackMap 与 StackMapTable 属性从而简化类加载器真正的校验步骤使类文件可以被更快、更省内存地加载。ProGuard 会自动对其处理的代码执行预校验因此在面向 JME 或 Java 6 目标时无需再单独运行外部预校验器。ProGuard 支持的优化类型除在收缩阶段移除未使用的类、字段和方法外ProGuard还在字节码层面执行方法内与方法间的优化。借助控制流分析、数据流分析、部分求值、静态单赋值SSA、全局值编号和活跃性分析等技术它可以对常量表达式进行求值移除多余的字段访问与方法调用移除多余的分支移除多余的比较与instanceof测试移除未使用的代码块合并相同的代码块减少变量分配移除只写字段与未使用的方法参数内联常量字段、方法参数与返回值内联较短或仅被调用一次的方法简化尾递归调用合并类与接口尽可能将方法改为 private、static、final尽可能将类改为 static、final用唯一实现类替换单实现的接口执行 200 余种 peephole 优化例如把The answer is 42替换为The answer is 42可选地移除日志代码。需要说明的是这些优化的实际收益取决于你的代码以及代码运行所在的虚拟机。简单的虚拟机通常比带有成熟 JIT 编译器的高级虚拟机受益更多但无论如何字节码体积至少会有所减小。法律与运行环境问题能否用 ProGuard 处理商业应用可以。ProGuard本身以 GPL 协议发布仓库中 BUILD 第 1 行即标注licenses([restricted]) # GNU GPL v2但 GPL 并不影响你所处理的程序你的代码仍然属于你其许可证可以保持不变。支持哪些 JDK 版本ProGuard支持从 JDK 1.1 到 JDK 12 的所有版本并正确处理各版本引入的类文件格式差异Java 2类文件格式的小改动Java 5新增泛型与注解属性Java 6引入可选的预校验属性Java 7预校验变为强制并支持动态语言Java 8新增更多属性与默认方法Java 9新增模块支持Java 11新增动态常量dynamic constants与基于嵌套nest-based的访问控制。是否支持 Java Micro EditionJME支持。ProGuard自身运行在 Java SE 上但你可以自由指定程序的目标运行时环境包括 JME。此时 ProGuard 会执行 JME 所需的预校验产出比传统外部预校验器更紧凑的结果它还附带 JME Wireless Toolkit 的混淆器插件。是否支持 Android支持。Google 的 dx 与 D8 编译器会把 Java 字节码转换为运行在 Android 设备上的 Dalvik 字节码。通过预处理原始字节码ProGuard可以显著减小文件体积并提升代码的运行时性能因此它被分发为 Android SDK 的一部分。此外还有针对 Android 的闭源姊妹产品DexGuard提供额外优化与更强的应用保护详见下文对比。构建工具集成Ant、Gradle、Maven 与 GUIFAQ 明确指出ProGuard提供一等公民的构建集成能力Ant提供 Ant task可无缝融入 Ant 构建流程。既可使用 ProGuard 自身可读的配置格式也可使用等价的 XML 配置Gradle提供 Gradle task配置既可用 ProGuard 自有格式书写也可内嵌于 Groovy 配置中MavenProGuard 的 jar 以构件形式发布在 Maven Central并有若干第三方插件如 android-maven-plugin、IDFC Maven ProGuard Plug-in提供支持GUI除了可作为命令行工具轻松集成进任何自动化构建流程外ProGuard 还提供图形界面简化配置的创建、加载、编辑、执行与保存。在 Bazel 仓库中ProGuard 的接入正是通过 Java 二进制目标完成的BUILD.tools 用java_import引入proguard6.2.2/lib/proguard.jar再以main_class proguard.ProGuard定义java_binary目标供整个构建系统按标签引用。反射、资源与混淆的边界如何处理Class.forName调用ProGuard自动处理Class.forName(SomeClass)与SomeClass.class这类构造被引用的类会在收缩阶段被保留字符串参数会在混淆阶段被正确替换。但对于变量形式的字符串参数通常无法确定其可能取值例如可能来自配置文件。不过 ProGuard 会提示类似(SomeClass)Class.forName(variable).newInstance()的构造暗示类或接口SomeClass及其实现可能需要保留开发者可据此调整配置。官方示例配置 examples/standalone/library.pro 末尾也以注释形式给出示例# -keep public class com.example.MyClass # -keep public interface com.example.MyInterface # -keep public class * implements com.example.MyInterface如何处理资源文件ProGuard会复制所有非 class 资源文件并可选地根据已应用的混淆结果调整它们的名称与内容例如-adaptresourcefilenames、-adaptresourcefilecontents等选项。是否加密字符串常量不。程序代码中的字符串加密按定义必须可完美逆反因此它只能提高混淆等级却会增加代码体积。出于用户需求ProGuard 面向 Android 的闭源姊妹产品DexGuard才提供字符串加密以及更多针对静态与动态分析的防护技术。是否执行控制流混淆不。控制流混淆会向字节码注入额外分支以欺骗反编译器ProGuard不做此事其优化技术中略有涉及。DexGuard则提供控制流混淆作为加固 Android 应用的众多技术之一。增量混淆与自定义字典增量混淆支持。可以在新的混淆步骤中指定上一次的混淆映射文件mapping file从而为已混淆的代码生成插件add-ons或补丁patches。保留关键字字典支持。可以指定自定义混淆字典例如保留关键字列表、含外来字符的标识符、随机源文件甚至莎士比亚文本。但 FAQ 明确指出这几乎不会提高混淆强度——优秀的反编译器会自动替换保留关键字且通过用更简单的名称再次混淆即可轻易还原效果。堆栈追踪还原ReTrace 工具ProGuard附带配套工具ReTrace可以反混淆混淆应用产生的堆栈追踪。重建基于 ProGuard 写出的映射文件mapping file进行。如果行号已被混淆移除ReTrace 会对每个存在歧义反向映射的混淆方法名列出候选的替代方法名列表。详细用法见 ProGuard 用户手册。DexGuard 与 ProGuard 的差异DexGuard是ProGuard的商业扩展DexGuard专为 Android 应用与库设计它不仅优化和混淆字节码还处理 manifest 文件、资源、资源文件、assets 与原生库DexGuard专注于让应用对逆向工程与篡改具备自我防御能力其混淆、加密与检测技术远强于 ProGuard 的基础名称混淆DexGuard向后兼容 ProGuard读取相同的配置并已为 Android 运行时与常见 Android 库提供调优配置。ProGuard 在 Bazel 构建链中的实际落地除官方 FAQ 描述的能力外本仓库还展示了 ProGuard 在真实大型构建系统中的接入细节。1. 二进制目标封装third_party/java/proguard/BUILD.tools 将proguard6.2.2/lib/proguard.jar封装为java_binary入口类proguard.ProGuard而 third_party/java/proguard/BUILD 通过filegroup与release_archive把proguard.jar与 GPL 许可证文档打进发布归档供//src使用。2. 可插拔的 ProGuard 版本src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java 定义了--proguard_top选项用于指定构建 Java 二进制文件时使用哪个版本的 ProGuard 做代码移除JavaConfiguration.java 中通过getProguard()返回该标签。这意味着 Bazel 用户可以把 ProGuard 替换为任意兼容实现而无需改动构建规则。3. 库级配置白名单校验tools/jdk/proguard_allowlister.py 实现了ProguardConfigValidator它规定了库library级 ProGuard 配置只允许使用-keep、-assumenosideeffects、-assumevalues、-adaptresourcefilecontents、-if以及带参数的-dontnote/-dontwarn。校验逻辑会剥离注释后按-前缀切分参数一旦发现库依赖试图通过配置引入全局性副作用例如禁用混淆就会抛出Invalid library proguard config parameters运行时错误并在输出配置头部写入# Merged from path标记。其配套测试见 tools/jdk/proguard_allowlister_test.py 与测试输入 proguard_allowlister_test_input.pgcfg。4. 标准配置模板examples/standalone/library.pro 展示了处理仍需作为库使用的程序库的完整配置可直接作为基线-verbose -injars in.jar -outjars out.jar # Java 9 之后运行时类打包为模块化 jmod 文件 -libraryjars java.home/jmods/java.base.jmod(!**.jar;!module-info.class) # 保存混淆映射便于事后反混淆堆栈 -printmapping out.map -keepparameternames -renamesourcefileattribute SourceFile -keepattributes Exceptions,InnerClasses,Signature,Deprecated, SourceFile,LineNumberTable,EnclosingMethod -keepattributes *Annotation* -keep public class * { public protected *; } -keepclasseswithmembernames,includedescriptorclasses class * { native methods; } -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); }该配置依次处理运行时库引用-libraryjars、堆栈可读性-printmapping/-keepattributes、公开 API 保留-keep public class *、native 方法与枚举方法保留以及 Java 序列化所需成员保留。仓库 examples/standalone 下还提供了applications.pro、android.pro、midlets.pro、proguard.pro、retrace.pro等针对不同场景的模板。小结ProGuard 的核心价值在于以功能等价为前提的字节码瘦身、加固与加速收缩移除死代码、混淆提高逆向成本、预校验加速类加载。围绕这三个能力它同时提供了跨 JDK/JME/Android 的运行时支持、Ant/Gradle/Maven/GUI 的集成形态以及Class.forName、资源适配、增量混淆、ReTrace 堆栈还原等精细化控制手段。在 Bazel 这类大规模构建系统中ProGuard 被封装为可插拔的java_binary并通过--proguard_top实现版本替换、通过 proguard_allowlister.py 约束库级配置的作用范围从而在获得混淆收益的同时保证构建的确定性与安全性。【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表