LEF文件格式
LEF文件有二种视图,一种是链接视图,一种是执行视图
链接视图是以节(section)为单位,执行视图是以段(segment)为单位,链接视图就是在链接时用到的视图,而执行视图则时用到的视图。上图左测的视图是执行来看的。总个文件可以分为4个部分
1.ELF header :描述整个文件的组织
2.Program Header Table :描述文件中的各种segments,用来告诉系统如何创建进程映像的。
3.sections 或者segment:segments是从运行的角度来描述elf文件,section是从链接的角度来描述elf文件,
4.Section Header Table:包含了文件各个section的属性信息
ELF Header 研究
typedef struct {v
unsigned char e_ident[EI_NIDENT];
ELF32_Half e_type;
ELF32_Half e_machine;
ELF32_Word e_version;
ELF32__Addr e_entry;
ELF32_Off e_phoff;
ELF32_Off e_shoff;
ELF32_Word e_flags;
ELF32_Half e_ehsize;
ELF32_Half e_phentsize;
ELF32_Half e_phnum;
ELF32_Half e_shentsize;
ELF32_Half e_shnum;
ELF32_Half e_shstrndx;
}Elf32_Ehdr;
e_ident : ELF的一些标识信息,前四位为.ELF,其他的信息比如大小端等
e_machine : 文件的目标体系架构,比如ARM
e_version : 0为非法版本,1为当前版本
e_entry : 程序入口的虚拟地址
e_phoff : 程序头部表偏移地址
e_shoff : 节区头部表偏移地址
e_flags :保存与文件相关的,特定于处理器的标志
e_ehsize :ELF头的大小
e_phentsize : 每个程序头部表的大小
e_phnum :程序头部表的数量
e_shentsize:每个节区头部表的大小
e_shnum : 节区头部表的数量
e_shstrndx:节区字符串表位置
如下图 在ubuntu命令行执行通过执行命令 readelf -h dra7-dsp1-fw.xe88 来查看该elf文件的头信息
Section Header Table 研究
一个EFL文件中到底有哪些具体的section,由包含在这个EFL文件中的section head table(SHT)决定。在SHT中,针对每个section,都设置有一个条目(entry),用来描述对应的这个section,其内容主要包括section的名称,类型,大小以及在整个ELF文件中的字节偏移位置等等,
typedef struct{
Elf32_Word sh_name; //节区名,是节区头部字符串表节区(Section Header String Table Section)
//的索引。名字是一个 NULL 结尾的字符串。
Elf32_Word sh_type; //为节区类型
Elf32_Word sh_flags; //节区标志
Elf32_Addr sh_addr; //如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置。
//否则,此字段为 0。
Elf32_Off sh_offset; //此成员的取值给出节区的第一个字节与文件头之间的偏移。
Elf32_Word sh_size; //此 成 员 给 出 节 区 的 长 度 ( 字 节 数 )。
Elf32_Word sh_link; //此成员给出节区头部表索引链接。其具体的解释依赖于节区类型。
Elf32_Word sh_info; //此成员给出附加信息,其解释依赖于节区类型。
Elf32_Word sh_addralign; //某些节区带有地址对齐约束.
Elf32_Word sh_entsize; //某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。
}Elf32_Shdr;
如下图,可以通过执行命令 readelf -S dra7-dsp1-fw.xe88
资源表结构研究
下图是描述资源表节的结构
资源表节的头部是一个resource_table结构体。通过resource_table结构体的offset偏移量可以找到资源表描述的具体资源的信息,而fw_rsc_hdr结构体是用来描述一个具体资源信息的结构体。里面包含这个具体资源信息的类型,地址等等。
下面是资源表节头的结构体
-
resource_table结构体的ver成员默认等于1
-
resource_table结构体的reserved[2]成员 reserved[0]和reserved[1]默认为0
-
resource_table结构体的num成员描述了资源表有多少资源描述条目
-
resource_table结构体的offset[0]成员可以用于找到资源描述条目的地址
struct resource_table {
u32 ver;
u32 num;
u32 reserved[2];
u32 offset[0];
} ;
注意:offset是通过越界访问来得到每个资源条目对应的偏移量地址。也就是offset[0]紧接着下面的地址装的数据是偏移量地址而偏移量offset的数量等于num。
下面是描述具体地址资源表的结构体
描述具体地址资源表的type类型,fw_resource_type分别举例了这些类型
struct fw_rsc_hdr {
u32 type;
u8 data[0];
};
enum fw_resource_type {
RSC_CARVEOUT = 0,
RSC_DEVMEM = 1,
RSC_TRACE = 2,
RSC_VDEV = 3,
RSC_INTMEM = 4,
RSC_CUSTOM = 5,
RSC_LAST = 6,
};
随着类型的不同下面描述的具体地址资源表的结构体也不同,处理方式也不同
当type类型为0时对应的下面这种结构体信息
da成员是设备地址
pa成员是物理地址
len成员是长度
flags成员iommu保护的标志
reserver成员必须是0
name成员是这个地址区域的名字
struct fw_rsc_carveout {
u32 da;
u32 pa;
u32 len;
u32 flags;
u32 reserved;
u8 name[32];
} __packed;
备注: __packed是用于将结构体对齐
当type类型为1时对应下面这种结构体信息
与上面的fw_rsc_carveout结构体基本一致
struct fw_rsc_devmem {
u32 da; //device address
u32 pa; //physical address
u32 len; //size of the mapping
u32 flags;
u32 reserved;
u8 name[32];
} __packed;
当type类型为2时对应下面这种结构体
里面的结构体成员的含义与上面的fw_rsc_carveout结构体基本一致
struct fw_rsc_trace {
u32 da;
u32 len;
u32 reserved;
u8 name[32];
} __packed;
当type类型为3时对应下面这种结构体
id成员是虚拟设备的id
notifyid成员是通知id
dfeatures成员是指定固件支持的virtio设备功能
gfeatures成员是占位符,主机使用它来写会双方支持的协议功能
config_len是vdev的virtio配置空间的大小
status是占位符
num_of_vrings成员是用于指示此vdev标头中描述了多少个vring
reserved成员是保留(但必须为零)
vring是struct fw_rsc_vdev_vring’结构的@num_of_vrings个条目的数组。
struct fw_rsc_vdev {
u32 id;
u32 notifyid;
u32 dfeatures;
u32 gfeatures;
u32 config_len;
u8 status;
u8 num_of_vrings;
u8 reserved[2];
struct fw_rsc_vdev_vring vring[0];
} __packedd;
资源表代码解析研究
下面是资源表的处理核心代码,rsc_table是资源表节头结构体,在下面的循环中将执行rsc_table->num次循环。每次循环将处理一个资源表的具体资源条目。根据具体资源表的条目的节结构体来分别用不同的函数对具体资源表的条目进行处理。
for (i = 0; i < rsc_table->num; i++) // rsc_table is a resources table content
{
int offset = rsc_table->offset[i];
struct fw_rsc_hdr *hdr = (void *)rsc_table + offset;
// len is table size
// offset is offset address
// sizeof(*hdr) is struct fw_rsc_hdr size
int avail = len - offset - sizeof(*hdr);
// (void *)hdr is struct hdr address
// sizeof(*hdr) is struct fw_rsc_hdr size
void *rsc = (void *)hdr + sizeof(*hdr);
/* make sure table isn't truncated */
if (avail < 0) {
printf("rsc table is truncated\n");
return -EINVAL;
}
debug("rsc: type %d\n", hdr->type);
if (hdr->type >= RSC_LAST) { //judge type
printf("unsupported resource %d\n", hdr->type);
continue;
}
handler = handlers[hdr->type]; //find corresponding handle function
if (!handler)
continue;
//rsc is (void *)hdr + sizeof(*hdr);
//offset + sizeof(*hdr)
//avail is len - offset - sizeof(*hdr);
ret = handler(rsc, offset + sizeof(*hdr), avail); //delivery
if (ret)
break;
}
定位具体的资源表条目
1 根据 i 变量找到对应的具体资源表条目的偏移量
offset = rsc_table->offset[i];
2 定义一个fw_rsc_hdr结构体指针来定位具体资源条目部分的地址。
struct fw_rsc_hdr *hdr = (void *)rsc_table + offset;
1.2步骤执行了下图所示的通过offset进行索引找到具体资源条目的头部结构体地址
3 avail变量用于检验资源表的空间合法性
// len is resourer table size
// offset is offset address
// sizeof(*hdr) is struct fw_rsc_hdr size
int avail = len - offset - sizeof(*hdr);
由下图红框所示avail就是通过下面这种方式进行计算。len是资源表节的大小。offset是偏移量的大小,hdr size是fw_rsc_hdr结构体的大小。
4.下面是用于找到具体资源条目的结构体地址
// (void *)hdr is struct hdr address
// sizeof(*hdr) is struct fw_rsc_hdr size
void *rsc = (void *)hdr + sizeof(*hdr);
5 下面是校验机制,判断资源表的合法性
/* make sure table isn't truncated */
if (avail < 0) {
printf("rsc table is truncated\n");
return -EINVAL;
}
debug("rsc: type %d\n", hdr->type);
if (hdr->type >= RSC_LAST) { //judge type
printf("unsupported resource %d\n", hdr->type);
continue;
}
6.下面是根据fw_rsc_hdr结构体type成员来判断这个具体资源条目的类型,然后根据这个类型调用对应的函数进行处理,而对应具体结构体。
handler = handlers[hdr->type]; //find corresponding handle function
if (!handler)
continue;
//rsc is (void *)hdr + sizeof(*hdr);
//offset + sizeof(*hdr)
//avail is len - offset - sizeof(*hdr);
ret = handler(rsc, offset + sizeof(*hdr), avail);
static handle_resource_t loading_handlers[RSC_LAST] = {
[RSC_CARVEOUT] = (handle_resource_t) handle_carveout,
[RSC_DEVMEM] = (handle_resource_t) handle_devmem,
[RSC_TRACE] = (handle_resource_t) handle_trace,
[RSC_VDEV] = (handle_resource_t) handle_vdev,
};
static int handle_carveout(struct fw_rsc_carveout *rsc, int offset, int avail)
static int handle_devmem(struct fw_rsc_devmem *rsc, int offset, int avail)
static int handle_trace(struct fw_rsc_trace *rsc, int offset, int avail)
static int handle_vdev(struct fw_rsc_vdev *rsc, int offset, int avail
Program Header Table 研究
程序头部(Program Header)描述了与程序执行直接相关的目标文件结构信息。用来在文件定位各个段的映像。同时包含其他一些用来程序创建映像所必须的信息。可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段或者系统准备程序执行所必须的其他信息。目标文件的段包含一个或者多个"节区", 也就是"段内容"(segment Contents)". 程序头部对可执行文件有意义。
程序头部的数据结构如下
typedef struct {
Elf32_Word p_type; //此数组元素描述的段的类型,或者如何解释此数组元素的信息。
Elf32_Off p_offset; //此成员给出从文件头到该段第一个字节的偏移
Elf32_Addr p_vaddr; //此成员给出段的第一个字节将被放到内存中的虚拟地址
Elf32_Addr p_paddr; //此成员仅用于与物理地址相关的系统中。System V忽略所有应用程序的物理地址信息。
Elf32_Word p_filesz; //此成员给出段在文件映像中所占的字节数。可以为0。
Elf32_Word p_memsz; //此成员给出段在内存映像中占用的字节数。可以为0。
Elf32_Word p_flags; //此成员给出与段相关的标志。
Elf32_Word p_align; //此成员给出段在文件中和内存中如何对齐。
} Elf32_phdr
如下图可以执行如下命令 readelf -l dra7-dsp1-fw.xe88
当elf文件被加载到内存中后,系统会将多个具有相同权限(flag值)section合并成一个segment。操作系统往往要以页为基本单位来管理内存。一般页的大小为4KB(4096B),同时内存的权限管理的最小单位也是以使页,页内内存的权限都是相同。因为ELF文件具有很多的section,如果不合并成段就会很容易就导致内存碎片造成内存的浪费。