c语言补充说明
稍有经验的同学肯定知道,编译得到的二进制文件中有text, bss, data, rodata等段。我们今天试着剖析一下这些概念。
segment & section
segment
和section
在中文某些场景下都被翻译成段
,很容易搞混。我们今天这里不再翻译,直接使用这两个词。
代码经编译器编译链接后得到ELF文件,操作系统将ELF文件加载至内存中并运行。这里涉及到2个过程,链接
与装载
。
- 代码编译得到可重定位文件(.o)后,链接器(ld)对可重定位文件中的符号进行地址重定位,并最终得到可执行文件。
- 操作系统将可执行文件通过分页机制加载至内存之后执行。加载时,不同的页对应的属性(访问权限)不同,比如代码部分是只读可执行的,其它段是可写的等。
ELF文件作为2个过程的桥梁,就存在链接视图(Linking View)
和执行视图(Execution View)
两种视图。其中,section对应链接过程,segment对应装载(运行)过程。
链接时,根据不同的功能,ELF文件会划分位多个section。装载时,操作系统以页(4K)为单位进行,如果不足一个页,也要进行对齐。这时如果有多个section,每个section占用1个页,几乎每个section都会在对齐时导致一部分内存空间浪费。而对于操作系统来说,只关注每个page的读写等属性。所以操作系统就将相同读写书写的section对应为一个segment进行加载。
readelf -l可以查看section与segment对应关系:
ELF文件格式
ELF header
ELF header格式是固定的,在/usr/include/elf.h中有定义:
/* The ELF file header. This appears at the start of every ELF file. */
#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_entry
就是程序总入口地址e_phoff
对应程序头表(Program header table)在文件中的偏移量e_phentsize
程序头表中每个条目(entry)的大小e_phnum
程序头表中条目数量,对应segment个数
Program header table
程序头表(Program header table)保存运行视图中的segment信息。
/* Program segment 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在文件内的偏移量p_vaddr
对应segment加载到虚拟内存(线性内存)中的地址p_filesz
对应segment在文件中的大小
操作系统解析ELF文件中的这些信息,然后从文件对应偏移地址处,将对应segment拷贝至内存中。拷贝完成后开始执行。
Section header table
节头表(section header table)保存链接视图中section信息。
堆 & 栈
操作系统运行程序时,除了根据ELF文件中的segment加载进内存外,还需要为程序分配堆(Stack)和栈(Heap)空间。而且也知道堆向下增长,栈向上增长。
好了,这部分和linux编程相关内容对接上了,我们也不展开了。