HarmonyOS NEXT - 方舟字节码文件格式(6-21)开发者大会更新

方舟字节码文件是ArkTS/TS/JS编译后的二进制产物。本文详细介绍了方舟字节码文件的格式,旨在帮助开发者深入了解构成字节码的各个部分,从而指导开发者进行字节码的分析和修改工作。

约束

本文仅适用于版本号为11.0.2.0的方舟字节码(版本号为方舟编译器内部保留字段,开发者无需关注)。

字节码文件数据类型

整型

名称说明
uint8_t8-bit无符号整数。
uint16_t16-bit无符号整数,采用小端字节序。
uint32_t32-bit无符号整数,采用小端字节序。
uleb128leb128编码的无符号整数。
sleb128leb128编码的有符号整数。

字符串

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
utf16_lengthuleb128值为len << 1 | is_ascii,其中len是字符串在UTF-16编码中的大小,is_ascii标记该字符串是否仅包含ASCII字符,可能的值是0或1。
datauint8_t[]以'\0'结尾的MUTF-8编码字符序列。

TaggedValue

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
taguint8_t表示数据种类的标记。
datauint8_t[]根据不同的标记,data是不同类型的数据或者为空。

TypeDescriptor

TypeDescriptor是类(Class) 名称的格式,由'L'、'_'、ClassName和';'组成:L_ClassName;。其中,ClassName是类的全名,名字中的'.'会被替换为'/'。

字节码文件布局

字节码文件起始于Header结构。文件中的所有结构均可以从Header出发,直接或间接地访问到。字节码文件中结构的引用方式包括偏移量和索引。偏移量是一个32位长度的值,表示当前结构的起始位置在字节码文件中相对于文件头的距离,从0开始计算。索引是一个16位长度的值,表示当前结构在索引区域中的位置,此机制将在IndexSection章节描述。

字节码文件中所有的多字节值均采用小端字节序。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
magicuint8_t[8]文件头魔数,值必须是'P' 'A' 'N' 'D' 'A' '\0' '\0' '\0'。
checksumuint32_t字节码文件除文件头魔数和本校验字段之外的内容的adler32校验和。
versionuint8_t[4]字节码文件的版本号 (Version) 。
file_sizeuint32_t字节码文件的大小,以字节为单位。
foreign_offuint32_t一个偏移量,指向外部区域。外部区域中仅包含类型为ForeignClassForeignMethod的元素。foreign_off指向该区域的第一个元素。
foreign_sizeuint32_t外部区域的大小,以字节为单位。
num_classesuint32_tClassIndex结构中元素的数量,即文件中定义的Class的数量。
class_idx_offuint32_t一个偏移量,指向ClassIndex
num_lnpsuint32_tLineNumberProgramIndex结构中元素的数量,即文件中定义的Line number program的数量。
lnp_idx_offuint32_t一个偏移量,指向LineNumberProgramIndex
reserveduint32_t方舟字节码文件内部使用的保留字段。
reserveduint32_t方舟字节码文件内部使用的保留字段。
num_index_regionsuint32_tIndexSection结构中元素的数量,即文件中IndexHeader的数量。
index_section_offuint32_t一个偏移量,指向IndexSection

Version

字节码版本号由4个部分组成,格式为:主版本号.次版本号.特性版本号.编译版本号。

名称格式说明
主版本号uint8_t标识整体架构调整引入的字节码文件格式变更。
次版本号uint8_t标识局部架构调整或者重大特性调整引入的字节码文件格式变更。
特性版本号uint8_t标识中小特性引入的字节码文件格式变更。
编译版本号uint8_t标识缺陷修复引入的字节码文件格式变更。

ForeignClass

描述字节码文件中的外部类。外部类在其他文件中声明,并在当前字节码文件中被引用。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
nameString外部类的名称,命名遵循TypeDescriptor语法。

ForeignMethod

