wav文件头字节数和文件实际字节不一致_详解 Linux 可执行文件 ELF 文件的内部结构...

1. 引言

上一篇文章中,我们介绍了如何让汇编语言与 C 语言相互调用:
如何实现汇编语言与 C 语言之间的相互调用

还记得我们自制操作系统的脚步到哪里了呢?没错,已经完成了从启动扇区跳转到 loader,那么,下一步就是如何用 loader 拉起内核了。
有了上面汇编语言调用 C 语言的基础,我们就可以做到让汇编语言编写的 loader 拉起用 C 语言编写的内核了,本文我们就来详细了解一下编译后的可执行文件 — ELF 文件的结构,下一篇文章将会介绍如何通过汇编将 ELF 文件载入内存并执行。

2. ELF 文件

上一篇文章中,当我们编译汇编代码时,指定了 -f elf 参数:

nasm -f elf -o asm.o main.asm

这就意味着生成的 asm.o 是 ELF 文件(Executable and Linkable Format)
所谓的 ELF 文件,翻译过来就是“可执行与可链接文件”,是一种用于二进制文件之间相互调用的可执行文件格式,通过链接即可引入调用,拥有非常强大的可扩展性和灵活性。
在 linux 中,可执行文件、Object文件、动态库文件都是ELF格式文件,他相当于 windows 操作系统中的 PE 文件。
通过 readelf 命令可以读取 ELF 文件的内容。

3. ELF 文件的结构

要想使用 ELF 文件,我们首先必须知道 ELF 文件是如何构成的。

679a4d2906e922f6f22e72d4945e377e.png

如上图所示,ELF 文件由四部分组成:

  1. elf header — ELF 头
  2. program header table — 程序头表
  3. sections — 节
  4. section header table — 节头表

并非所有的 ELF 文件都包含全部上述四部分,除了 ELF 头外,其他各部分的位置、大小都不固定。
这里提到了“节”的概念,上一篇文章中,我们在汇编中使用了 section 关键字,就是指定了对应代码块的 section 类型,linux 支持下面的三种 section:

  1. .text — 代码段,用来存放代码,可读可执行
  2. .data — 数据段,存放堆栈以及初始化过的 global 变量、static 变量等,可读可写
  3. .bss — 存放未初始化 global 变量和未初始化 static 变量,可读可写
  4. .rodata — 存放字符串、常量

3.1. ELF 头

既然除了 ELF 头外其他部分的位置、大小都不固定,那么他们又是如何决定的呢?很简单,他们的位置和大小都是由 ELF 头中的字段声明的。
ELF 头的格式如下:

#define EI_NIDENT 16  
typedef struct {
// 16 字节 ELF 文件声明,由固定信息组成,用来表示是 ELF 文件
unsigned char e_ident[EI_NIDENT];
// 标识 elf 文件类型
// 0. 未知, 1. 可重定位文件, 2. 可执行文件, 3. 共享目标文件, 4. core 文件
Elf32_Half e_type;
// 程序运行的硬件体系结构,80386 体系为 3
Elf32_Half e_machine;
// 文件版本号
Elf32_Word e_version;
// 程序入口地址
Elf32_Addr e_entry;
// Program header table 在文件中的偏移量(字节数)
Elf32_Off e_phoff;
// Section header table 在文件中的偏移量(字节数)
Elf32_Off e_shoff;
// 文件标识符,IA32 汇编为 0
Elf32_Word e_flags;
// ELF header 的字节数
Elf32_Half e_ehsize;
// Program header table 中每个条目的字节数
Elf32_Half e_phentsize;
// Program header table 中条目数
Elf32_Half e_phnum;
// Section header table 中每个条目的字节数
Elf32_Half e_shentsize;
// Section header table 中条目数
Elf32_Half e_shnum;
// 包含节名称的字符串表是第几个节
Elf32_Half e_shstrndx;
} Elf32_Ehdr;

上面使用到的数据类型如下:

