You are on page 1of 14

DAY 3

1) Asmhead.asm

; haribote-os

BOTPAK EQU 0x00280000 ; 加载 bootpack


DSKCAC EQU 0x00100000 ; 磁盘缓存的位置
DSKCAC0 EQU 0x00008000 ; 实模式磁盘缓存的位置

; 有关 BOOT_INFO
CYLS EQU 0x0ff0 ; 设置启动区
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 关于颜色数目的信息,颜色的位数
SCRNX EQU 0x0ff4 ; 分辨率 X
SCRNY EQU 0x0ff6 ; 分辨率 Y
VRAM EQU 0x0ff8 ; 图像缓冲区的起始位置

; 使用 linker script 指定起始地址


; ORG 0xc200 ; 程序被加载的内存地址

[SECTION .btext]
[BITS 16]
entry:
; 设置屏幕模式
MOV AL, 0x13 ; VGA 显卡,320x200x8 bit
MOV AH, 0x00
INT 0x10

MOV BYTE [VMODE], 8 ; 屏幕的模式


MOV WORD [SCRNX], 320
MOV WORD [SCRNY], 200
MOV DWORD [VRAM], 0x000a0000

; 用 BIOS 取得键盘上各种 LED 指示灯的状态


MOV AH, 0x02
INT 0x16 ; 键盘 BIOS
MOV [LEDS], AL
; 防止 PIC 接受所有中断
; 根据 AT 兼容机的规范初始化 PIC
; 如果没有在 CLI 指令前执行可能会挂起
; 并继续初始化 PIC
MOV AL, 0xff
OUT 0x21, AL
NOP ; 有些机器不能连续执行 NOP 指令
OUT 0xa1, AL

CLI

; 设置 A20GATE 使 CPU 支持 1M 以上的内存


CALL waitkbdout
MOV AL, 0xd1
OUT 0x64, AL
CALL waitkbdout
MOV AL, 0xdf ; 开启 A20
OUT 0x60, AL
CALL waitkbdout

; 切换到保护模式
; NASM 不支持 INSTRSET 命令
; [INSTRSET "i486p"] ; 使用 486 指令
LGDT [GDTR0] ; 设置临时 GDT
MOV EAX, CR0
AND EAX, 0x7fffffff ; 禁用分页
OR EAX, 0x00000001 ; 开启保护模式
MOV CR0, EAX
JMP pipelineflush

pipelineflush:
MOV AX, 1*8 ; 写 32bit 段
MOV DS, AX
MOV ES, AX
MOV FS, AX
MOV GS, AX
MOV SS, AX
; bootpack 传递
MOV ESI, bootpack ; 源
MOV EDI, BOTPAK ; 目标
MOV ECX, 512*1024/4
CALL memcpy

; 传输磁盘数据

; 从引导区开始

MOV ESI, 0x7c00 ; 源


MOV EDI, DSKCAC ; 目标
MOV ECX, 512/4
CALL memcpy

; 剩余的全部
MOV ESI, DSKCAC0+512 ; 源
MOV EDI, DSKCAC+512 ; 目标
MOV ECX, 0
MOV CL, BYTE [CYLS]
IMUL ECX, 512*18*2/4 ; 除以 4 得到字节数
SUB ECX, 512/4 ; IPL 偏移量
CALL memcpy

; 由于还需要 asmhead 才能完成


; 完成其余的 bootpack 任务

; bootpack 启动
; 修改后检验不通过,移除校验
; MOV EBX, BOTPAK
; MOV ECX, [EBX+16]
; ADD ECX, 3 ; ECX += 3
; SHR ECX, 2 ; ECX /= 4
; JZ skip ; 传输完成
; MOV ESI, [EBX+20] ; 源
; ADD ESI, EBX
; MOV EDI, [EBX+12] ; 目标
; CALL memcpy

