文章目录
- 用C语言写内核(例)
- 二进制程序的运行方法
- ELF格式的二进制文件
- ELF文件格式
- 数据类型
- ELF header的结构
- Elf32_Phdr的结构
- ELF文件实例分析
- 将内核载入内存
- 当前的OS信息
- 当前内存规划
- 源码
- boot.inc
- mbr.s
- loader.s
- 内核
- 编译并写入硬盘
用C语言写内核(例)
源码:
int func(){
return 0;
}
int main(void){
while(1);
func();
return 0;
}
编译并查看main.c的状态:
gcc -c -o main.o main.c
file main.o
-c表示进行编译
-o指定输出文件
relocatable表示待重定位
查看待重定位符号:
nm main.o
指定可执行文件的起始虚拟地址:
ld main.o -Ttext 0xc0001500 -e main -o kernel.bin
-Ttext:指定程序起始地址
-e:指定程序入口符号(默认_start)
-o:指定输出文件
查看kernel的状态:
file kernel.bin
executable表示可执行
编译链接与一键编译链接生成文件对比:
二进制程序的运行方法
程序组成:
header.s:
header:
program_length dd program_end-program_start
start_addr dd program_start
run_addr dd 0xc0000000
body:
program_start:
mov ax, bx
jmp $
program_end:
编译并执行:
nasm -o header.bin header.s
将header.bin加载入缓存
读取第4~8字节获取程序入口地址相对于文件起始地址的偏移
读取第9~12字节获取程序运行地址
将header.bin加载到运行地址
跳转至程序运行地址+偏移执行
ELF格式的二进制文件
ELF指的是Executable and Linkable Format,可执行链接格式
最初是由UNIX系统实验室应用程序二进制接口(ABI)而开发和发行的工具接口标准委员会选择了它作为IA32体系结构上不同操作系统之间的可移植二进制文件格式,于是它就发展成为了事实上的二进制文件格式标准
在ELF规范中,把符合ELF格式协议的文件统称为“目标文件”或ELF文件
目标文件:编译后但未经链接的文件,也称为待重定位文件
ELF目标文件类型:
程序中最重要的部分就是段(segment)和节(section),它们是真正的程序体,是真真切切的程序资源
段是由节来组成,多个节经过链接就被合并成一个段了
段和节的信息也是用header来描述的
程序头是program header,节头是section header,也就是程序头表program header table和节头表section header table,里面存储的是多个程序头programheader和多个节头section header 的信息,存储形式为数组
表中每个成员都统称为条目,即entry,一个条目代表一个段或一个节的头描述信息
对于程序头表,它本质上就是用来描述段segment的,段是程序本身的组成部分
ELF文件格式
数据类型
ELF header的结构
e_ident数组
e_type
占用2字节,指定目标文件类型
e_machine
占用2字节,用来描述elf目标文件的体系结构类型
e_version:占用4字节,表示版本信息
e_entry:占用4字节,操作系统运行该程序时,控制权转交的虚拟地址
e_phoff:占用4字节,指明程序头表在文件内的字节偏移量,无则为0
e_shoff:占用4字节,指明节头表在文件内的字节偏移量,无则为0
e_flags:占用4字节,指向与处理器相关的标志
e_ehsize:占用2字节,指明elfheader的字节大小
e_phentsize:占用2字节,程序头表中每个条目的字节大小
e_phnum:程序头表中的条目数量
e_shentsize:占用2字节,节头表中每个条目的字节大小
e_shnum:节头表中的条目数量
e_shstrndx:指明string name table在节头表中的索引index
Elf32_Phdr的结构
p_type:
占用4字节,指明程序中该段的类型
p_offset:占用4字节,指明本段在文件内的起始偏移字节
p_vaddr:占用4字节,指明本段在内存中的起始虚拟地址
p_paddr:占用4字节,仅用于与物理地址相关的系统中
p_filesz:占用4字节,指明本段在文件中的大小
p_memsz:占用4字节,指明本段在内存中的大小
p_flags:
占用4字节。指明与本段相关的标志
p_align:
占用4字节,指明本段在文件和内存中的对齐方式
ELF文件实例分析
C源文件:
void func(){
while(0);
}
int main(void){
for(int i=0; i < 10; i++){
func();
}
while(1);
func();
return 0;
}
生成32位ELF文件:
gcc -c -m32 -o main.o main.c
ld -m elf_i386 -e main -Ttext 0xc0001500 -o kernel.bin main.o
readelf查看ELF文件:
ELF Header
Program Header
Section Header
查看bin文件前320字节:
bin文件分析:
ELF Header部分:0~51字节
0~15字节:e_ident数组
16~17字节:文件类型为可执行文件
18~19字节:运行平台为EM_386
20~23字节:版本信息
24~27字节:程序的虚拟入口地址为0xC0001515
28~31字节:程序头表(Program table)在文件中的偏移量为0x34
32~35字节:节头表(Section table)在文件中的偏移量为0x21D0
36~39字节:e_flags属性
40~41字节:ELF Header大小为0x34
42~43字节:程序头表中元素大小为32字节
44~45字节:程序头表中元素数量为7
46~47字节:节头表中元素大小为40字节
48~49字节:节头表中元素数量为9
50~51字节:string name table在节头表中的索引为8
Program Header部分:从52字节开始
第1个程序段为默认段
52~55字节:程序段类型为可加载程序段
56~59字节:本段在文件内的偏移量为0
60~63字节:本段在内存中的起始虚拟地址为0x8048000
64~67字节:本段在内存中的起始物理地址为0x8048000
68~71字节:本段在文件中的字节大小为0x130字节
72~75字节:本段在内存中的字节大小为0x130字节
76~79字节:本段可读
80~83字节:本段的对齐方式为0x1000
第2个程序段为实际的第1个程序段
84~87字节:程序段类型为可加载程序段
56~59字节:本段在文件内的偏移量为0x500
60~63字节:本段在内存中的起始虚拟地址为0xC0001500
64~67字节:本段在内存中的起始物理地址为0xC0001500
68~71字节:本段在文件中的字节大小为0x47字节
72~75字节:本段在内存中的字节大小为0x47字节
76~79字节:本段可读,可执行
80~83字节:本段的对齐方式为0x1000
…
Section Header部分:从8656字节开始
…
将内核载入内存
当前的OS信息
mbr.s:从硬盘中加载loader.s,并跳转至loader.s处执行
loader.s:进行物理内存检查, 开启32位保护模式开启内存分页机制, 从硬盘中加载内核文件将其移至对应位置并移交控制权
接下里需要完成从硬盘中加载内核并移交控制权
mbr.s:
字符串打印
加载loader.s
跳转至loader.s执行
loader.s:
字符串打印
获取内存信息
开启32位保护模式
字符串打印
设置页目录表
开启内存分页机制
字符串打印
新的loader.s:
字符串打印
获取内存信息
开启32位保护模式
字符串打印
将kernel加载到缓冲区
设置页目录表
开启内存分页机制
字符串打印
初始化kernel
字符串打印
跳转至kernel处执行
当前内存规划
mbr.s可用内存分布如下:
0~0x7C00:约31KB
0x7E00~0x9FC00:约608KB
loader.s在实模式下可用内存分布如下:
0~0x7E00:31.5KB
0x8600~0x9FC00:607.5KB
loader.s在保护模式下可用内存分布如下:
0~0x7E00:31.5KB
0x8600~0x100000:990.5KB
ards信息位于0x500
mbr.s位于0x7C00
loader.s位于0x7E00
页目录表位于0x100000
调整进行页对齐:
系统信息(ards信息)位于0x500
mbr.s位于0x7C00~0x7E00,占用0.5KB
loader.s位于0x2000~0x2800,占用2KB
页目录表位于0x100000~0x101000,占用4KB
缓冲区位于0x70000~0x100000,占用192KB
内核位于0x1500
源码
boot.inc
;--------- mbr & loader ---------
LBA_START_SECTOR equ 0x1
SECTOR_COUNT equ 0x4
LOADER_BASE_ADDR equ 0x200
LOADER_OFF_ADDR equ 0x0
LOADER_ADDR equ 0x2000
ARDS_ADDR equ 0x500
;--------- gdt ---------
DESC_G_4K equ 1_00000000000000000000000b
DESC_D_32 equ 1_0000000000000000000000b
DESC_L equ 0_000000000000000000000b
DESC_AVL equ 0_00000000000000000000b
DESC_LIMIT_CODE2 equ 1111_0000000000000000b
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2 equ 0000_000000000000000b
DESC_P equ 1_000000000000000b
DESC_DPL_0 equ 00_0000000000000b
DESC_DPL_1 equ 01_0000000000000b
DESC_DPL_2 equ 10_0000000000000b
DESC_DPL_3 equ 11_0000000000000b
DESC_S_CODE equ 1_000000000000b
DESC_S_DATA equ DESC_S_CODE
DESC_S_sys equ 0_000000000000b
DESC_TYPE_CODE equ 1000_00000000b ;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.
DESC_TYPE_DATA equ 0010_00000000b ;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.
;code section
DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00
;data section
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
;video section
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b
;-------------- paragraph selection ---------------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
;-------------- loader & kernel --------------
Memo_Buffer_Start equ 0x70000
Memo_Buffer_Size equ 0x30000
Kernel_Sector_Start equ 10
Kernel_Sector_Count equ 200
PAGE_DIR_TABLE_POS equ 0x100000
;-------------- page tab --------------
PG_P equ 1b
PG_RW_R equ 00b
PG_RW_W equ 10b
PG_US_S equ 000b
PG_US_U equ 100b
;------------- program type 定义 --------------
PT_NULL equ 0
PT_LOAD equ 1
mbr.s
;主引导程序
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov ax, 0xB800
mov gs, ax
mov sp,0x7c00
; 清屏 利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
mov ax, 0x600
mov bx, 0x700
mov cx, 0 ; 左上角: (0, 0)
mov dx, 0x184f ; 右下角: (80,25),
; VGA文本模式中,一行只能容纳80个字符,共25行。
; 下标从0开始,所以0x18=24,0x4f=79
int 0x10 ; int 0x10
;;;;;;;;; 打印字符串 ;;;;;;;;;;;
mov cx, sx - msg
mov si, msg
mov di, 0
show_str:
mov byte al, [si]
mov byte ah, [sx]
mov word [gs:di], ax
inc si
add di, 2
loop show_str
jmp L0
msg db "enter mbr"
sx db 0x24
;;;;;;;;; 打字字符串结束 ;;;;;;;;;;;;;;;
L0:
push ds
push di
mov eax, LBA_START_SECTOR
push eax
mov ax, SECTOR_COUNT
push ax
mov ax, LOADER_BASE_ADDR
push ax
mov ax, LOADER_OFF_ADDR
push ax
call read_disk
add sp, 10
pop di
pop ds
jmp LOADER_ADDR
;;;;;;;;; read disk ;;;;;;;;;
; LBA: [bp+10]
; sector count: [bp+8]
; destination: sec=[bp+6], off=[bp+4]
read_disk:
push bp
mov bp, sp
;sector_count
mov dx,0x1f2
mov ax, [bp+8]
out dx, al
;sector_addr
mov dx, 0x1f3
mov ax, [bp+10]
out dx, al
mov dx, 0x1f4
mov al, ah
out dx, al
mov dx, 0x1f5
mov ax, [bp+12]
out dx, al
mov dx, 0x1f6
mov al, ah
and al, 0x0f
or al, 0xe0
out dx, al
;command_write
mov dx, 0x1f7
mov al, 0x20
out dx, al
disk_test:
nop ;give disk a moment
in al, dx
and al, 0x88 ;7: BUSY, 3: READY
cmp al, 0x08 ; (BUSY=0 & READY=1) or not?
jnz disk_test
;data_read:
mov ax, [bp+8]
mov dx, 256
mul dx
mov cx, ax
mov bx, [bp+4]
mov ax, [bp+6]
mov ds, ax
mov dx, 0x1f0
go_on_read:
in ax, dx
mov [bx], ax
add bx,2
loop go_on_read
mov sp, bp
pop bp
ret
;;;;;;;;; read disk ;;;;;;;;;
times 510-($-$$) db 0
db 0x55,0xaa
loader.s
%include "boot.inc"
section loader vstart=LOADER_ADDR
;;;;;;;;; 打印字符串 ;;;;;;;;;;;
mov eax, row_index
mov cx, sx-msg1
mov si, msg1
mov di, 160
show_str0:
mov byte al, [si]
mov byte ah, [sx]
mov word [gs:di], ax
inc si
add di, 2
loop show_str0
jmp get_memoinfo
msg1 db "enter loader"
sx db 0x24
;;;;;;;;; 打字字符串结束 ;;;;;;;;;;;;;;;
;----- get memory information -----
get_memoinfo:
;----- get all: 0xE820
jmp_e820:
xor ebx, ebx
mov edx, 0x534d4150
mov di, ARDS_ADDR+2
ards_e820:
mov eax, 0x0000e820
mov ecx, 20
int 0x15
jc Error
add di, cx
inc word [ARDS_ADDR]
cmp ebx, 0
jnz ards_e820
show_memoinfo:
mov ax, 0xb800
mov gs, ax
mov cx, [ARDS_ADDR]
mov si, ARDS_ADDR+0x2
mov di, 320
loop_print:
push di
addr_info:
mov edx, [si]
mov di, memo_addr+22
call BtH
shr edx, 16
mov di, memo_addr+18
call BtH
mov edx, [si+4]
mov di, memo_addr+14
call BtH
shr edx, 16
mov di, memo_addr+10
call BtH
size_info:
mov edx, [si+8]
mov di, memo_size+22
call BtH
shr edx, 16
mov di, memo_size+18
call BtH
mov edx, [si+12]
mov di, memo_size+14
call BtH
shr edx, 16
mov di, memo_size+10
call BtH
type_info:
mov edx, [si+16]
mov di, memo_type+14
call BtH
shr edx, 16
mov di, memo_type+10
call BtH
pop di
push cx
push si
push di
mov cx, BtH_Table-memo_addr
mov ah, 0x24
mov si, memo_addr
info_print:
mov al, [si]
mov [gs:di], ax
inc si
add di, 0x2
loop info_print
pop di
add di, 160
pop si
add si, 20
pop cx
dec cx
cmp cx, 0
jne loop_print
jmp Jmp_there
memo_addr db "ADDR:0xxxxxxxxxxxxxxxxx. "
memo_size db "SIZE:0xxxxxxxxxxxxxxxxx. "
memo_type db "TYPE:0xxxxxxxxx."
BtH_Table db "0123456789ABCDEF"
;params: dx, di
BtH:
push ax
push bx
push edx
push di
mov bh, 0
mov bl, dl
and bl, 00001111b
mov al, [BtH_Table+bx]
mov [di], al
dec di
mov bl, dl
shr bl, 4
mov al, [BtH_Table+bx]
mov [di], al
dec di
mov bl, dh
and bl, 00001111b
mov al, [BtH_Table+bx]
mov [di], al
dec di
mov bl, dh
shr bl, 4
mov al, [BtH_Table+bx]
mov [di], al
pop di
pop edx
pop bx
pop ax
ret
Error:
;----- open A20 -----
Jmp_there:
in al, 0x92
or al, 00000010b
out 0x92, al
;----- load gdtr-----
lgdt [gdt_ptr]
;----- cr0 set -----
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
;----- clear instruction pipeline -----
jmp dword SELECTOR_CODE:p_m_start
[bits 32]
p_m_start:
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp, LOADER_ADDR
mov ax, SELECTOR_VIDEO
mov gs, ax
mov ecx, msg2_end-msg2
mov si, msg2
mov di, 1280
show_str2:
mov byte al, [si]
mov byte [gs:di], al
inc si
add di, 2
loop show_str2
;---------- load kernel into buffer ----------
mov eax, msg4
push eax
mov eax, msg4_end
push eax
mov eax, [row_index]
push eax
call show_str
add esp, 8
mov eax, [row_index]
add eax, 160
mov [row_index], eax
mov eax, Memo_Buffer_Start
push eax
mov eax, Kernel_Sector_Start
push eax
mov ax, Kernel_Sector_Count
push ax
call read_disk_32
add esp, 12
;---------- open pagination_mode ----------
pagination_mode:
call setup_page
sgdt [gdt_ptr]
mov eax, [gdt_ptr + 2]
or dword [eax + 0x18 + 4], 0xc0000000
add dword [gdt_ptr + 2], 0xc0000000
set_cr3:
mov eax, PAGE_DIR_TABLE_POS
mov cr3, eax
set_cr0_page:
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
reload_gdt:
lgdt [gdt_ptr]
mov eax, msg3
push eax
mov eax, msg3_end
push eax
mov eax, [row_index]
push eax
call show_str
add esp, 12
mov eax, [row_index]
add eax, 160
mov [row_index], eax
;---------- kernel loading ----------
mov eax, msg5
push eax
mov eax, msg5_end
push eax
mov eax, [row_index]
push eax
call show_str
add esp, 12
mov eax, [row_index]
add eax, 160
mov [row_index], eax
mov eax, Memo_Buffer_Start
push eax
call elf_program_loader
add esp, 4
push eax
mov eax, msg6
push eax
mov eax, msg6_end
push eax
mov eax, [row_index]
push eax
call show_str
add esp, 12
mov eax, [row_index]
add eax, 160
mov [row_index], eax
pop eax
;jmp $
jmp eax
;---------- Data ----------
;string
row_index dd 1440
msg2 db "enter protect mode"
msg2_end:
msg3 db "enable pagination mode"
msg3_end:
msg4 db "load kernel......"
msg4_end:
msg5 db "init kernel......"
msg5_end:
msg6 db "enter kernel"
msg6_end:
;GDT_CREATE
GDT_BASE:
dd 0x00000000, 0x00000000
CODE_DESC:
dd 0x0000FFFF, DESC_CODE_HIGH4
DATA_DESC:
dd 0x0000FFFF, DESC_DATA_HIGH4
VIDEO_DESC:
dd 0x80000007, DESC_VIDEO_HIGH4
GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 30 dq 0
;SELECTOR_SET
SELECTOR_CODE equ 0000000000001_000b + TI_GDT + RPL0
SELECTOR_DATA equ 0000000000010_000b + TI_GDT + RPL0
SELECTOR_VIDEO equ 0000000000011_000b + TI_GDT + RPL0
;GDT_pointer
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
setup_page:
mov ecx, 1024
mov esi, 0
clear_page_dir:
mov dword [PAGE_DIR_TABLE_POS + 4*esi], 0
inc esi
loop clear_page_dir
create_pde:
mov eax, PAGE_DIR_TABLE_POS
add eax, 0x1000
mov ebx, eax
set_1_769_1024_PDE:
or eax, PG_US_U | PG_RW_W | PG_P
mov [PAGE_DIR_TABLE_POS + 0x0], eax
mov [PAGE_DIR_TABLE_POS + 0xc00], eax
sub eax, 0x1000
mov [PAGE_DIR_TABLE_POS + 0xffc], eax
mov ecx, 254
mov edx, PAGE_DIR_TABLE_POS
mov edi, 769
add eax, 0x2000
set_kernel_769~1022_PDE:
mov [edx + 4*edi], eax
inc edi
add eax, 0x1000
loop set_kernel_769~1022_PDE
set_0_PTE:
mov ecx, 256
mov edx, PAGE_DIR_TABLE_POS
add edx, 0x1000
mov edi, 0
and eax, 0x00000111
create_pte:
mov [edx + 4*edi], eax
inc edi
add eax, 0x1000
loop create_pte
ret
;---------- routines ----------
;param: start(dd),end(dd), dst(dd)
show_str:
push ebp
mov ebp, esp
mov ax, SELECTOR_VIDEO
mov gs, ax
mov edx, [esp+16]
mov ecx, [esp+12]
sub ecx, edx
mov esi, edx
mov edi, [esp+8]
show_str_loop:
mov byte al, [esi]
mov byte [gs:edi], al
inc esi
add edi, 2
loop show_str_loop
mov esp, ebp
pop ebp
ret
;param: dst addr(dd), sector start(dd), sector count(dw)
; dst: [ebp+14]
; LBA: [ebp+10]
; sector count: [ebp+8]
read_disk_32:
push ebp
mov ebp, esp
;sector_count
mov dx,0x1f2
mov ax, [ebp+8]
out dx, al
;sector_addr
mov dx, 0x1f3
mov ax, [bp+10]
out dx, al
mov dx, 0x1f4
mov al, ah
out dx, al
mov dx, 0x1f5
mov ax, [bp+12]
out dx, al
mov dx, 0x1f6
mov al, ah
and al, 0x0f
or al, 0xe0
out dx, al
;command_write
mov dx, 0x1f7
mov al, 0x20
out dx, al
disk_test:
nop ;give disk a moment
in al, dx
and al, 0x88 ;7: BUSY, 3: READY
cmp al, 0x08 ; (BUSY=0 & READY=1) or not?
jnz disk_test
;data_read:
mov ax, [ebp+8]
mov dx, 256
mul dx
mov cx, ax
mov ebx, [ebp+14]
mov dx, 0x1f0
go_on_read:
in ax, dx
mov [ebx], ax
add ebx,2
loop go_on_read
mov esp, ebp
pop ebp
ret
;param: addr(dd)
;return: jmp addr(dd, eax)
elf_program_loader:
push ebp
mov ebp, esp
;elf addr=eax
mov eax, [ebp+8]
;program_table_addr=ebx
mov ebx, [eax+28]
;program_table_element_nums=ecx
xor ecx, ecx
mov cx, [eax+44]
;program_table_element_size=edx
xor edx, edx
mov dx, [eax+42]
;skip number 1 default segment
add ebx, eax
add ebx, edx
dec ecx
.each_segment:
cmp byte [ebx+0], PT_LOAD
jne .PTNULL
;push dst
mov esi, [ebx+8]
push esi
;push source
mov esi, eax
add esi, [ebx+4]
push esi
;push length
mov esi, [ebx+16]
push esi
call mem_cpy
add esp, 12
.PTNULL:
add ebx, edx
loop .each_segment
mov eax, [eax+24]
mov esp, ebp
pop ebp
ret
;param: dst(dd), source(dd), length(dd)
mem_cpy:
cld;sequential transmission
push ebp
mov ebp, esp
push ecx
mov edi, [ebp+16]
mov esi, [ebp+12]
mov ecx, [ebp+8]
rep movsb
pop ecx
mov esp, ebp
pop ebp
ret
内核
void func(){
while(0);
}
int main(void){
for(int i=0; i < 10; i++){
func();
}
while(1);
func();
return 0;
}
编译并写入硬盘
nasm -I OS/include/ -o OS/boot/mbr.bin OS/boot/mbr.s
nasm -I OS/include/ -o OS/boot/loader.bin OS/boot/loader.s
sudo gcc -c -m32 -o OS/kernel/main.o OS/kernel/main.c && sudo ld -m elf_i386 -e main -Ttext 0xc0001500 -o OS/kernel/kernel.bin OS/kernel/main.o
dd if=OS/boot/mbr.bin of=bochs/hd60M.img bs=512 count=1 seek=0 conv=notrunc
dd if=OS/boot/loader.bin of=bochs/hd60M.img bs=512 count=4 seek=1 conv=notrunc
dd if=OS/kernel/kernel.bin of=bochs/hd60M.img bs=512 count=200 seek=10 conv=notrunc
启动bochs运行: