diff --git a/articles/20230919-elf2flt-flt-analysis-0.md b/articles/20230919-elf2flt-flt-analysis-0.md new file mode 100644 index 0000000000000000000000000000000000000000..7a7f5944d0d3f33894ff26867576d8947d3467f0 --- /dev/null +++ b/articles/20230919-elf2flt-flt-analysis-0.md @@ -0,0 +1,184 @@ +> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [codeinline urls refs pangu autocorrect]
+> Author: Odysseus <320873791@qq.com>
+> Date: 2023/09/19
+> Revisor: walimis <>
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Proposal: [为 ELF2FLT 完善独立编译与安装支持](https://gitee.com/tinylab/riscv-linux/issues/I79PO2)
+> Sponsor: PLCT Lab, ISCAS + +# ELF 和 FLAT 的区别 + +## 简介 + +bFLT 是一种用于嵌入式系统的可执行文件格式,它的特点是简单、紧凑,适合于资源有限的嵌入式系统。 + +而 FLAT 的二进制文件通常成为 BFLT,是基于原始的 ELF 文件格式的一种简化版本,它的特点是简单、紧凑,适合于资源有限的嵌入式系统。 + +ELF 的头较为重量级,bflt 格式对文件头和一些段信息进行了简化。ELF 支持动态加载,因此更统一使用和创建动态链接库。而 FLAT 并不支持动态加载。 + +接下来我们主要从头文件和重定位的方式解析两种格式的区别。 + +## bflt 程序结构 + +bflt 格式的结构如下: + +![uclinux_file_bflt](images/20230919-elf2flt-flt-analysis-0/bflt-structure.png) + +其中,header 由以下结构体定义: + +```c +struct flat_hdr { + char magic[4]; + uint32_t rev; /* version */ + uint32_t entry; /* Offset of first executable instruction with text segment from beginning of file */ + uint32_t data_start; /* Offset of data segment from beginning of file */ + uint32_t data_end; /* Offset of end of data segment from beginning of file */ + uint32_t bss_end; /* Offset of end of bss segment from beginning of file */ + + /* (It is assumed that data_end through bss_end forms the bss segment.) */ + + uint32_t stack_size; /* Size of stack, in bytes */ + uint32_t reloc_start; /* Offset of relocation records from beginning of file */ + uint32_t reloc_count; /* Number of relocation records */ + uint32_t flags; + uint32_t build_date; /* When the program/library was built */ + uint32_t filler[5]; /* Reserved, set to zero */ +}; +``` + +如上,bflt 的 header 只有 19 个字节。 +header 以由"bFLT"四个字符组成的幻数开头。下一个字段 rev 表明 bflt header 的版本。目前而言,bflt 有两个主要的大版本(version 2 和 version 4),不同的 bflt 版本支持的 flags 和重定位格式都有所不同。 + +接下来的字段标记程序各段的起始/结束位置:entry 为程序执行的第一条指令,也就是.text 段的起始位置;data_start 和 data_end 标示.data 段的起始/结束位置;bss_end 标示.bss 段的结束位置,bflt header 并未标示 bss_start,而是假定从 data_end 到 bss_end 都为.bss 段;stack_size 指定栈空间大小;reloc_start 和 reloc_count 共同决定重定位表的范围,其中 reloc_start 标示起始位置,reloc_count 表明重定位条目数。 + +flags 字段可能的取值如下: + +```c +#define FLAT_FLAG_RAM 0x0001 /* load program entirely into RAM */ +#define FLAT_FLAG_GOTPIC 0x0002 /* program is PIC with GOT */ +#define FLAT_FLAG_GZIP 0x0004 /* all but the header is compressed */ +#define FLAT_FLAG_GZDATA 0x0008 /* only data/relocs are compressed (for XIP) */ +#define FLAT_FLAG_KTRACE 0x0010 /* output useful kernel trace for debugging */ +#define FLAT_FLAG_L1STK 0x0020 /* use a 4k stack in L1 scratch memory. */ +``` + +其中前三个 flag 是 version 4 新增的。FLAT_FLAG_RAM 表明程序不是 XIP 的,执行时会完全加载进内存;FLAT_FLAG_GOTPIC 表明程序是有 GOT 表的 PIC 程序,此时程序.data 段的开头有一张 GOT 表,该表以 -1 结尾,包含需要在运行时重定位的偏移量,用以实现动态链接。 + +重定位表由重定位条目组成。在 version 2 中,重定位条目定义如下: + +```c +#define OLD_FLAT_VERSION 0x00000002L +#define OLD_FLAT_RELOC_TYPE_TEXT 0 +#define OLD_FLAT_RELOC_TYPE_DATA 1 +#define OLD_FLAT_RELOC_TYPE_BSS 2 + +typedef union { + uint32_t value; + struct { +# if defined(mc68000) && !defined(CONFIG_COLDFIRE) + int32_t offset : 30; + uint32_t type : 2; +# define OLD_FLAT_FLAG_RAM 0x1 /* load program entirely into RAM */ +# elif defined(__BIG_ENDIAN_BITFIELD) + uint32_t type : 2; + int32_t offset : 30; +# define OLD_FLAT_FLAG_RAM 0x1 /* load program entirely into RAM */ +# elif defined(__LITTLE_ENDIAN_BITFIELD) + int32_t offset : 30; + uint32_t type : 2; +# define OLD_FLAT_FLAG_RAM 0x1 /* load program entirely into RAM */ +# else +# error "Unknown bitfield order for flat files." +# endif + } reloc; +} flat_v2_reloc_t; +``` + +在 version 4 中,重定位类型被去掉,一个条目就是一个偏移量(以 0 为基)。各偏移量在重定位时依靠与程序各段起始/结束位置(例如 entry、data_start、data_end、bss_end)进行比较来确定其所处的程序段。 + +## ELF 文件格式 + +ELF 文件结构如下图所示:(这边改成文件夹下的 elf.png) + +ELF 文件格式的 header 有 32bit 和 64bit 两个版本,这边给出 32bit 的版本: + +```c +#define EI_NIDENT 16 + +typedef struct elf32_hdr{ + unsigned char e_ident[EI_NIDENT]; + Elf32_Half e_type; + Elf32_Half e_machine; + Elf32_Word e_version; + Elf32_Addr e_entry; /* Entry point */ + 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; +``` + +64bit 版本的 header 与 32bit 版本的 header 基本相同,区别仅仅是字长的区别 + +header 中的 e_ident 字段用于标识文件类型,是一个 16 字节的表示,其中前四个字节为魔数,取值是固定的 `0x7f`、`'E'`、`'L'`、`'F'`,[4,4] 字节为文件类型,[5,5] 字节为字长,[6,6] 字节为字节序,[7,7] 字节为版本号,[8,15] 字节为保留字节。 + +e_type 字段标识文件类型,e_machine 字段标识目标文件体系结构,e_version 是当前文件的版本,e_entry 是程序入口地址,e_phoff 是程序头表的偏移量,e_shoff 是节头表的偏移量,e_flags 是特定于目标文件的标志,e_ehsize 是 elf header 的大小,e_phentsize 是程序头表中每个条目的大小,e_phnum 是程序头表中条目的数量,e_shentsize 是节头表中每个条目的大小,e_shnum 是节头表中条目的数量,e_shstrndx 是节头表中节名称字符串表的索引。 + +e_type 字段决定了 ELF 文件的类型如下三种: + +* 可重定位文件(relocatable file):包含可重定位的目标文件,可以用于链接生成可执行文件或者共享目标文件。 +* 可执行文件(executable file):包含可执行的目标文件,可以直接被加载到内存中执行。 +* 共享目标文件(shared object file):包含共享目标文件,可以被动态加载到内存中。 + +ELF 的节头表是节头数组。每一个节头描述其信息。sh_name 字段为节名,sh_type 字段为节类型,sh_flags 字段为节标志,sh_addr 字段为节的虚拟地址,sh_offset 字段为节的偏移量,sh_size 字段为节的大小,sh_link 字段为节头表索引,sh_info 字段为节头表索引,sh_addralign 字段为节的对齐要求,sh_entsize 字段为节头表中每个条目的大小。 + +除此之外,节中比较特殊的是如符号表,重定位表等。 + +## 重定位 + +bFLT 文件一般而言是 PIC 的,执行之前需要由加载器进行重定位。重定位有两种,一种是对全局/静态指针的重定位,一种是对 GOT 表的重定位。这两种重定位在没有 XIP 时使用相同的方法,即将程序基址直接加上偏移量。如果需要处理代码段和数据段不连续的情况以实现 XIP,可以通过如下方法确定偏移量所属程序段: + +```c +if (offset < (data_start - entry)) { + //pointing to code section +} else if (offset < (data_end - entry)) { + //pointing to data section +} else if (offset < (bss_end - entry)) { + //pointing to bss section +} +``` + +BFLT 文件通常是没有显式的重定位表的,而 ELF 文件的重定位信息被包含在其重定位表重,在加载 ELF 文件时,操作系统通过解析重定位表的信息,根据需要修正符号引用,以适应实际内存布局。这里的重定位也是支持不同类型的,绝对地址和相对地址都可以被使用。 + +总的来说,其在重定位的不同是他们的复杂性和灵活性的不同的决定因素。ELF 文件用更规范也更复杂的重定位方式,而 bFLT 文件则使用更简单,更紧凑的重定位方式。 + +## 共享库 + +bFLT 对共享库的链接在编译时而非运行时被决定,这点不同于带有 MMU 的系统。但是,程序在加载时仍然需要对共享库链接进行重定位。 + +共享库的重定位条目由两个部分组成:共享库的编号和所引用符号在共享库中的偏移。其中,共享库编号范围是 0~255,占用最高字节。例如,一个共享库的重定位条目为 `0x030003a0`,表明该重定位条目指向编号为 `0x03` 的共享库当中偏移量相对于共享库为 `0x03a0` 的符号。如果该共享库被加载进 `0x2000`,而使用该库的程序被加载进 `0x1000`,那么该符号重定位后的绝对地址应当是 0x23a0。 + +而 ELF 文件支持动态链接,在运行时可以加载共享库而不需要将他们完全静态链接到可执行文件中,这样可以减少可执行文件的大小。ELF 文件的动态链接是通过动态链接器(dynamic linker)来实现的,动态链接器是一个独立的可执行文件,它在程序加载时被调用,负责加载共享库并进行重定位。 + +## 总结 + +总结来说,通过上文对 ELF 和 bFLT 的文件从头,结构,重定位和共享库的解析,其实可以看出其区别主要在于: + +1. 通用性:ELF 文件格式是通用的,而 bFLT 文件格式是针对嵌入式系统。 +2. 功能复杂性:ELF 文件格式支持动态链接,共享库,多种重定位类型,而 bFLT 文件格式更简单紧凑。 +3. 资源效率:BFLT 文件紧凑简单,对资源的利用率高,更适合资源有限的嵌入式系统。ELF 文件元数据更多,对资源的利用率低,更适合资源充足的系统。 + +## 参考资料 + +-. [http://myembeddeddev.blogspot.com/2010/02/uclinux-flat-file-format.html][001] +-. [https://blog.tangrs.id.au/2012/04/07/bflt-format-implementation-notes/][002] +-. [https://gitee.com/tinylab/elf2flt][003] + +[001]: http://myembeddeddev.blogspot.com/2010/02/uclinux-flat-file-format.html +[002]: https://blog.tangrs.id.au/2012/04/07/bflt-format-implementation-notes/ +[003]: https://gitee.com/tinylab/elf2flt diff --git a/articles/images/20230919-elf2flt-flt-analysis-0/bflt-structure.png b/articles/images/20230919-elf2flt-flt-analysis-0/bflt-structure.png new file mode 100644 index 0000000000000000000000000000000000000000..d059d56b0a4f1bd8262602d39d042164c30de693 Binary files /dev/null and b/articles/images/20230919-elf2flt-flt-analysis-0/bflt-structure.png differ