Linux内核系列—11.操作系统开发之ELF格式

ELF文件的结构如下图所示:

ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Sections)和节头表(Section header table)。

实际上,一个文件中不一定包含全部这些内容,而且它们的位置也未必如上图所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。

ELF header的格式如下代码所示:

#define EI_NIDENT  16

typedef struct{
    unsigned char    e_ident[EI_NIDENT];
    Elf32_Half          e_type;
    Elf32_Half          e_machine;
    Elf32_word        e_version;
    Elf32_Addr         e_entry;
    Elf32_Off           e_phoff;
    Elf32_Off           e_shoff;
    Elf32_Word        e_flags;
    Elf32_Haif          e_ehsize;
    Elf32_Haif          e_phentsize;
    Elf32_Haif          e_phnum;
    Elf32_Haif          e_shentsize;
    Elf32_Haif          e_shnum;
    Elf32_Haif          e_shstrndx;
}Elf32_Ehdr;

其中各类型的说明见下表,由于ELF文件力求支持从8位到32位不同架构的处理器,所以才定义了下表中这些数据类型,从而让文件格式与机器无关。

下面看一下ELF header中各项的意义。foobar文件:

最开头是16字节的e_ident,其中包含用以表示ELF文件的字符,以及其他一些与机器无关的信息。

开头的4字节是固定不变的,第1个字节值为0x7F,紧跟着就是ELF三个字符,这4字节表明这个文件是个ELF文件。

1.e_type——标识的是该文件的类型,取值就不一一列出了。文件foobar的e_type是2,表明它是一个可执行文件。

2.e_machine——foobar中此项的值为3,表明运行该程序需要的体系结构为Intel 80386.

3.e_version——文件的版本。

4.e_entry——程序的入口地址。文件foobar的入口地址为0x80480A0.

5.e_phoff——Program header table在文件中的偏移量(以字节计数),这里的值是0x34.

6.e_shoff——Section header table在文件中的偏移量(以字节计数),这里的值是0x1C0.

7.e_flags——对IA32而言,此项为0.

8.e_ehsize——ELF header大小(以字节计数),这里值为0x34.

9.e_phentsize——Program header table中每一个条目(一个Program header)的大小。这里值为0x20.

10.e_phnum——Program header table中有多少个条目,这里有3个。

11.e_shentsize——Section header table中每一个条目(一个Section header)的大小,这里值为0x28.

12.e_shnum——Section header table中有多少个条目,这里有6个。

13.e_shstrndx——包含节名称的字符串表是第几个节(从零开始数)。这里值为5,表示第5个节包含节名称。

我们看到,Program header table在文件中的偏移量(e_phoff)为0x34,而ELF header大小(e_ehsize)也是0x34,可见ELF header后面紧接着就是Program header table。Program header数据结构如下:

typedef struct{
    Elf32_Word        p_type;
    Elf32_Off            p_offset;
    Elf32_Addr         p_vaddr;
    Elf32_Addr         p_paddr;
    Elf32_Word        p_filesz;
    Elf32_Word        p_memsz;
    Elf32_Word        p_flags;
    Elf32_Word        p_align;
}Elf32_Phdr;

实际上Program header描述的是系统准备程序运行所需的一个段或其他信息。程序头表中共有三项(e_phnum=3),偏移分别是0x34~0x53、0x54~0x73和0x74~0x93.

1.p_type——当前Program header所描述的段的类型。

2.p_offset——段的第一个字节在文件中的偏移。

3.p_vaddr——段的第一个字节在内存中的虚拟地址。

4.p_paddr——在物理地址定位相关的系统中,此项是为物理地址保留。

5.p_filesz——段在文件中的长度。

6.p_memsz——段在内存中的长度。

7.p_flags——与段相关的标志。

8.p_align——根据此项值来确定段在文件以及内存中如何对齐。

Program header描述的是一个段在文件中的位置、大小以及它被放进内存后所在的位置和大小。如果我们想把一个文件加载进内存的话,需要的正是这些信息。

在foobar中共有三个Program header,其取值如下表所示:

根据这些信息,我们很容易知道foobar在加载进内存之后的情形,如下图:

时间: 2024-12-19 07:26:28

Linux内核系列—11.操作系统开发之ELF格式的相关文章

Linux内核系列—10.操作系统开发之内核HelloWorld

a.我们先来体验一下在Linux下用汇编编程的感觉,见代码 [section .data] ; 数据在此 strHello db "Hello, world!", 0Ah STRLEN equ $ - strHello [section .text] ; 代码在此 global _start ; 我们必须导出 _start 这个入口,以便让链接器识别 _start: mov edx, STRLEN mov ecx, strHello mov ebx, 1 mov eax, 4 ; sy

