vova7878/DexFile

GitHub: vova7878/DexFile

一个用于读取、生成和修改Android DEX文件的现代Java库,采用类似JDK Class-File API的流式设计。

Stars: 32 | Forks: 4

![Java 17+](https://img.shields.io/badge/Java-17%2B-blue) [![许可证](https://img.shields.io/github/license/vova7878/DexFile)](https://github.com/vova7878/DexFile/blob/main/LICENSE) # 关于 该库专为读取、生成、修改和写入 dex 文件而设计。其灵感主要来源于 JDK 22 中引入的 [Class-File API](https://openjdk.org/jeps/484)。 ### 安装 ``` dependencies { implementation("io.github.vova7878:DexFile:") } ``` ### 支持的 DEX 版本 | 版本 | 支持的 API | 读取 | 写入 | 指令集 | |:---------------|:-------------:|:--------------:|:---------------:|:------------------------------------------------------| | **041** | 35+ | ✅ 1 | ✅ 1 | 标准 2 | | **040** | 30+ | ✅ | ✅ | 标准 + ODEX | | **039** | 28+ | ✅ | ✅ | 标准 + ODEX | | **038** | 26+ | ✅ | ✅ | 标准 + ODEX | | **037** | 26+ | ✅ | ✅ | 标准 + ODEX | | **037** | 24 - 25 | ✅ | ✅ | 标准 + ODEX + ⏳lambda opcodes 3 | | **036** | 16 - 20 | ✅ | ✅ | 标准 + ODEX 4 | | **036** | 14 - 15 | ✅ | ✅ | 标准 + ODEX + expanded jumbo opcodes 5 | | **035** | 1+ | ✅ | ✅ | 标准 + ODEX | | **013** | M5 | ✅ | ⏳ | 早期 Dalvik + ODEX 6 | | **009** | M3 | ✅ | ⏳ | 早期 Dalvik + ODEX 6 | | **cdex (001)** | 28 - 35 | ✅ 7 | ⚠️ 8 | 标准 + ODEX | * 1 支持作为**容器**进行读写,该容器可将多个逻辑 DEX 文件合并为单个物理文件,以节省空间并共享数据(例如字符串)。 * 2 与所有其他 DEX 版本不同,版本 041 不包含任何特殊的 `odex` 指令。这是因为从 API 32 开始,`odex` 指令已从 Android runtime 中完全移除,而 `dex041` 容器格式直到 API 35 才被引入。 * 3 这是针对 Android 7.x 的早期实验性功能,从未发布过稳定版本。但是,存在一些样本,并且 ART 源码中也包含这些 opcodes。未来版本计划支持这些指令。 * 4 在这些 Android 版本中,此格式已回退至 `dex035` 状态,因此本库对其有完整的读写支持。 * 5 该格式从未被 Google 正式定稿,并在稳定版本发布前被废弃。然而它确实完整存在过,并且基于完整的文档和实际样本,本库为其提供了完整的读写支持,包括所有不寻常的指令族(如 expanded jumbo opcodes)。 * 6 历史性的预发布版本(Milestone 3 和 5)。完全支持读取,包括 `odex` 指令。写入功能计划在未来的版本中实现。 * 7 完全支持。特殊之处在于,`cdex` 通常总是被打包在 `vdex` 容器内。 * 8 仅支持作为 `vdex` 外部的单个文件进行写入。通过 API 28–35 中的原生代码使用特殊技术,或者在 API 34 和 35 中通过 `InMemoryClassLoader` 以标准方式(但**不能**通过 `PathClassLoader`),可以加载此类写入的文件。 ### 示例
BrainFuck 到 DEX 的编译器 这是一个将 [BrainFuck](https://en.wikipedia.org/wiki/Brainfuck) 语言编译为 dex 格式的简单编译器。 ``` public static byte[] compile(int tape_length, String bf) { var impl_name = "com.v7878.bf.Main"; var impl_id = TypeId.ofName(impl_name); var system_id = TypeId.of(System.class); var print_stream_id = TypeId.of(PrintStream.class); var system_out_id = FieldId.of(system_id, "out", print_stream_id); var system_write_id = MethodId.of(print_stream_id, "print", TypeId.V, TypeId.C); var input_stream_id = TypeId.of(InputStream.class); var system_in_id = FieldId.of(system_id, "in", input_stream_id); var system_read_id = MethodId.of(input_stream_id, "read", TypeId.I); var write_byte_id = MethodId.of(impl_id, "write", TypeId.V, TypeId.B); var read_byte_id = MethodId.of(impl_id, "read", TypeId.B); var impl_def = ClassBuilder.build(impl_id, cb -> cb .withSuperClass(TypeId.OBJECT) .withFlags(ACC_PUBLIC | ACC_FINAL) .withMethod(mb -> mb .of(write_byte_id) .withFlags(ACC_PRIVATE | ACC_STATIC) .withCode(/* locals */ 1, ib -> { ib.generate_lines(); int field_reg = ib.l(0); ib.sget(field_reg, system_out_id); ib.invoke(VIRTUAL, system_write_id, field_reg, ib.p(0)); ib.return_void(); }) ) .withMethod(mb -> mb .of(read_byte_id) .withFlags(ACC_PRIVATE | ACC_STATIC) .withCode(/* locals */ 2, ib -> { ib.generate_lines(); int field_reg = ib.l(0); ib.sget(field_reg, system_in_id); ib.invoke(VIRTUAL, system_read_id, field_reg); int data_reg = ib.l(1); ib.move_result(data_reg); ib.unop(INT_TO_BYTE, data_reg, data_reg); ib.return_(data_reg); }) ) .withMethod(mb -> mb .withFlags(ACC_PUBLIC | ACC_STATIC) .withName("main") .withReturnType(TypeId.V) .withParameterTypes(TypeId.of(String[].class)) .withCode(/* locals */ 3, ib -> { ib.generate_lines(); int tape_reg = ib.l(0); ib.const_(tape_reg, tape_length); ib.new_array(tape_reg, tape_reg, TypeId.B.array()); ib.local(tape_reg, "tape", TypeId.B.array()); int index_reg = ib.l(1); ib.const_(index_reg, /* start */ 0); ib.local(index_reg, "index", TypeId.I); int tmp_reg = ib.l(2); ib.local(tmp_reg, "tmp", TypeId.I); int depth = 0; var labels = new LinkedList(); for (char op : bf.toCharArray()) { switch (op) { case '>' -> ib.binop_lit(ADD_INT, index_reg, index_reg, 1); case '<' -> ib.binop_lit(ADD_INT, index_reg, index_reg, -1); case '+' -> { ib.aget('B', tmp_reg, tape_reg, index_reg); ib.binop_lit(ADD_INT, tmp_reg, tmp_reg, 1); ib.aput('B', tmp_reg, tape_reg, index_reg); } case '-' -> { ib.aget('B', tmp_reg, tape_reg, index_reg); ib.binop_lit(ADD_INT, tmp_reg, tmp_reg, -1); ib.aput('B', tmp_reg, tape_reg, index_reg); } case '.' -> { ib.aget('B', tmp_reg, tape_reg, index_reg); ib.invoke(STATIC, write_byte_id, tmp_reg); } case ',' -> { ib.invoke(STATIC, read_byte_id); ib.move_result(tmp_reg); ib.aput('B', tmp_reg, tape_reg, index_reg); } case '[' -> { labels.add(depth); int open_depth = depth++; ib.label("label_open_" + open_depth); ib.aget('B', tmp_reg, tape_reg, index_reg); ib.if_testz(EQ, tmp_reg, "label_close_" + open_depth); } case ']' -> { int close_depth = labels.pollLast(); ib.goto_("label_open_" + close_depth); ib.label("label_close_" + close_depth); } default -> { /* nop */ } } } ib.return_void(); }) ) ); return DexIO.write(Dex.of(impl_def)); } ```
标签:Android开发, Dalvik字节码, DEX文件, Java, 代码处理库, 域名枚举