把ELF文件加载到内存后进行重新搬移,并且执行
ELF文件, Executable and Linkable Format,为可执行可链接的文件格式。我用这个格式的文件是因为想把汇编语言写得程序和C语言写的程序链接起来,然后放到内存里去运行。
ELF文件分为三种,可执行文件、可重定位文件、共享目标库文件。
可执行文件中,程序由段(Segment)构成,每一个段都有一个对应的段头(program header)来描述这个段(包括段在ELF文件中的偏移,大小,类型等)。所有的program header就构成了段表(program header table),一般对于可执行文件,我们只用段表,不用节表。
可重定位文件,程序由节(Section)构成,同段类似,每个节有节头,和节表。
共享目标库文件,既有段表(program header table)也有节表(section header table)。
目前,我只用了可执行文件这种类型的文件,因为我感觉它比较简单。现在说ELF文件的整个结构
最开头是ELF头,它的大小固定,里面包含了对整个ELF文件的信息,例如有多少个段表,段表大小等。再接下去,紧接着的就是程序头表,也就是段表,其中每一个程序头都包含着对应段的一些信息(比较重要的有该段在ELF文件中的偏移,要映射到内存中的虚拟地址,段大小,段的类型)。通过段表,我们把各个段按照它的要求搬移到内存中的对应位置,然后在跳转入ELF入口地址,就可以执行这个ELF文件了。
现在给出ELF头的数据结构 和 程序头的数据结构
/**********************************************************************************
----------------------------------------
| ELF header --> ELF头 |
----------------------------------------
| Program header 0 \ |
| Program header 1 >-> Program header table 程序头表
| Program header 2 / |
| ...... |
----------------------------------------
| Section 0 \ |
| Section 1 >-> 节 |
| Section 2 / |
| ...... |
----------------------------------------
| Section header 0 \ |
| Section header 1 >-> Section header table 节头表
| Section header 2 / |
| ...... |
----------------------------------------
**********************************************************************************/
/**********************************************************************************
因为ELF文件想要支持8位到32位的各种架构的机器,所以定义了如下数据类型
数据类型名称 大小(字节) 对齐 用途
Elf32_Addr 4 4 无符号程序地址
Elf32_Half 2 2 无符号中等大小整数
Elf32_Off 4 4 无符号文件偏移
Elf32_Sword 4 4 有符号大整数
Elf32_Word 4 4 无符号大整数
unsigned char 1 1 无符号小整数
**********************************************************************************/
/* ELF header */
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* 0 一共16个字节,其中开头的4个字节固定不变,位7F 45 4C 46 表示ELF文件 */
Elf32_Half e_type; /* 16 文件的类型(可读可写可执行) */
Elf32_Half e_machine; /* 18 运行改程序需要的体系结构,如Intel 80386 */
Elf32_Word e_version; /* 20 该文件的版本 */
Elf32_Addr e_entry; /* 24 程序入口地址 */
Elf32_Off e_phoff; /* 28 Program header table在文件中的偏移量(以字节位单位) */
Elf32_Off e_shoff; /* 32 Section header table在文件中的偏移量(以字节为单位) */
Elf32_Word e_flags; /* 36 IA32中为0 */
Elf32_Half e_ehsize; /* 40 ELF header的大小(以字节位单位) */
Elf32_Half e_phentsize; /* 42 Program header table中每一条目的大小 */
Elf32_Half e_phnum; /* 44 Program header table中有多少个条目 */
Elf32_Half e_shentsize; /* 46 Section header table中每一个条目大小 */
Elf32_Half e_shnum; /* 48 Section header table中有多少个条目 */
Elf32_Half e_shstrndx; /* 50 包含节名称的字符串表示第几个字节 */
}Elf32_Ehdr;
/* Program header */
typedef strcut {
Elf32_Word p_type; /* 0 当前Program header所描述的段的类型 */
Elf32_Off p_offset; /* 4 段的第一个字节在文件中的偏移 */
Elf32_Addr p_vaddr; /* 8 段的第一个字节在内存中的虚拟地址 */
Elf32_Addr p_paddr; /* 12 此项为物理地址保留 */
Elf32_Word p_filesz; /* 16 段在文件中的长度 */
Elf32_Word p_memsz; /* 20 段在内存中的长度 */
Elf32_Word p_flags; /* 24 与段相关的标志 */
Elf32_Word p_align; /* 28 规定该段在文件或者内存中的对其方式 */
}Elf32_Phdr;
然后我们在Linux下用readelf -a elf.bin来查看该ELF文件的信息
这是我自己写的一个.asm文件和一个.c文件编译链接后得到的ELF格式文件。其中在ELF头中,我们要注重程序入口地址为0x50000,在我们搬移完ELF文件后就要跳转到这个位置来执行ELF文件;还有就是程序头起点,从这个位置开始保存着我们的段的信息。
接下来是接头,不重要,跳过不看、
最后是程序头,这个可以说是最重要的了。在这里我的程序一共只有一个段,另外一个GNU_STACK大小为零,就不搬移了。这一个段在ELF文件中的偏移为0x001000,在内存中需要的虚拟地址为0x00050000,段的大小为0x000B8,可读可执行,最后一个是对齐。这就是我们要搬移的规则,必须按照这个规则搬移。
则搬移ELF文件的函数为:
test_move_elf:
mov ax, ds
push ax
mov ax, es
push ax
push esi
push edi
mov ax, SelectorElf
mov ds, ax
mov ax, SelectorPhy
mov es, ax
mov ecx, 2
.check_elf:
cmp ecx, 0
jz .move_end
dec ecx
push ecx
mov ecx, 0xB8
mov esi, 0x1000
mov edi, 0x50000
.test_move:
cmp ecx, 0
jz .test_move_end
dec ecx
mov al, byte [ds:esi]
mov byte [es:edi], al
inc esi
inc edi
jmp .test_move
.test_move_end:
pop ecx
.move_end:
pop edi
pop esi
pop ax
mov es, ax
pop ax
mov ds, ax
ret
最后我们再直接跳转到入口地址 jmp SelectorExe:0x50000就可以了
图是运行结果