稍有经验的同学肯定知道,编译得到的二进制文件中有text, bss, data, rodata等段。我们今天试着剖析一下这些概念。

segment & section

segmentsection在中文某些场景下都被翻译成,很容易搞混。我们今天这里不再翻译,直接使用这两个词。

代码经编译器编译链接后得到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对应关系:

segment-section

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编程相关内容对接上了,我们也不展开了。