《操作系统真象还原》第三篇:解析ELF文件头,加载内核

第三篇: 解析ELF文件头,加载内核

  • 第三篇: 解析ELF文件头,加载内核
    • ELF文件头解析
      • 数据结构
      • struct Elf32_Ehdr
      • struct Elf32_Phdr
    • 加载内核
      • 编写内核
      • 将内核载入内存

ELF文件头解析

数据结构

elf header中的数据类型

数据结构名称 字节大小 对齐 意义
Elf32_Half 2 2 无符号中等大小的整数
Elf32_Word 4 4 无符号大整数
Elf32_Addr 4 4 无符号程序运行地址
Elf32_Off 4 4 无符号的文件偏移量

struct Elf32_Ehdr

elf header结构如下

类型 名称 描述
unsigned char e_ident[16] 魔数和类型
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_Half e_ehsize elf header的大小
Elf32_Half e_phentsize 程序头表每个条目的大小
Elf32_Half e_phnum 程序头表的条目数量
Elf32_Half e_shentsize 节头表每个条目的大小
Elf32_Half e_shnum 节头表的条目数量
Elf32_Half e_shstmdx string name table在节头表的索引

struct Elf32_Phdr

program header结构如下

类型 名称 描述
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 表示对齐方式

加载内核

编写内核

初始内核很简单,就是一个无限循环

int main(void){
	while(1);
	return 0;
}

通过以下命令编译、链接并写入硬盘

gcc -m32 -c -o main.o main.c;
ld -m elf_i386 main.o -Ttext 0xc0001500 -e main -o kernel.bin;
dd if=kernel.bin of=/home/alex/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc;

将内核载入内存

首先将内核从硬盘读入内存

;------加载kernel------
	mov eax, KERNEL_START_SECTOR ;kernel.bin 所在的扇区号
	mov ebx, KERNEL_BIN_BASE_ADDR ;写入到ebx指定的地址
	
	mov ecx, 200 ;读入的扇区数

	call rd_disk_m_32
	;功能:读取硬盘n个扇区 
rd_disk_m_32:		;eax = LBA扇区号 bx = 将数据写入的内存地址 cx = 读入的扇区数
    mov esi,eax		;备份eax
    mov edi,ecx		;备份cx
;读写硬盘
;第一步:设置要读取的扇区数
    mov dx,0x1f2	
    mov al,cl
    out dx,al
   
    mov eax,esi		;恢复eax
    
;第二步:将LBA地址存入0x1f3 ~ 0x1f6
    
    ;LBA地址7~0位写入端口0x1f3
    mov dx,0x1f3
    out dx,al
    
    ;LBA地址15~8位写入端口0x1f4
    mov cl,8
    shr eax,cl
    mov dx,0x1f4
    out dx,al
    
    ;LBA地址23~16位写入端口0x1f5
    shr eax,cl
    mov dx,0x1f5
    out dx,al
    
    shr eax,cl
    and al,0x0f		;LBA第24~27位
    or al,0xe0		;设置7~4位为1110,表示LBA模式
    mov dx,0x1f6
    out dx,al    
    
;第三步:向0x1f7端口写入读命令,0x20
    mov dx,0x1f7
    mov al,0x20
    out dx,al
    
;第四步:检测硬盘状态
.not_ready:
	;同一端口,写时表示写入命令数,读时表示读入硬盘状态
  	nop
  	in al,dx
  	and al,0x88		;第3位为1表示硬盘控制器已准备好数据传输
  					;第7位为1表示硬盘忙
  	cmp al,0x08		
  	jnz .not_ready	;若未准备好,继续等
  	
 ;第五步:从0x1f0端口读数据
  	mov ax,di
  	mov dx,256
  	mul dx
  	mov cx,ax
;di为要读取的扇区数,一个扇区有512字节,每次读入2个字节
;共需di*512/2次,所以di*256
  	mov dx,0x1f0

.go_on_read:
	in ax,dx
	mov [ebx],ax
	add ebx,2
	loop .go_on_read
	ret

然后在开启分页后,解析内核文件,并跳转进内核执行

	call kernel_init
	mov esp, 0xc009f000
	jmp KERNEL_ENTRY_POINT
	
	;-----将kernel.bin中的segment拷贝到编译的地址-------
kernel_init:
	xor eax, eax
	xor ebx, ebx	;ebx记录程序头表的地址
	xor ecx, ecx	;ecx记录程序头表中program header数量
	xor edx, edx	;edx记录program header尺寸,即e_phentsize
	
	mov dx, [KERNEL_BIN_BASE_ADDR + 42]		;e_phentsize
	mov ebx, [KERNEL_BIN_BASE_ADDR + 28]	;e_phoff
	add ebx, KERNEL_BIN_BASE_ADDR			
	mov cx, [KERNEL_BIN_BASE_ADDR + 44] 	;e_phnum
	
.each_segment:
	cmp byte [ebx + 0], PT_NULL
;若p_type等于PT_NULL说明改程序头未使用
	je .PTNULL
	
	;为函数memcpy(dst,src,size)压入参数,顺序为从右往左
	;第三个参数:size
	push dword [ebx + 16]	;p_filesz
	
	;第二个参数:src
	mov eax, [ebx + 4]	;p_offset
	add eax, KERNEL_BIN_BASE_ADDR
	push eax
	
	;第三个参数:dst
	push dword [ebx + 8] ;p_vadrr
	
	call mem_cpy
	add esp, 12	;清除栈中的三个参数

.PTNULL:
	add ebx, edx	;令ebx指向下一个program header
	loop .each_segment
	ret
	
;------mem_cpy(dst,src,size)------
;输入:栈中三个参数(dst, src, size)
;输出:无
;---------------------------------
mem_cpy:
	cld
	push ebp
	mov ebp, esp
	push ecx	;rep指令用到了ecx,故先备份
	
	mov edi, [ebp + 8]	;dst
	mov esi, [ebp + 12]	;src
	mov ecx, [ebp + 16]	;size
	rep movsb			;逐字节拷贝
	
	;恢复环境
	pop ecx 
	pop ebp
	ret

你可能感兴趣的:(操作系统,linux,汇编,系统架构)