1. 进程
"""
1. 只能使用那些操作系统分配给进程的地址, 如果访问未经允许的空间, 那么操作系统就会捕获到这些访问,
将进程的这种访问当作非法操作, 强制结束进程.
2. 从硬件层面上来讲, 原先的32位地址线只能访问最多4GB的物理内存. 但是自从扩展至36位地址线之后,
Intel 修改了页映射的方式, 使得新的映射方式可以访问到更多的物理内存. Intel把这个地址扩展方式叫做PAE.
3. 创建一个进程, 然后装载相应的可执行文件并且执行.
- 创建一个独立的虚拟地址空间.
- 读取可执行文件头, 并且建立虚拟空间与可执行文件的映射关系.
- 将CPU的指令寄存器设置成可执行文件的入口地址, 启动运行.
"""
2. VMA区域
"""
操作系统通过给进程空间划分出一个个VMA来管理进程的虚拟空间; 基本原则是将相同权限属性的, 有相同映像文件的映射成一个VMA;
一个进程基本上可以分为如下几种VMA区域:
- 代码VMA, 权限只读, 可执行, 有映像文件
- 数据VMA, 权限可读写, 可执行, 有映像文件
- 堆VMA, 权限可读写, 可执行, 无映像文件, 匿名, 可向上扩展
- 栈VMA, 权限可读写, 不可执行, 无映像文件, 匿名, 可向下扩展
- 查看内存映射: ./section.elf & cat /proc/59320/maps
"""
3. 装载过程
"""
在Linux系统的bash下输入一个命令执行某个ELF程序时, Linux装载这个ELF文件并且执行的过程.
- 首先在用户层面, bash进程会调用fork()系统调用创建一个新的进程, 然后新的进程调用execve()系统调用执行指定的ELF文件,
原先的bash进程继续返回等待刚才启动的新进程结束, 然后继续等待用户输入命令.
- 原型: int execve(const char* filename, char* const argv[], char* const envp[]);
- 三个参数分别是被执行的程序文件名, 执行参数和环境变量. Glibc 对execvp()系列调用进行了包装, 提供了execl(), execlp()
execle(), execv()和execvp()等5个不同形式的exec系列API, 它们只是在调用的参数形式上有所区别,
但最终都会调用到execve()这个系统调用函数.
- 在进入 execve() 系统调用之后, Linux内核就开始进行真正的装载工作. 在内核中, execve()系统调用相应的入口是
sys_execve(). sys_execve()进行一些参数的检查复制之后, 调用do_execve(). do_execve()读取文件的前128个字节
的文件头部之后, 然后调用 search_binary_handle().search_binary_handle()会通过判断文件头部的魔数确定文件的格式,
并且调用相应的装载处理过程. ELF可执行文件的装载处理过程叫做 load_elf_binary().
"""
4. load_elf_binary()被定义在 fs/Binfmt_elf.c
"""
- 检查elf可执行文件格式的有效性, 比如魔数, 程序头表中段的数量.
- 寻找动态链接的"interp"段, 设置动态链接器路径.
- 根据ELF可执行文件的程序头表的描述, 对ELF文件进行映射, 比如代码, 数据, 只读数据.
- 初始化ELF进程环境, 比如进程启动时EDX寄存器的地址应该是DT_FINI的地址.
- 将系统调用的返回地址修改成ELF可执行文件的入口点, 这个入口点取决于程序的链接方式, 对于静态链接的ELF可执行文件,
这个程序入口就是ELF文件的文件头中e_entry所指的地址; 对于动态链接的ELF可执行文件, 程序入口点是动态链接器.
"""
5. 装载一个PE可执行文件
"""
- 先读取文件的第一个页, 在这个页中, 包含了DOS头, PE文件头和段表.
- 检查进程地址空间中, 目标地址是否可用, 如果不可用, 则另外选一个装载地址.
- 使用段表中提供的信息, 将PE文件中所有的段一一映射到地址空间中相应的位置.
- 如果装载地址不是目标地址, 则进行rebasing.
- 装载所有PE文件所需要的DLL文件.
- 对PE文件中的所有导入符号进行解析.
- 根据PE头中指定的参数, 建立初始化栈和堆.
- 建立主线程并且启动进程.
"""
6. 段
// 单独设立 .rodata 段, 不光是在语义上支持了C++的const关键字, 而且操作系统在加载的时候可以将
// .rodata 段的属性映射成只读, 这样对于这个段的任何修改操作都会作为非法操作确定, 保证了程序的安全性.
// 正常情况下, GCC编译出来的目标文件中, 代码会被放到 .text 段, 全局变量和静态变量会被放到 .data 和 .bss 段.
// GCC 提供了一个扩展机制, 使得程序员可以指定变量所处的段.
__attribute__((section("FOO"))) int global = 42
__attribute__((section("BAR"))) void foo() {}
// objdump -x -s -d hello.o
// readelf -S hello.o
7. ELF 文件结构描述
// ELF的文件头中定义了ELF魔数, 文件机器字节长度, 数据存储方式, 版本, 运行平台, ABI版本, ELF重定位类型,
// 硬件平台, 硬件平台版本, 入口地址, 程序头入口和长度, 段表的位置和长度以及段的数量等.
// - ELF32 文件头结构体
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
// ELF 魔数 Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
// - 最开始的四个字节是所有ELF文件都必须相同的标识码, 分别为0x7F, 0x4F, 0x4c, 0x46.
// 第一个字节对应ASCII字符里面的DEL控制符, 后面3个字节恰好是ELF这3个字母的ASCII码. 这四个字节被称为ELF文件的魔数.
// - 第五个字节用来标识ELF的文件类型, 0x01表示32位的, 0x02表示是64位的.
// - 第六个字节是字节序, 规定该ELF文件是大端的还是小端的(1 -> 小端格式; 2 -> 大端格式).
// - 文件类型: EX_REL -> 可重定位文件 EX_EXEC -> 可执行文件 EX_DYN -> 共享目标文件
8. 段表
typedef struct {
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;