知识点
1. phnum指的是什么?有什么意义?如何使用?
phnum = program header number
program header
程序头是专门用来描述段信息的,这个段不是内存中的段,内存中的段是记录在全局描述符表中的。程序头描述的段是磁盘上程序中的一个段,常见的如代码段和数据段,下面是其结构
typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;
p_offset表示该segment相对ELF文件开头的偏移量
p_filesz表示该segment在ELF文件中的大小
p_memsz表示该segment加载到内存后所占用的大小
p_filesz和p_memsz的大小只有在少数情况下不相同,如包含.bss section的segment,因为.bss section在ELF文件中不占用空间,但在内存中需要占用相应字节大小的空间
通过p_offset和p_filesz两个成员就可以获得相应segment中的所有内容,所以这里就不再需要section header的支持,但需要ELF文件头中的信息来确定program header表(每个program header的大小相同)的开头位置,因此ELF文件头(它包含在第一个LOAD segment中)也要加载到内存中 [1]
ELF header
#define EI_NIDENT (16)
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;
e_phentsize表示Program header table entry size
e_phoff表示Program header的offset
获取对应program header的计算方式 = e_phoff + e_phentsize × i
2. fseek函数 [3]
头文件
#include <stdio.h>
函数原型
int fseek(FILE *stream, long offset, int fromwhere);
参数
- stream:指向打开的文件指针。
- offset:以基准点为起始点的偏移量。
- fromwhere:基准点。
其中基准点包括这三个枚举
- SEEK_SET:文件头
- SEEK_CUR:当前位置
- SEEK_END:文件件尾
返回值
成功返回0,失败返回-1
作用
重定位流(数据流/文件)的内部位置指针。
描述
函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置。如果执行失败,则不改变stream指向的位置。
3. static函数 [4]
函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。由于static变量的以上特性,可实现一些特定功能,如统计次数功能
4. sbss bss段 内容,ELF里和内存里的存储
.bss 表示未初始化和初始化为 0的全局变量的一块内存区域。在程序载入时由内核清零。数据段属于静态内存分配。从可执行程序的角度来说,如果一个数据未被初始化,就不需要为其分配空间,所以.data 和.bss 的区别就是 .bss 并不占用可执行文件的大小,仅仅记录需要用多少空间来存储这些未初始化的数据,而不分配实际空间[5]。
.sbss是小的BSS段,用于存放“近”数据,即使用短指针(near)寻址的数据。有利于小的对象组合到单个可以直接寻址的区域。《程序员的自我修养--链接、装载与库》一书的3.3.4节说:“以前用过的一些名字如.sdata、.tdesc、sbss、lit4、lit8、reginfo、gptab、liblist、.confict。可以不用理会这些段,它们已经被遗弃了。”话虽然这么说,不过实际使用中还是会碰到[6]。
5. mmap函数
参考资料
[1] 程序的本质之二ELF文件的文件头、section header和program header
[2] Linux内核如何装载和启动一个可执行程序
[3] c语言fseek函数的总结
[4] c语言中static 函数和普通函数的区别?
[5] 再谈应用程序分段: 数据段、代码段、BSS段以及堆和栈
[6] .text、.data、.bss、sbss、scommon段
[7] 全网首发】万字图文 | 你写的代码是如何跑起来的?