尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

第 18 章:ART 运行时

第 18 章:ART 运行时 Android 运行时(ART)是每一个 Android 应用核心的托管执行环境。它加载、校验并执行 DEX 字节码 —— 即 Java 与 Kotlin 源码编译后的输出产物。ART 在 Android 5.0(Lollipop)版本取代 Dalvik,之后经历了巨大的演进:从简单的解释器 + AOT 模型,演变为一套具备并发垃圾回收、设备端配置文件引导优化、通过 Mainline 项目模块化交付的多层级高级编译引擎。本章完整讲解 Android 托管代码的整个生命周期:从 DEX 文件格式,到 AOT 提前编译与 JIT 即时编译,再到内存管理以及原生代码互操作,全部基于 AOSP 源码展开。18.1 ART 架构18.1.1 目录结构art / 下的 ART 工程主要划分为如下子目录:目录用途runtime/核心运行时:类链接器、GC、JIT、JNI、线程compiler/优化编译器后端(13 个子目录)dex2oat/AOT 编译器驱动(7 个子目录)libdexfile/DEX 文件解析与校验库libartbase/ART 各组件共用的基础工具库libartpalette/平台抽象层libartservice/ART 系统服务(artd)libarttools/命令行工具工具集libprofile/配置文件数据读写libnativebridge/用于指令集转换的 NativeBridge 接口libnativeloader/按应用进行原生库加载libelffile/用于读取 OAT 文件的 ELF 文件解析odrefresh/OTA 升级后设备端刷新工具openjdkjvm/JVM 接口实现openjdkjvmti/用于调试的 JVMTI 代理实现artd/ART 守护进程(dexopt 服务)perfetto_hprof/Perfetto 堆分析器集成模块dalvikvm/dalvikvm 命令行启动器dexdump/DEX 文件转储工具oatdump/OAT 文件转储工具profman/配置文件管理工具sigchainlib/信号链库disassembler/机器码反汇编器tools/各类辅助脚本与工具test/测试基础设施与测试用例benchmark/性能基准测试build/编译系统集成18.1.2 运行时单例Runtime类是核心单例对象,持有全部主要子系统。该类声明于art/runtime/runtime.h,实现代码约 3000 行,位于art/runtime/runtime.cc。// art/runtime/runtime.h, line 130 class Runtime { public: static bool Create(RuntimeArgumentMap runtime_options) SHARED_TRYLOCK_FUNCTION(true, Locks::mutator_lock_); bool Start() UNLOCK_FUNCTION(Locks::mutator_lock_); static Runtime* Current() { return instance_; } ... };Runtime 持有的关键子系统:ClassLinker—— 类加载、链接、校验、符号解析Heap (gc::Heap)—— 托管堆、垃圾回收器Jit (jit::Jit)—— JIT 编译器前端、线程池、性能采样JitCodeCache—— 编译后代码存储,代码垃圾回收JavaVMExt—— JNI JavaVM 实现、原生库管理InternTable—— 字符串驻留表ThreadList—— 线程管理、线程挂起、检查点OatFileManager—— OAT 文件查找与加载MonitorList / MonitorPool—— 对象监视器管理SignalCatcher—— 处理 ANR 转储的 SIGQUIT 信号处理器Instrumentation—— 方法进入 / 退出钩子Trace—— 方法跟踪RuntimeCallbacks—— 插件与代理的扩展点18.1.3 Runtime 启动流程Zygote 进程启动,或者直接调用 dalvikvm 时,运行时会执行如下初始化序列:解析参数—Runtime::ParseOptions()处理命令行参数,例如堆大小、编译器过滤规则、GC 配置。创建运行时实例—Runtime::Create()分配并初始化单例 Runtime 对象。初始化堆—gc::Heap构造函数创建内存空间(区域堆空间、大对象空间、非移动空间),选定垃圾回收器。加载 boot 镜像—ClassLinker::InitFromBootImage()将预编译的 boot 镜像(.art文件)映射到内存,加载java.lang.Object、java.lang.String等基础类。初始化类链接器— 注册 boot 类路径 DEX 文件,创建 DexCache 对象,初始化字符串驻留表。创建 JIT— 如果开启 JIT 编译(应用进程默认开启),Jit::Create()加载编译器共享库,创建 JIT 线程池。启动运行时—Runtime::Start()切换至完整运行状态:启动信号捕获线程、堆任务处理器、终结器守护线程。// art/runtime/runtime.cc, lines 96-97 (include chain) #include "jit/jit.h" #include "jit/jit_code_cache.h"18.1.4 Zygote 与进程模型ART 高度依赖 Zygote 进程模型实现高效进程创建:Zygote 启动:Zygote 进程启动 ART 运行时,加载 boot 镜像,预初始化常用类。fork:需要新建应用进程时,Zygote 执行fork()。子进程会继承:完整 boot 镜像内存映射(共享只读)已加载类及其元数据字符串驻留表Zygote JIT 模式下的 JIT 代码缓存子进程特化:子进程做自身初始化工作:加载应用的 DEX、OAT 文件创建应用专属类加载器根据应用内存等级调整 GC 配置启动 JIT 线程池该模型下 ART 运行时的初始化开销仅在 Zygote 中执行一次,所有应用通过写时复制机制共享 boot 镜像内存页。Zygote 相关核心运行时方法:// art/runtime/runtime.h (selected methods) bool IsZygote() const { return is_zygote_; } bool IsPrimaryZygote() const { return is_primary_zygote_; } bool IsSystemServer() const { return is_system_server_; } void SetAsSystemServer() { is_system_server_ = true; is_zygote_ = false; is_primary_zygote_ = false; }18.1.5 App 镜像除 boot 镜像之外,ART 可以为每个应用生成 App 镜像,保存应用预初始化完成的类状态。使用speed‑profile过滤规则时,dex2oat 会生成 App 镜像,作为.art文件与.oat文件放在一起。App 镜像包含:应用 DEX 文件中经过预初始化的类应用使用的已解析字符串应用 DEX 文件对应的 DexCache 条目应用启动时,运行时将 App 镜像映射进堆内存,省去重复加载、初始化类的开销,大幅降低类数量较多应用的冷启动时延。18.1.6 编译流水线总览ART 采用多层级编译策略:流水线支持多种compiler filters(编译器过滤规则),控制 dex2oat 的工作内容(见 18.3.3 小节)。18.1.7 执行模式ART 对同一个方法提供 4 种执行模式:解释器 (Nterp):手写汇编实现的解释器,逐条执行 DEX 字节码。Nterp 针对不同架构(ARM64、x86‑64、RISC‑V)做优化,同时采集性能采样数据。基线 JIT:轻量非优化 JIT 编译,对达到热度阈值的方法快速生成原生代码。优化 JIT:对热点方法做完整优化编译。AOT(dex2oat):提前编译生成存放在 OAT 文件中的原生代码。art/runtime/compilation_kind.h中定义CompilationKind枚举:// art/runtime/compilation_kind.h, lines 27-32 enum class CompilationKind { kOsr = 0, // On‑Stack Replacement 栈上替换 kFast = 1, // Fast (pattern‑matched) compilation 快速模式匹配编译 kBaseline = 2, // Non‑optimizing baseline 非优化基线编译 kOptimized = 3, // Full optimization 完整优化编译 };18.1.8 Nterp:高速解释器Nterp(Next‑generation TeRP,读作 en‑terp)是 ART 的主力解释器。不同于传统基于 C++ switch 的解释器,Nterp 为每一种支持的架构手写汇编实现。消除 C++ 函数调用开销,实现寄存器到寄存器的 DEX 字节码执行。源码路径:art/runtime/interpreter/mterp/(各架构汇编文件)。Nterp 核心特性:直接线程调度:每条 DEX 指令处理器直接跳转至下一条指令的处理函数,消除 switch 分发开销。寄存器映射:DEX 虚拟寄存器映射到栈上一块连续内存,高频访问寄存器直接使用机器寄存器。内联缓存:Nterp 在虚调用位置记录接收者类型,供 JIT 内联器使用。热度计数:每一次方法调用、回跳分支都会递减热度计数器;计数器归零时通知 JIT。GC 协同:在回跳分支、方法入口处设置安全点检查,支持 GC 挂起线程。Nterp 很好平衡启动时延(无编译耗时)与执行速度,比朴素 C++ 解释器快 2‑5 倍。18.1.9 线程模型ART 通过Thread类(art/runtime/thread.h)和ThreadList类(art/runtime/thread_list.h)管理 Java 线程。每个 Thread 对象包含:线程本地状态 — 当前状态(Runnable、Native、Suspended、WaitingForGc 等)托管栈 — 用于栈回溯的托管帧链表JNI 局部引用 — 线程本地 JNI 引用表TLAB — 线程本地分配缓冲区,用于快速对象分配Mark 栈 — 并发 GC 使用的线程本地标记栈Exception — 当前待抛出异常Class table — 事务模式下快速查找类线程状态控制 GC 协同:只有处于kRunnable状态的线程可以访问托管堆。GC 执行部分操作前,要求所有 mutator 线程到达安全点(退出 kRunnable 状态)。18.1.10 锁基础设施ART 使用一套定义明确的锁层级避免死锁。依靠注解(REQUIRES、REQUIRES_SHARED、GUARDED_BY)与编译期检查强制锁顺序。按获取顺序排列的核心锁:mutator_lock_— 核心读写锁,隔离 mutator 线程(读)与 GC(独占写)。绝大多数运行时操作以共享模式持有该锁。heap_bitmap_lock_— GC 标记、清扫阶段保护 GC 位图。classlinker_classes_lock_— 类加载过程保护类表。dex_lock_— 保护已注册 DEX 文件列表。jni_libraries_lock_— 保护原生库表。thread_list_lock_— 保护全局线程列表。18.1.11 信号链库信号链库(art/sigchainlib/)管理信号处理,让 ART 信号处理器与应用、NDK 信号处理器共存。信号到来时:ART 处理器优先处理。如果 ART 不处理(非托管故障),则传递给下一个处理器。全部处理器都不处理,则执行系统默认行为。该机制对以下场景至关重要:空指针异常检测(SIGSEGV)栈溢出检测(SIGSEGV/SIGBUS)GC 隐式挂起(读屏障页面触发 SIGSEGV)ANR 信息转储(SIGQUIT)18.1.12 异常处理ART 异常处理遵循 Java 异常模型:抛出:执行 throw 指令时,运行时在当前线程设置待处理异常。栈展开:栈遍历器向上遍历调用栈,寻找匹配的 catch 处理器。捕获:找到匹配处理器后,在对应 PC 位置恢复执行;通过move‑exception指令将捕获异常存入专用寄存器。未捕获:找不到处理器时,调用线程的UncaughtExceptionHandler。编译代码依靠栈映射表确定每个 PC 位置有效的异常处理器;解释代码读取 CodeItem 中的 try‑item 表。CommonThrows模块(art/runtime/common_throws.h)提供常用异常的工厂函数:// art/runtime/common_throws.h (selected) void ThrowNullPointerExceptionForFieldAccess(ArtField*, bool is_read); void ThrowNullPointerExceptionForMethodAccess(uint32_t method_idx, InvokeType); void ThrowArrayIndexOutOfBoundsException(int index, int length); void ThrowClassCastException(ObjPtrmirror::Class actual, ObjPtrmirror::Class expected); void ThrowArithmeticExceptionDivideByZero(); void ThrowStackOverflowError(Thread* self); void ThrowNoSuchMethodError(InvokeType, mirror::Class*, std::string_view, Signature); void ThrowNoSuchFieldError(std::string_view scope, mirror::Class*, std::string_view);18.1.13 Monitor 与同步机制Java 的synchronized关键字、Object.wait()/Object.notify()由 monitor 系统实现(art/runtime/monitor.h)。轻量锁 vs 重量锁ART 使用双层锁机制:轻量锁:无竞争场景,锁信息直接存放在对象的锁字(每个对象第一个字)。轻量锁获取仅一次 CAS(比较并交换)操作。锁字布局(32bit) [31:30] 状态 (00 = 未锁定,01 = 轻量锁,10 = 重量锁,11 = 哈希) [29:16] 锁计数(轻量锁递归深度) [15:0] 线程 ID(轻量锁)重量锁:发生竞争(其他线程尝试获取被别的线程持有的轻量锁)时,轻量锁膨胀为重量锁。重量锁使用 Monitor 对象,附带条件变量,支持wait()/notify()。CAS 成功 → 递归锁计数 + 1 → 释放; 检测到竞争 → 锁膨胀; 释放锁:条件允许则降级; Object.wait → 进入等待;notify/notifyAll 唤醒。MonitorPoolMonitorPool(art/runtime/monitor_pool.h)预分配 Monitor 对象,避免锁膨胀时执行对象分配(防止在不合时宜的时机触发 GC)。18.1.14 内置方法 (Intrinsics)ART 识别部分 Java 方法为 intrinsic 内置方法,提供绕过常规调用流程的优化实现。内置方法在各架构代码生成器中实现,对以下场景带来显著性能提升:数学运算 —Math.abs()、Math.min()、Math.max()、Math.sqrt()直接使用硬件指令。字符串操作 —String.charAt()、String.compareTo()、String.equals()使用优化汇编。对象操作 —Object.getClass()直接读取类字段。系统操作 —System.arraycopy()使用优化内存拷贝。Unsafe 操作 —Unsafe.compareAndSwapInt()等使用硬件 CAS 指令。线程操作 —Thread.currentThread()直接读取线程本地存储。是否为内置方法由ArtMethod::access_flags_中的kAccIntrinsic标记位控制;附加 bit 位区分具体是哪一个内置方法。// art/runtime/art_method.h, lines 256-258 bool IsIntrinsic() const { return IsIntrinsic(GetAccessFlags()); }18.1.15 内存对象布局ART 为性能设计了专用对象内存表示。对象布局每个 Java 对象以两个字的对象头开头:锁字 (32bit):monitor 状态、哈希码、GC 转发指针。类指针 (32bit,压缩引用):指向mirror::Class对象。实例字段紧跟对象头,排布顺序尽可能减少内存对齐填充:引用类型字段优先(便于 GC 扫描)64 位字段、32 位字段、16 位字段、8 位字段依次排列数组布局数组在对象头之后多一个字:长度 (32bit):元素数量数组元素紧跟长度字段,按自身类型自然对齐。String 布局java.lang.String对象包含:对象头(锁字 + 类指针)count (32bit) — 字符数量hash (32bit) — 缓存哈希码字符数据(内联存储,在字段之后)String 可以使用 UTF‑16 或者压缩 Latin‑1 编码;编码类型由 count 字段内的标记位标识。18.1.16 核心数据结构ArtMethod每一个 Java/Kotlin 方法在内部由ArtMethod对象表示(定义于art/runtime/art_method.h第 89 行)。ArtMethod 不是 GC 托管对象,分配在 LinearAlloc,由托管层java.lang.reflect.Method对象持有指针。// art/runtime/art_method.h, lines 89-99 class EXPORT ArtMethod final { public: DECLARE_RUNTIME_DEBUG_FLAG(kCheckDeclaringClassState); static constexpr uint32_t kRuntimeMethodDexMethodIndex = 0xFFFFFFFF; ArtMethod() : access_flags_(0), dex_method_index_(0), method_index_(0), hotness_count_(0) { } ... };关键字段:declaring_class_— GC 根,指向所属mirror::Classaccess_flags_— 方法修饰符与运行时标记(原子访问,支持并发)dex_method_index_— DEX 文件 method_id 表索引method_index_— vtable 或者 iftable 索引hotness_count_— JIT 性能采样计数器ptr_sized_fields_.data_— 指向 DEX CodeItem(解释模式)或者性能采样信息ptr_sized_fields_.entry_point_from_quick_compiled_code_— 函数指针,指向编译代码或者跳板函数入口点字段决定 ART 选用哪一种执行模式:可以指向解释器桥接函数、JIT 编译代码、AOT 编译代码,或者未解析方法的跳板。ArtMethod 提供大量查询接口读取方法属性:// art/runtime/art_method.h, lines 168-234 (selected) bool IsPublic() const { return (GetAccessFlags() kAccPublic) != 0; } bool IsPrivate() const { return (GetAccessFlags() kAccPrivate) != 0; } bool IsStatic() const { return (GetAccessFlags() kAccStatic) != 0; } bool IsConstructor() const { return (GetAccessFlags() kAccConstructor) != 0; } bool IsDirect() const { constexpr uint32_t direct = kAccStatic | kAccPrivate | kAccConstructor; return (GetAccessFlags() direct) != 0; } bool IsSynchronized() const { constexpr uint32_t synchonized = kAccSynchronized | kAccDeclaredSynchronized; return (GetAccessFlags() synchonized) != 0; } bool IsFinal() const { return (GetAccessFlags() kAccFinal) != 0; } bool IsIntrinsic() const { return (GetAccessFlags() kAccIntrinsic) != 0; }直接方法与虚方法的区分对方法分发至关重要:直接方法(static、private、构造器):直接调用实现,不需要查虚函数表。虚方法(其余全部):通过 vtable 或者接口方法表 IMT 分发。ArtMethod 入口点 ¶mirror::Class已加载类的托管对象表示。包含 vtable、iftable 接口表、静态字段数组、实例字段数组、方法数组、类状态(已校验、已初始化等)。作为托管对象,mirror::Class实例存放在 GC 堆,会被 GC 移动与回收。mirror::Class主要组成:vtable—ArtMethod*数组,用于虚方法分发iftable— 接口表,映射接口到对应方法数组imt— 接口方法表,基于哈希的快速分发sfields— 静态字段数组ifields— 实例字段数组class_size— 该类实例对象大小status— 类当前状态(已加载、已校验、已初始化等)class_loader— 加载该类的 ClassLoader 引用class_flags— uint32_t 位掩码,供 GC、运行时快速分支判断,无需完整解析类。普通类标记为kClassFlagNormal;其余 bit 标记字符串、数组、引用类、类加载器、dex cache;Android17 新增 record、value 类标记。完整布局与 Android17 对 record 类的修改见 18.11 小节。DexCache每个 DEX 文件专属缓存,存放解析完成的字符串、类型、方法、字段。避免重复解析 DEX 文件,加速重复查找。DexCache 由 ClassLinker 打开 DEX 文件时创建,作为托管mirror::DexCache对象存放在堆中。包含:已解析字符串数组 — StringIndex 映射到mirror::String*已解析类型数组 — TypeIndex 映射到mirror::Class*已解析方法数组 — 方法索引映射到ArtMethod*已解析字段数组 — 字段索引映射到ArtField*未解析项存 null(GcRootnullptr),第一次访问时触发解析。InternTable驻留表保存java.lang.String的标准实例。调用String.intern()时,如果驻留表已有该字符串,直接返回;否则插入并返回该实例。boot 镜像预先填充高频字符串。LinearAlloc指针步进式分配器,用于运行时元数据;元数据不会单独释放,仅在类加载器卸载时整体释放。用于:ArtMethod 数组ArtField 数组类表IMT 冲突表DexCache 底层存储18.2 DEX 文件格式DEX(Dalvik Executable)是 ART 使用的字节码容器。不同于 Java.class一个文件对应一个类,单个.dex将编译单元全部类打包为一份去重结构,针对内存映射访问做优化。源码:art/libdexfile/(DEX 解析校验库)。18.2.1 文件头结构DEX 头定义在art/libdexfile/dex/dex_file.h:// art/libdexfile/dex/dex_file.h, lines 134-179 struct Header { Magic magic_ = {}; // "dex\n035\0" or similar uint32_t checksum_ = 0; // adler32 of everything except magic and this field Sha1 signature_ = {}; // SHA‑1 hash of the rest of the file uint32_t file_size_ = 0; // size of entire file uint32_t header_size_ = 0; // offset to start of next section uint32_t endian_tag_ = 0; // 0x12345678 uint32_t link_size_ = 0; // unused uint32_t link_off_ = 0; // unused uint32_t map_off_ = 0; // offset to map list uint32_t string_ids_size_ = 0; // number of StringIds uint32_t string_ids_off_ = 0; // file offset of StringIds array uint32_t type_ids_size_ = 0; // number of TypeIds uint32_t type_ids_off_ = 0; // file offset of TypeIds array uint32_t proto_ids_size_ = 0; // number of ProtoIds uint32_t proto_ids_off_ = 0; // file offset of ProtoIds array uint32_t field_ids_size_ = 0; // number of FieldIds uint32_t field_ids_off_ = 0; // file offset of FieldIds array uint32_t method_ids_size_ = 0; // number of MethodIds uint32_t method_ids_off_ = 0; // file offset of MethodIds array uint32_t class_defs_size_ = 0; // number of ClassDefs uint32_t class_defs_off_ = 0; // file offset of ClassDef array uint32_t data_size_ = 0; // size of data section uint32_t data_off_ = 0; // file offset of data section };DEX 版本 41(魔数dex\n041\0)引入容器支持:多个逻辑 DEX 拼接成一块连续数据块;每个逻辑 DEX 增加两个头字段指向外层容器。容器版本常量:DexFile::kDexContainerVersion = 41(art/libdexfile/dex/dex_file.h第 112 行);扩展头HeaderV41:// art/libdexfile/dex/dex_file.h, lines 181-184 struct HeaderV41 : public Header { uint32_t container_size_ = 0; // total size of all dex files in the container uint32_t header_offset_ = 0; // offset of this dex's header in the container };DexFile::HasDexContainer()在 V41 及以上版本返回 true(容器内仅单个 DEX 也返回 true);GetDexContainerRange()从文件起始位置回退header_offset_字节,读取container_size_得到完整容器范围(art/libdexfile/dex/dex_file.h291‑303 行)。加载器以此做多条目处理:art/libdexfile/dex/dex_file_loader.h第 113 行,版本大于等于kDexContainerVersion的非主 DEX 作为容器条目打开;读取完毕后断言IsDexContainerLastEntry()(213 行)。Android17 两处加固修改:校验器拒绝声明的container_size_大于实际映射大小的头文件([V41] Check that the claimed size is LE than the actual size);加载器忽略容器尾部多余字节,要求普通 dex 文件只能包含一个容器。V41 与 profile、dex2oat 的交互见 18.11(Android17 变更)。18.2.2 文件布局18.2.3 索引表DEX 使用索引表做数据去重。每张表是定长结构体数组,文件各处使用索引引用表项。StringId(art/libdexfile/dex/dex_file_structs.h第 52 行)struct StringId { uint32_t string_data_off_; // offset to MUTF‑8 string data };TypeId(第 60 行)struct TypeId { dex::StringIndex descriptor_idx_; // index into string_ids };FieldId(第 68 行)struct FieldId { dex::TypeIndex class_idx_; // defining class dex::TypeIndex type_idx_; // field type dex::StringIndex name_idx_; // field name };MethodId(第 89 行)struct MethodId { dex::TypeIndex class_idx_; // defining class dex::ProtoIndex proto_idx_; // method prototype (return type + parameters) dex::StringIndex name_idx_; // method name };ProtoId(第 78 行)struct ProtoId { dex::StringIndex shorty_idx_; // shorty descriptor (e.g., "VIL") dex::TypeIndex return_type_idx_; // return type uint16_t pad_; // alignment padding uint32_t parameters_off_; // offset to TypeList for parameters };18.2.4 ClassDef 与类数据ClassDef结构体(art/libdexfile/dex/dex_file_structs.h第 108 行)定义一个类:struct ClassDef { dex::TypeIndex class_idx_; // this class's type uint16_t pad1_; uint32_t access_flags_; // ACC_PUBLIC, ACC_FINAL, etc. dex::TypeIndex superclass_idx_; // superclass type uint16_t pad2_; uint32_t interfaces_off_; // offset to TypeList of interfaces dex::StringIndex source_file_idx_; // source file name uint32_t annotations_off_; // annotations directory uint32_t class_data_off_; // offset to class_data_item uint32_t static_values_off_; // initial static field values };class_data_off_指向class_data_item,使用 LEB128 编码存储:静态字段数量实例字段数量直接方法(static、private、构造器)数量虚方法数量每个字段:字段索引差值、访问标记每个方法:方法索引差值、访问标记、code 偏移18.2.5 CodeItem — 方法字节码CodeItem 存放方法真正的 DEX 字节码。标准 DEX 格式包含:registers_size— 使用寄存器总数ins_size— 传入参数寄存器数量outs_size— 调用其他方法使用的输出寄存器数量tries_size— try/catch 块数量debug_info_off— 调试信息偏移insns_size— 16 位指令单元数量insns[]— 字节码指令数组try 项与 catch 处理器数据(tries_size0 时存在)18.2.6 Map Item 类型DEX 文件末尾 map list 是带类型的目录表。map item 类型码定义于art/libdexfile/dex/dex_file.h190‑211 行:enum MapItemType : uint16_t { kDexTypeHeaderItem = 0x0000, kDexTypeStringIdItem = 0x0001, kDexTypeTypeIdItem = 0x0002, kDexTypeProtoIdItem = 0x0003, kDexTypeFieldIdItem = 0x0004, kDexTypeMethodIdItem = 0x0005, kDexTypeClassDefItem = 0x0006, kDexTypeCallSiteIdItem = 0x0007, kDexTypeMethodHandleItem = 0x0008, kDexTypeMapList = 0x1000, kDexTypeTypeList = 0x1001, kDexTypeAnnotationSetRefList = 0x1002, kDexTypeAnnotationSetItem = 0x1003, kDexTypeClassDataItem = 0x2000, kDexTypeCodeItem = 0x2001, kDexTypeStringDataItem = 0x2002, kDexTypeDebugInfoItem = 0x2003, kDexTypeAnnotationItem = 0x2004, kDexTypeEncodedArrayItem = 0x2005, kDexTypeAnnotationsDirectoryItem = 0x2006, kDexTypeHiddenapiClassData = 0xF000, };18.2.7 类型描述符DEX 类型描述符遵循 JNI 规范:描述符类型VvoidZbooleanBbyteSshortCcharIintJlongFfloatDdoubleLjava/lang/String;对象引用[Iint 数组[[Ljava/lang/Object;二维 Object 数组18.2.8 Hidden API 数据Android P 开始,DEX 格式增加HiddenapiClassData段(art/libdexfile/dex/dex_file_structs.h274 行),为每个字段、方法打上隐藏 API 限制标记。实现灰名单、黑名单管控,阻止应用访问平台内部 API。18.2.9 MethodHandle 与 CallSite 项DEX38 版本及以上支持invokedynamic,依靠:MethodHandleItem(179 行):静态 / 实例 getter、setter、方法调用器的引用CallSiteIdItem(189 行):动态调用的引导方法引用对 lambda 与其他基于 invokedynamic 的特性是必需的。18.2.10 DEX 字节码指令DEX 字节码使用 16bit 指令单元。一条指令占用 1‑5 个 16bit 字;第一个字低 8bit 为操作码。操作码空间约 256 条指令,分类如下。Move 指令00: nop 01: move vA, vB (4‑bit registers) 02: move/from16 vAA, vBBBB (8‑bit and 16‑bit registers) 03: move/16 vAAAA, vBBBB (16‑bit registers) 04: move‑wide vA, vB (wide move, 64‑bit) 07: move‑object vA, vB (object reference move) 0a: move‑result vAA (captures return value) 0b: move‑result‑wide vAA 0c: move‑result‑object vAA 0d: move‑exception vAA (captures exception in catch)Return 指令0e: return‑void 0f: return vAA 10: return‑wide vAA 11: return‑object vAAConst 常量指令12: const/4 vA, #+B (4‑bit constant) 13: const/16 vAA, #+BBBB (16‑bit constant) 14: const vAA, #+BBBBBBBB (32‑bit constant) 15: const/high16 vAA, #+BBBB0000 18: const‑wide vAA, #+BBBBBBBBBBBBBBBB (64‑bit constant) 1a: const‑string vAA, string@BBBB 1c: const‑class vAA, type@BBBB实例与静态字段操作52‑58: iget, iget‑wide, iget‑object, iget‑boolean, ... 59‑5f: iput, iput‑wide, iput‑object, iput‑boolean, ... 60‑66: sget, sget‑wide, sget‑object, sget‑boolean, ... 67‑6d: sput, sput‑wide, sput‑object, sput‑boolean, ...调用指令6e: invoke‑virtual {vC..vG}, method@BBBB 6f: invoke‑super {vC..vG}, method@BBBB 70: invoke‑direct {vC..vG}, method@BBBB 71: invoke‑static {vC..vG}, method@BBBB 72: invoke‑interface {vC..vG}, method@BBBB 74: invoke‑virtual/range {vCCCC..vNNNN}, method@BBBB算术逻辑指令90‑af: add, sub, mul, div, rem, and, or, xor, shl, shr, ushr (for int, long, float, double variants) b0‑cf: add/2addr, sub/2addr, mul/2addr, ... (2‑address form: vA = vA op vB) d0‑d7: add‑int/lit16, rsub‑int, mul‑int/lit16, ... (immediate operand forms)比较与分支2d‑31: cmpl‑float, cmpg‑float, cmpl‑double, cmpg‑double, cmp‑long 32‑37: if‑eq, if‑ne, if‑lt, if‑ge, if‑gt, if‑le 38‑3d: if‑eqz, if‑nez, if‑ltz, if‑gez, if‑gtz, if‑lez 28‑2a: goto, goto/16, goto/32 2b: packed‑switch 2c: sparse‑switch数组操作44‑4a: aget, aget‑wide, aget‑object, aget‑boolean, ... 4b‑51: aput, aput‑wide, aput‑object, aput‑boolean, ... 21: array‑length vA, vB 23: new‑array vA, vB, type@CCCC 24: filled‑new‑array {vC..vG}, type@BBBB对象操作1f: check‑cast vAA, type@BBBB 20: instance‑of vA, vB, type@CCCC 22: new‑instance vAA, type@BBBB 27: throw vAA所有指令操作 DEX 虚拟寄存器(编号 0~registers_size‑1)。最后的ins_size个寄存器是方法传入参数。18.2.11 MUTF‑8 字符串编码DEX 文件使用 Modified UTF‑8(MUTF‑8),和标准 UTF‑8 两点区别:空字符 U+0000 编码为双字节0xC0 0x80,而不是单字节 0,兼容 C 语言以 0 结尾字符串。大于等于 U+10000 的增补字符,编码为两组三字节 UTF‑16 代理对,而不是四字节 UTF‑8。DEX 字符串数据头部是 ULEB128 编码的长度(UTF‑16 码元数量,不是字节数),末尾跟一个 0 结束符。18.2.12 LEB128 编码DEX 大量使用 LEB128 小端基 128 变长整数,节省小数值存储空间:ULEB128 无符号:每个字节 7bit 数据 + 1bit 延续标记;0‑127 占 1 字节,128‑16383 占 2 字节。SLEB128 有符号:类似,做符号扩展。ULEB128p1:对 value+1 做 ULEB128 编码,用于大量 0 值场景。LEB128 使用场景:class_data_item(字段、方法计数与差值)调试信息注解编码Hidden API 标记18.2.13 注解DEX 支持 Java 注解,三种可见等级(art/libdexfile/dex/dex_file.h231‑235 行):enum class DexVisibility : uint8_t { kBuild = 0x00, // visible at build time only kRuntime = 0x01, // visible at runtime via reflection kSystem = 0x02, // visible to the runtime system only };注解数据组织:AnnotationsDirectoryItem:每个类的注解总目录struct AnnotationsDirectoryItem { uint32_t class_annotations_off_; uint32_t fields_size_; uint32_t methods_size_; uint32_t parameters_size_; };AnnotationSetItem:同一个目标(类、字段、方法、参数)的一组注解AnnotationItem:单条注解,包含可见标记与编码后的注解数据注解数据使用带类型标记编码:// art/libdexfile/dex/dex_file.h, lines 237‑255 kDexAnnotationByte = 0x00, kDexAnnotationShort = 0x02, kDexAnnotationChar = 0x03, kDexAnnotationInt = 0x04, kDexAnnotationLong = 0x06, kDexAnnotationFloat = 0x10, kDexAnnotationDouble = 0x11, kDexAnnotationString = 0x17, kDexAnnotationType = 0x18, kDexAnnotationField = 0x19, kDexAnnotationMethod = 0x1a, kDexAnnotationEnum = 0x1b, kDexAnnotationArray = 0x1c, kDexAnnotationAnnotation = 0x1d, kDexAnnotationNull = 0x1e, kDexAnnotationBoolean = 0x1f,18.2.14 调试信息DEX 调试信息段保存源码调试相关内容:行号表:字节码偏移映射源码行号;使用类字
返回列表