skip:
; MOV ESP, [EBX+12] ; 堆栈初始化
; JMP DWORD 2*8:0x0000001b
MOV ESP, 0xffff
JMP DWORD 2*8:0x00000000

waitkbdout:
IN AL, 0x64
AND AL, 0x02
JNZ waitkbdout ; AND 结果不为 0 跳转至 waitkbdout
RET

memcpy:
MOV EAX, [ESI]
ADD ESI, 4
MOV [EDI], EAX
ADD EDI, 4
SUB ECX, 1
JNZ memcpy ; 结果不为 0 跳转至 memcpy
RET
; memcpy 地址前缀大小

ALIGN 16
GDT0:
RESB 8 ; 初始值
DW 0xffff, 0x0000, 0x9200, 0x00cf ; 可写的 32 位段寄存器
DW 0xffff, 0x0000, 0x9a28, 0x0047 ; 可执行的文件的 32 位寄存器

DW 0

GDTR0:
DW 8*3-1
DD GDT0

ALIGN 16
bootpack:
2) Bootpack.c

void io_hlt(void);

// 入口函数 HariMain 重命名为标准的 main


// 返回类型修改为 int,避免编译器警告
int main(void) {
fin:
io_hlt();
goto fin;
}
3) Func.asm

; [FORMAT "WCOFF"]
[BITS 32]

GLOBAL io_hlt ; 程序中包含函数名

[SECTION .text]
io_hlt: ; void io_hlt(void);
HLT
RET
4) Haribote.ld

ENTRY( entry )

SECTIONS
{
. = 0xc200;
.btext : { *(.btext) }

.text : { *(.text) }
.data : { *(.data) }
}
5) Ipl.asm

; hello-os

CYLS EQU 10 ; 读取的柱面数量(CYLS = cylinders)

ORG 0x7c00 ; 指明程序的装载地址

; 用于标准 FAT12 格式的软盘


JMP entry ; 跳转指令
NOP ; NOP 指令
DB "HARIBOTE" ; OEM 标识符(8 字节)
DW 512 ; 每个扇区(sector)的字节数(必须为 512 字
节)
DB 1 ; 每个簇(cluster)的扇区数(必须为 1 个扇
区)
DW 1 ; FAT 的预留扇区数(包含 boot 扇区)
DB 2 ; FAT 表的数量,通常为 2
DW 224 ; 根目录文件的最大值(一般设为 224 项)
DW 2880 ; 磁盘的扇区总数,若为 0 则代表超过 65535 个
扇区,需要使用 22 行记录
DB 0xf0 ; 磁盘的种类(本项目中设为 0xf0 代表 1.44MB
的软盘)
DW 9 ; 每个 FAT 的长度(必须为 9 扇区)
DW 18 ; 1 个磁道(track)拥有的扇区数(必须是 18)
DW 2 ; 磁头数(必须为 2)
DD 0 ; 隐藏的扇区数
DD 2880 ; 大容量扇区总数,若 16 行记录的值为 0 则使用
本行记录扇区数
DB 0 ; 中断 0x13 的设备号
DB 0 ; Windows NT 标识符
DB 0x29 ; 扩展引导标识
DD 0xffffffff ; 卷序列号
DB "HARIBOTEOS " ; 卷标(11 字节)
DB "FAT12 " ; 文件系统类型(8 字节)
RESB 18 ; 空 18 字节
; 程序核心

entry:
MOV AX, 0 ; 初始化寄存器
MOV SS, AX
MOV SP, 0x7c00
MOV DS, AX

; 读取硬盘
MOV AX, 0x0820
MOV ES, AX
MOV CH, 0 ; 柱面 0
MOV DH, 0 ; 磁头 0
MOV CL, 2 ; 扇区 2

readloop:
MOV SI, 0 ; 记录失败次数的寄存器
retry:
MOV AH, 0x02 ; AH=0x02:读盘
MOV AL, 1 ; 1 个扇区
MOV BX, 0
MOV DL, 0x00 ; A 驱动器
INT 0x13 ; 调用磁盘 BIOS
JNC next ; 没出错跳转到 next

ADD SI, 1 ; 失败次数+1


CMP SI, 5 ; 失败次数是否达到 5 次
JAE error ; 失败次数达到 5 次跳转到 error
MOV AH, 0x00
MOV DL, 0x00 ; A 驱动器
INT 0x13 ; 重置驱动器
JMP retry

next:
MOV AX, ES ; 把内存地址后移 0x200
ADD AX, 0x0020
MOV ES, AX ; 实现 ES += 0x0020 的目的
; 扇区范围 1~18
ADD CL, 1 ; 扇区加 1
CMP CL, 18 ; 扇区是否达到 18
JBE readloop ; 小于等于 18 扇区则跳转到 readloop

MOV CL, 1 ; 恢复到扇区 1


; 磁头范围 0~1(正面 0,反面 1)
ADD DH, 1
CMP DH, 2
JB readloop ; 磁头未达到 2 则跳转到 readloop

MOV DH, 0
; 柱面范围 0 ~ 79
ADD CH, 1
CMP CH, CYLS
JB readloop ; 读取指定数量的柱面,未达到 CYLS 则跳转
readloop

; 读取完毕,跳转到 haribote.sys 执行
MOV [0x0ff0], CH ; 记下 IPL 读了多远(谷歌翻译自 IPL がどこま
で読んだのかをメモ)
JMP 0xc200

fin:
HLT ; CPU 停止,等待指令
JMP fin ; 无限循环

error:
MOV SI, msg

putloop:
MOV AL, [SI]
ADD SI, 1 ; SI 加 1
CMP AL, 0

JE fin
MOV AH, 0x0e ; 显示一个文字
MOV BX, 15 ; 指定字符颜色
INT 0x10 ; 调用显卡 BIOS
JMP putloop

msg:
DB 0x0a, 0x0a ; 两个换行
DB "load error"
DB 0x0a ; 换行
DB 0

RESB 0x1fe - ($ - $$) ; 填写 0x00,直到 0x001fe


DB 0x55, 0xaa
6) Makefile

ifndef GCCPREFIX
GCCPREFIX :=
endif

AS := nasm
CC := $(GCCPREFIX)gcc
LD := $(GCCPREFIX)ld
OBJCOPY := $(GCCPREFIX)objcopy
CFLAGS := -Wall -m32 -fno-pie
QEMU := qemu-system-i386

OBJS := ipl.bin asmhead.bin bootpack.bin func.bin


SYS := haribote.sys
IMG := haribote.img

QEMU_FLAGS :=

ifdef DEBUG
QEMU_FLAGS += -gdb tcp::1234 -S
CFLAGS += -g
endif

ipl.bin:
$(AS) -f bin ipl.asm -o ipl.bin -l ipl.lst

asmhead.bin:
$(AS) -f elf asmhead.asm -o asmhead.bin -l asmhead.lst

bootpack.bin:
$(CC) $(CFLAGS) -c bootpack.c -o bootpack.bin

func.bin:
$(AS) -f elf func.asm -o func.bin -l func.lst
haribote.sys: asmhead.bin bootpack.bin func.bin
$(LD) -m elf_i386 --oformat binary asmhead.bin bootpack.bin func.bin -o haribote.sys -T
haribote.ld

image: ipl.bin haribote.sys


dd if=/dev/zero of=$(IMG) bs=512 count=2880
dd if=ipl.bin of=$(IMG) bs=512 count=1 conv=notrunc
dd if=haribote.sys of=$(IMG) seek=33 bs=512 conv=notrunc

all: ${OBJS} haribote.sys image

clean:
rm -rf *.bin
rm -rf *.sys
rm -rf *.obj
rm -rf *.lst
rm -rf $(IMG)

qemu: clean all


$(QEMU) -fda $(IMG) $(QEMU_FLAGS)

.PHONY:
all

You might also like