描述字节码文件中的外部方法。外部方法在其他文件中声明,并在当前字节码文件中被引用。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
class_idxuint16_t一个指向该方法所从属的类的索引,指向一个在ClassRegionIndex中的位置,该位置的值是一个指向ClassForeignClass的偏移量。
reserveduint16_t方舟字节码文件内部使用的保留字段。
name_offuint32_t一个偏移量,指向字符串,表示方法名称。
index_datauleb128方法的MethodIndexData数据。

注意:

通过ForeignMethod的偏移量,可以找到适当的IndexHeader以解析class_idx。

ClassIndex

ClassIndex结构的作用是通过名称快速地定位到Class的定义。

  • 对齐方式:4个字节
  • 格式:
名称格式说明
offsetsuint32_t[]一个数组,数组中每个元素的值是一个指向Class的偏移量。数组中的元素根据类的名称进行排序,名称遵循TypeDescriptor语法。数组长度由Header中的num_classes指定。

Class

在字节码文件中,一个类可以表示方舟字节码的一个源代码文件或者一种内置的Annotation。当表示源代码文件时,类的方法对应源代码文件中的函数,类的字段对应源文件中的内部信息;当表示内置的Annotation时,类中不包含字段与方法。源代码文件中的一个类在字节码文件中表示为一个对应其构造函数的方法。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
nameStringClass的名称,命名遵循TypeDescriptor语法。
reserveduint32_t方舟字节码文件内部使用的保留字段。
access_flagsuleb128Class的访问标志,是ClassAccessFlag的组合。
num_fieldsuleb128Class的字段的数量。
num_methodsuleb128Class的方法的数量。
class_dataTaggedValue[]不定长度的数组,数组中每个元素都是TaggedValue类型,元素的标记是ClassTag类型,数组中的元素按照标记递增排序(0x00标记除外)。
fieldsField[]Class的字段的数组,数组中每一个元素都是Field类型。数组长度由num_fields指定。
methodsMethod[]Class的方法的数组,数组中每一个元素都是Method类型。数组长度由num_methods指定。

ClassAccessFlag

名称说明
ACC_PUBLIC0x0001默认属性,方舟字节码中的Class均具备此标志。
ACC_ANNOTATION0x2000声明该类为Annotation类型。

ClassTag

  • 对齐方式:单字节对齐
  • 格式:
名称数量格式说明
NOTHING0x001none拥有此标记的TaggedValue,是其所在class_data的最后一项。
SOURCE_LANG0x020-1 uint8_t拥有此标记的TaggedValue的data是0,表示源码语言是ArkTS/TS/JS。
SOURCE_FILE0x070-1uint32_t拥有此标记的TaggedValue的data是一个偏移量,指向字符串,表示源文件的名称。

注意:

ClassTag是class_data中元素 (TaggedValue) 所具备的标记,表头中的“数量”指的是在某一个Class的class_data中拥有此标记的元素出现的次数。

Field

描述字节码文件中的字段。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
class_idxuint16_t一个指向该字段从属的类的索引,指向一个在ClassRegionIndex中的位置,该位置的值是Type类型,是一个指向Class的偏移量。
type_idxuint16_t一个指向定义该字段的类型的索引,指向一个在ClassRegionIndex中的位置,该位置的值是Type类型。
name_offuint32_t一个偏移量,指向字符串,表示字段的名称。
reserveduleb128方舟字节码文件内部使用的保留字段。
field_dataTaggedValue[]不定长度的数组,数组中每个元素都是TaggedValue类型,元素的标记是FieldTag类型,数组中的元素按照标记递增排序(0x00标记除外)。

注意:

通过Field的偏移量,可以找到适当的IndexHeader以解析class_idx和type_idx。

FieldTag

  • 对齐方式:单字节对齐
  • 格式:
名称数量格式说明
NOTHING0x001none拥有此标记的TaggedValue,是其所在field_data的最后一项。
INT_VALUE0x010-1sleb128拥有此标记的TaggedValue的data的类型为boolean、byte、char、short 或 int。
VALUE0x020-1uint32_t拥有此标记的TaggedValue的data的类型为Value formats中的FLOAT或ID。

注意:

FieldTag是field_data中元素 (TaggedValue) 所具备的标记,表头中的“数量”指的是在某一个Field的field_data中拥有此标记的元素出现的次数。

Method

描述字节码文件中的方法。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
class_idxuint16_t一个指向该方法所从属的类的索引,指向一个在ClassRegionIndex中的位置,该位置的值是Type类型,是一个指向Class的偏移量。
reserveduint16_t方舟字节码文件内部使用的保留字段。
name_offuint32_t一个偏移量,指向字符串,表示方法名称。
index_datauleb128方法的MethodIndexData数据。
method_dataTaggedValue[]不定长度的数组,数组中每个元素都是TaggedValue类型,元素的标记是MethodTag类型,数组中的元素按照标记递增排序(0x00标记除外)。

注意:

通过Method的偏移量,可以找到适当的IndexHeader以解析class_idx。

MethodIndexData

MethodIndexData是一个无符号32位整数,划分为3个部分。

名称格式说明
0 - 15header_indexuint16_t指向一个在IndexSection中的位置,该位置的值是IndexHeader。通过IndexHeader可以找到该方法引用的所有方法 (Method) 、字符串或字面量数组 (LiteralArray) 的偏移量。
16 - 23function_kinduint8_t表示方法的函数类型 (FunctionKind) 。
24 - 31reserveduint8_t方舟字节码文件内部使用的保留字段。

FunctionKind

名称说明
FUNCTION0x1普通函数。
NC_FUNCTION0x2普通箭头函数。
GENERATOR_FUNCTION0x3生成器函数。
ASYNC_FUNCTION0x4异步函数。
ASYNC_GENERATOR_FUNCTION0x5异步生成器函数。
ASYNC_NC_FUNCTION0x6异步箭头函数。
CONCURRENT_FUNCTION0x7并发函数。

MethodTag

名称数量格式说明
NOTHING0x001none拥有此标记的TaggedValue,是其所在method_data的最后一项。
CODE0x010-1 uint32_t拥有此标记的TaggedValue的data是一个偏移量,指向Code,表示方法的代码段。
SOURCE_LANG0x020-1uint8_t拥有此标记的TaggedValue的data是0,表示源码语言是ArkTS/TS/JS。
DEBUG_INFO0x050-1uint32_t拥有此标记的TaggedValue的data是一个偏移量,指向DebugInfo,表示方法的调试信息。
ANNOTATION0x06>=0uint32_t拥有此标记的TaggedValue的data是一个偏移量,指向Annotation, 表示方法的注解。

注意:

MethodTag是method_data中元素 (TaggedValue) 所具备的标记,表头中的“数量”指的是在某一个Method的method_data中拥有此标记的元素出现的次数。

Code

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
num_vregsuleb128寄存器的数量,存放入参和默认参数的寄存器不计算在内。
num_argsuleb128入参和默认参数的总数量。
code_sizeuleb128所有指令的总大小,以字节为单位。
tries_sizeuleb128try_blocks数组的长度,即TryBlock的数量。
instructionsuint8_t[]所有指令的数组。
try_blocksTryBlock[]一个数组,数组中每一个元素都是TryBlock类型。

TryBlock

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
start_pculeb128TryBlock的第一条指令距离其所在Code的instructions的起始位置的偏移量。
lengthuleb128TryBlock的大小,以字节为单位。
num_catchesuleb128与TryBlock关联的CatchBlock的数量,值为1。
catch_blocksCatchBlock[]与TryBlock关联的CatchBlock的数组,数组中有且仅有一个可以捕获所有类型的异常的CatchBlock。

CatchBlock

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
type_idxuleb128值是0,表示此CatchBlock块捕获了所有类型的异常。
handler_pculeb128异常处理逻辑的第一条指令的程序计数器。
code_sizeuleb128此CatchBlock的大小,以字节为单位。

Annotation

描述一个注解结构。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
class_idxuint16_t一个指向当前Annotation所从属的类的索引,指向一个在ClassRegionIndex中的位置,该位置的值是Type类型,是一个指向Class的偏移量。
countuint16_telements数组的长度。
elementsAnnotationElement[]一个数组,数组的每个元素都是AnnotationElement类型。
element_typesuint8_t[]一个数组,数组的每个元素都是AnnotationElementTag类型,用于描述一个AnnotationElement。每个元素在element_types数组中的位置和其对应的AnnotationElement在elements数组中的位置一致。

注意:

通过Annotation的偏移量,可以找到适当的IndexHeader以解析class_idx。

AnnotationElementTag

名称标记
u1'1'
i8'2'
u8'3'
i16'4'
u16'5'
i32'6'
u32'7'
i64'8'
u64'9'
f32'A'
f64'B'
string'C'
method'E'
annotation'G'
literalarray'#'
unknown'0'

AnnotationElement

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
name_offuint32_t一个偏移量,指向字符串,表示注解元素的名称。
valueuint32_t注解元素的值,若值的宽度不超过32位,则此处存储值本身。否则,此处存储的值为指向Value formats格式的偏移量。

Value formats

不同的值类型,有不同的值编码格式,包括INTEGER, LONG, FLOAT, DOUBLE, ID。

名称格式说明
INTEGERuint32_t有符号的四字节整数值。
LONGuint64_t有符号的八字节整数值。
FLOATuint32_t四字节位模式,向右零扩展,系统会将其解译为 IEEE754 32 位浮点值。
DOUBLEuint64_t八字节位模式,向右零扩展,系统会将其解译为 IEEE754 64 位浮点值。
IDuint32_t四字节位模式,表示文件中某个结构的偏移量。

LineNumberProgramIndex

行号程序索引 (LineNumberProgramIndex) 结构是一个数组,便于使用更紧凑的索引访问行号程序 (Line number program) 。

  • 对齐方式:4个字节
  • 格式:
名称格式说明
offsetsuint32_t[]一个数组,数组中每个元素的值是一个偏移量,指向一个行号程序。数组长度由Header中的num_lnps指定。

DebugInfo

调试信息 (DebugInfo) 包含方法的程序计数器与源代码中的行列号之间的映射以及有关局部变量的信息。调试信息的格式由DWARF调试信息格式第3版(见第6.2项)的内容演变形成。基于状态机 (State machine) 的执行模型对行号程序 (Line number program)进行解释,可得到映射和局部变量信息编码。为对不同方法的相同行号程序进行去重,程序中引用的所有常量都被移动到了常量池 (Constant pool) 中。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
line_startuleb128状态机的行号寄存器的初始值。
num_parametersuleb128入参和默认参数的总数量。
parametersuleb128[]存放方法入参的名称的数组,数组长度是num_parameters。每一个元素的值是字符串的偏移量或者0,如果是0,则代表对应的参数不具有名称。
constant_pool_sizeuleb128常量池的大小,以字节为单位。
constant_pooluleb128[]存放常量池数据的数组,数组长度是constant_pool_size。
line_number_program_idxuleb128一个索引,指向一个在LineNumberProgramIndex中的位置,该位置的值是一个指向Line number program的偏移量。Line number program的长度可变,以END_SEQUENCE操作码结束。

Constant pool

常量池 (Constant pool) 是DebugInfo中存放常量的结构。很多方法都具有相似的行号程序,其区别仅在于变量名、变量类型和文件名。为了对这类行号程序进行去重,程序中引用的所有常量都存储在常量池。在解释程序时,状态机维护一个指向常量池的指针。当状态机解释一条需要常量参数的指令时,会从内存常量池指针指向的位置读取值,然后递增指针。

State machine

状态机 (State machine) 的作用是产生DebugInfo信息。状态机中有以下的寄存器:

名称初始值说明
address0程序计数器(指向方法的某个指令),只能单调递增。
lineDebugInfo的属性line_start的值无符号整数,对应源码中的行号。所有的行都是从1开始编号,因此寄存器的值不能小于1。
column0无符号整数,对应源码中的列号。
fileclass_data(参见Class)中SOURCE_FILE标记的值,或者0一个偏移量,指向字符串,表示源文件的名称。如果没有文件名信息(Class中没有SOURCE_FILE标记),那么寄存器的值是0。
source_code0一个偏移量,指向字符串,表示源文件的源码。如果没有源码信息,那么寄存器的值是0。
constant_pool_ptrDebugInfo中常量池的第一个字节的地址指向当前常量值的指针。

Line number program

一个行号程序 (Line number program) 由指令组成。每条指令都包含一个字节的操作码以及可选参数。根据操作码的不同,参数的值可能被编码在指令中(称为指令参数),或者需要从常量池中获取(称为常量池参数)。

操作码指令参数常量池参数参数说明说明
END_SEQUENCE0x00标记行号程序的结束。
ADVANCE_PC0x01uleb128 addr_diffaddr_diff:address寄存器的值待增加的数值address寄存器中的值加上addr_diff,指向下一个地址,而不生成位置条目。
ADVANCE_LINE0x02sleb128 line_diffline_diff:line寄存器的值待增加的数值line寄存器中的值加上line_diff,指向下一个行位置,而不生成位置条目。
START_LOCAL0x03sleb128 register_num

uleb128 name_idx

uleb128 type_idx

register_num:将包含局部变量的寄存器

name_idx:一个偏移量,指向字符串,表示变量的名称

type_idx:一个偏移量,指向字符串,表示变量的类型

在当前地址中引入一个带有名称和类型的局部变量。将要包含这个变量的寄存器的编号被编码在指令中。如果寄存器的编号是-1,则意味着这个是累加器寄存器。name_idx和type_idx的值可能是0,如果是0,则代表着对应的信息是不存在的。
START_LOCAL_EXTENDED0x04sleb128 register_num

uleb128 name_idx

uleb128 type_idx

uleb128 sig_idx

register_num:将包含局部变量的寄存器

name_idx:一个偏移量,指向字符串,表示变量的名称

type_idx:一个偏移量,指向字符串,表示变量的类型

sig_idx:一个偏移量,指向字符串,表示变量的签名

在当前地址中引入一个带有名称、类型和签名的局部变量。将要包含这个变量的寄存器的编号被编码在指令中。如果寄存器的编号是-1,则意味着这个是累加器寄存器。name_idx、type_idx和sig_idx的值可能是0,如果是0,则代表着对应的信息是不存在的。
END_LOCAL0x05sleb128 register_numregister_num:包含局部变量的寄存器在当前地址将指定寄存器中的局部变量标记为超出范围。寄存器的编号为-1,则意味着是累加器寄存器。
SET_FILE0x09uleb128 name_idxname_idx:一个偏移量,指向字符串,表示文件的名称设置file寄存器的值。name_idx的值可能是0,如果是0,则代表着对应的信息是不存在的。
SET_SOURCE_CODE0x0auleb128 source_idxsource_idx:一个偏移量,指向字符串,表示文件的源码设置source_code寄存器的值。source_idx的值可能是0,如果是0,则代表着对应的信息是不存在的。
SET_COLUMN0x0buleb128 column_numcolumn_num:待设置的列号设置column寄存器的值,并生成一个位置条目。
特殊操作码0x0c..0xff使 line 和 address 寄存器指向下一个地址,并生成一个位置条目。详情参阅下文中的说明。

对于值在0x0c和0xff(含)之间的特殊操作码,状态机按照以下步骤将line和address寄存器移动一小部分,然后生成一个新的位置条目(参见DWARF调试信息格式第3版第6.2.5.1项 Special Opcodes):

步骤序号操作说明
1adjusted_opcode = opcode - OPCODE_BASE计算调整后的操作码。OPCODE_BASE的值是0x0c,是第一个特殊操作码。
2address += adjusted_opcode / LINE_RANGE增加address寄存器中的值。LINE_RANGE的值是15,用来计算行号信息的变化。
3line += LINE_BASE + (adjusted_opcode % LINE_RANGE)增加line寄存器中的值。LINE_BASE的值是-4,是最小的行号增量值;最大的行号增量值是LINE_BASE + LINE_RANGE - 1。
4生成一个新的位置条目。

注意:

“特殊操作码”是通过此公式计算得到:(line_increment - LINE_BASE) + (address_increment * LINE_RANGE) + OPCODE_BASE。

IndexSection

通常情况下,字节码文件的各个结构使用32位偏移量来引用,当一个结构引用另一个结构时,需要在当前结构中记录被引用结构的32位偏移量。为了减小文件体积,字节码文件被分割成多个索引区域 (Index region),每个索引区域内的结构使用16位索引。IndexSection结构描述了索引区域的集合。

  • 对齐方式:4个字节
  • 格式:
名称格式说明
headersIndexHeader[]一个数组,数组中每个元素是IndexHeader类型。数组中的元素根据区域的起始偏移量进行排序。数组长度由Header中的num_index_regions指定。

IndexHeader

每个IndexHeader结构描述一个索引区域。每个索引区域都有两类索引:指向Type的索引和指向方法、字符串或者字面量数组的索引。

  • 对齐方式:4个字节
  • 格式:
名称格式说明
start_offuint32_t该区域的起始偏移量。
end_offuint32_t该区域的结束偏移量。
class_region_idx_sizeuint32_t该区域的ClassRegionIndex中元素的数量,最大值为65536。
class_region_idx_offuint32_t一个偏移量,指向ClassRegionIndex
method_string_literal_region_idx_sizeuint32_t该区域的MethodStringLiteralRegionIndex中元素的数量,最大值为65536。
method_string_literal_region_idx_offuint32_t一个偏移量,指向MethodStringLiteralRegionIndex
reserveduint32_t方舟字节码文件内部使用的保留字段。
reserveduint32_t方舟字节码文件内部使用的保留字段。
reserveduint32_t方舟字节码文件内部使用的保留字段。
reserveduint32_t方舟字节码文件内部使用的保留字段。

ClassRegionIndex

ClassRegionIndex结构的作用是允许通过更紧凑的索引,找到对应的Type

  • 对齐方式:4个字节
  • 格式:
名称格式说明
typesType[]一个数组,数组中每个元素都是Type类型。数组长度由IndexHeader中的class_region_idx_size指定。

Type

表示一个基本类型编码或一个指向Class的偏移量,是一个32位的值。

基本类型采用以下方式编码:

类型编码
u10x00
i80x01
u80x02
i160x03
u160x04
i320x05
u320x06
f320x07
f640x08
i640x09
u640x0a
any0x0c

MethodStringLiteralRegionIndex

MethodStringLiteralRegionIndex结构的作用是允许通过更紧凑的索引,找到对应的方法、字符串或者字面量数组。

  • 对齐方式:4个字节
  • 格式:
名称格式说明
offsetsuint32_t[]一个数组,数组中每个元素的值是一个偏移量,指向方法、字符串或者字面量数组。数组长度由IndexHeader中的method_string_literal_region_idx_size指定。

LiteralArray

描述字节码文件中的字面量数组。

  • 对齐方式:单字节对齐
  • 格式:
名称格式说明
num_literalsuint32_tliterals数组的长度。
literalsLiteral[]一个数组,数组的每个元素都是Literal类型。

Literal

描述字节码文件中的字面量,根据字面量值的字节数的不同,有四种编码格式。

名称格式对齐方式说明
ByteOneuint8_t1个字节单字节的值。
ByteTwouint16_t2个字节双字节的值。
ByteFouruint32_t4个字节四字节的值。
ByteEightuint64_t8个字节八字节的值。
  • 33
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值