elf header中的数据类型
数据结构名称 | 字节大小 | 对齐 | 意义 |
---|---|---|---|
Elf32_Half | 2 | 2 | 无符号中等大小的整数 |
Elf32_Word | 4 | 4 | 无符号大整数 |
Elf32_Addr | 4 | 4 | 无符号程序运行地址 |
Elf32_Off | 4 | 4 | 无符号的文件偏移量 |
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在节头表的索引 |
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