Linux内核系列—8.操作系统开发之时钟中断

外部中断的情况复杂一些,因为需要建立硬件中断与向量号之间的对应关系.外部中断分为不可屏蔽中断(NMI)和可屏蔽中断两种,分别由CPU的两根引脚NMI和INTR来接收.如下图所示: 可屏蔽中断与CPU的关系是通过对可编程中断控制器8259A建立起来的.8259A可以认为它是中断机制中所有外围设备的一个代理.在BIOS初始化它的时候,IRQ0~IRQ7被设置为对应向量号08h~0Fh,在保护模式下向量号08h~0Fh已经被占用了,所以我们不得不重新设置主从8259A. 对8259A的设置并不复杂,通

Linux内核系列—5.操作系统开发之特权级CPL、DPL、RPL

CPL——当前执行的程序或任务的特权级,它被存储在cs和ss的第0位和第1位上. DPL——段或者门的特权级,如果是数据段DPL则规定了可以访问此段的最低特权级 RPL——通过段选择子的第0位和第1位表现出来的.处理器通过检查RPL和CPL来确认一个访问请求是否合法.RPL保证了操作系统不会越俎代庖地代表一个程序去访问一个段. 我们先来展示一下特权级错误访问版本. 先把LABEL_DESC_DATA对应的段描述符的DPL修改为1: LABEL_DESC_DATA: Descriptor 0, D

linux内核系列(一)编译安装Linux内核 2.6.18

1.配置环境 操作系统:CentOS 5.2 下载linux-2.6.18版本的内核,网址:http://www.kernel.org 说明:该编译文档适合2.6.18以上的Linux内核版本,只需所编译的 Linux内核版本不能低于Linux操作系统自身的内核版本,不然会遇到很多问题:   2.开始编译 cp  ./ linux-2.6.18.tar.gz  /usr/src/ tar –zxvf ./linux-2.6.18.tar.gz cd /usr/src/linux-2.6.18 /

Linux内核3.11的socket busy poll机制避免睡眠切换

Linux的网络协议栈非常独立,上下通过两个接口分别和用户态以及设备相连,也可以看作是北向和南向接口...北向通过socket接口,南向通过qdisc接口(你可以认为是上层的netdev queue,对于接收接口,NAPI的poll队列则是另一个例子),不管是socket还是qdisc,都是基于队列来管理的,也就是说,三个部分是独立的,socket只能看到读写队列,而看不到协议栈本身,socket在读一个数据的时候,它取的是队列里面的数据,至于说这个数据是谁放进去的,它并不知道,是不是协议栈放进

linux内核系列(一)内核数据结构之链表

双向链表 传统链表与linu内核链表的区别图: 图一 图二 从上图中看出在传统链表中各种不同链表间没有通用性,因为各个数据域不同,而在linux内核中巧妙将链表结构内嵌到数据域结构中使得不同结构之间能连接起来: 链表的常用操作 内核中链表实现文件路径:include/linux/list.h 链表结构定义 struct list_head {     struct list_head *next, *prev; }; 获取结构入口地址(list_entry) #define list_entry

Linux内核系列—操作系统开发之进入32位保护模式

源码如下: ; ========================================== ; pmtest1.asm ; 编译方法:nasm pmtest1.asm -o pmtest1.bin ; ========================================== %include "pm.inc" ; 常量, 宏, 以及一些说明 org 07c00h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 属

Linux内核系列—12.d.操作系统开发之扩充内核 ●

现在把esp.GDT等内容放进内核中,我们现在可以用C语言了,只要能用C,我们就避免用汇编. 下面看切换堆栈和GDT的关键代码: ; 导入函数 extern cstart ; 导入全局变量 extern gdt_ptr [SECTION .bss] StackSpace resb 2 * 1024 StackTop: ; 栈顶 ; 把 esp 从 LOADER 挪到 KERNEL mov esp, StackTop ; 堆栈在 bss 段中 sgdt [gdt_ptr] ; cstart() 中

Linux内核系列—12.a.操作系统开发之从Loader到内核

Loader要做两项工作,我们先来做第一项,把内核加载到内存: 1.加载内核到内存. 2.跳入保护模式. 首先编译无内核时: nasm boot.asm -o boot.bin nasm loader.asm -o loader.bin dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc sudo mount -o loop a.img /mnt/hgfs/ sudo cp loader.bin /mnt/hgfs/ -v sudo umoun