ELF header 声明中的类型
名称大小对齐用途
Elf32_Addr44无符号程序地址
Elf32_Half22无符号中等大小整数
Elf32_Off44无符号文件偏移
Elf32_Sword44有符号大整数
Elf32_Word44无符号大整数
unsigned char11无符号小整数

下图展示了上篇文章中我们生成的两个文件通过 readelf 命令读取到的头信息:

6088d3bb977e85923e272a8a38ab5e2e.png

3.2. Section Header 的结构

Section Header Table 中的每个条目 Section Header 都描述了 ELF 文件中 Sections 区域中一个节的信息。
他的结构如下:

typedef struct {  
// 节区名,是节区头部字符串表节区(Section Header String Table Section)的索引
// 名字是一个 NULL 结尾的字符串
Elf32_Word sh_name;
// 该节类型
Elf32_Word sh_type;
// 节区标志
Elf32_Word sh_flags;
// 如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置
// 否则,此字段为 0
Elf32_Addr sh_addr;
// 该节区首个字节的偏移。
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;

下图展示了上篇文章中可执行文件 main 的 Section Header 结构:

6b9c88d6918cc483fb51ab50ede34922.png

3.3. Program Header 的结构

Program Header Table 中的条目 Program Header 是与程序执行直接相关的,他描述了一个即将被载入内存的段在文件中的位置、大小以及它被载入内存后所在的位置和大小。
一个段包含一个或多个节。
Program Header 结构如下:

typedef struct {  
// 当前 Program header 所描述的段的类型
Elf32_Word p_type;
// 该段首地址在文件中的偏移量(字节数)
Elf32_Off p_offset;
// 该段被载入内存后,首个字节的虚拟地址
Elf32_Addr p_vaddr;
// 该段被载入内存后,首个字节的物理地址(对于使用虚拟地址的系统来说,该项为 0)
Elf32_Addr p_paddr;
// 段长度(字节数)
Elf32_Word p_filesz;
// 段在内存中的长度
Elf32_Word p_memsz;
// 段标志位
Elf32_Word p_flags;
// 段在文件内和内存中的对齐方式
Elf32_Word p_align;
} Elf32_Phdr;

通过 readelf -l 命令我们就可以查看文件的 program headers 信息了:

c67f01d8ede1679589b0e0bc0eef2333.png

从图中,我们可以看到这个 ELF 文件有五个 Program header 条目。

4. 后记

本文,我们介绍了 ELF 文件的四个组成部分,以及其中三个的具体结构,而实际存储数据的 section 的结构我们并没有介绍。
别忘了我们的目标,我们需要通过汇编语言编写的 loader 程序将在 linux 环境上编译的 C 语言内核程序载入到内存并执行,因此,实际上我们只需要知道 ELF 文件需要如何被载入内存,并从哪里开始执行。
了解了上面的结构信息,你就会发现,事实上与我们的目标直接相关的是 ELF 文件中的 Program Header 部分,他描述了可执行文件中有那几个段,每个段需要被载入到内存的哪个位置,而每个段包含多少个节、以及每个节内部的具体信息我们其实并不需要关心。
也就是说,我们通过 ELF header 中的字段,找到 Program Header Table,然后读取每个 Program Header,将对应的段载入到内存指定的位置,然后跳转,即可实现可执行文件的执行了。
这样一来,是不是读取 ELF 文件并载入内存的工作已经呼之欲出了呢?
敬请期待博主下一篇文章,详细讲解 loader 加载内核的完整代码。

5. 微信公众号

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤。

5326b9add8e40e6fd7eee31fb2caca89.png

6. 参考资料

https://blog.csdn.net/mergerly/article/details/94585901。
http://www.choudan.net/2013/11/16/Linux%E8%BF%9B%E7%A8%8B%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4%E5%86%8D%E5%AD%A6%E4%B9%A0.html。
http://www.choudan.net/2013/10/25/Linux%E8%BF%9B%E7%A8%8B%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4%E5%AD%A6%E4%B9%A0%28%E4%BA%8C%29.html。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值