這次我們要用到4個文件,分別是ipl.nas、asmhead.nas、func.nas和 bootpack.c。
ipl.nas是引導扇區中的16bit程序,用於從磁盤中加載數據並跳轉到asmhead.nas中。
asmhead.nas也是16bit程序,用於加載全局變量表,切換cpu到32位的保護模式,並跳轉到后面的程序。
bootpack.c用於改變屏幕顏色,func.nas為bootpack.c提供相應的一些函數。
那么我們現在就開始吧。
ipl.nas和上一篇文章中的程序幾乎沒有什么變化,我去掉了打印hello world的部分,同時在其中多加了一句MOV [0x0ff0],CH來記錄讀了多少個柱面。
程序一、ipl.nas
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; hello-os
; TAB=8
ORG 0x7C00
; 標准FAT格式軟盤
start:
JMP entry
DB "HELLOIPL" ; 啟動區名稱(8字節)
DW 512 ; 扇區大小(512字節)
DB 1 ; 簇大小(1扇區)
DW 1 ; FAT起始位置 Reserved Sectors
DB 2 ; FAT個數
DW 224 ; 根目錄(224項) Root Entries
DW 2880 ; 磁盤大小(2880扇區) 2*80*18 Small Sectors
DB 0xf0 ; 磁盤種類
DW 9 ; FAT長度
DW 18 ; 每個磁道扇區數
DW 2 ; 磁頭數
DD 0 ; 隱藏扇區
DD 2880 ; 重寫一次磁盤大小 Large Sectors
DB 0,0,0x29 ; Physical Drive Number, Current Head, Signature
DD 0xffffffff ; 可能是卷標號碼 ID
DB "HELLO-OS " ; 磁盤名稱(11字節) Volume Label
DB "FAT12 " ; 格式名稱(8字節) System ID
RESB 18 ; 空出18字節
; 進入引導程序
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX
; 讀磁盤
CYLS EQU 10
MOV AX,0x0820
MOV ES,AX ; ES:BX為內存緩存地址 ESx16+BX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁頭0
MOV CL,2 ; 扇區2
readloop:
MOV SI,0 ; 記錄失敗次數
retry:
MOV AH,0x02 ; 讀盤
MOV AL,1 ; 1個扇區
MOV BX,0
MOV DL,0x00 ; 0驅動器
INT 0x13 ; 調用磁盤BIOS
JNC next ; 沒出錯則讀下一個扇區
ADD SI,1
CMP SI,5 ; 比較SI與5
JAE error ; 超過允許錯誤次數,跳轉到error
MOV AH,0x00
MOV DL,0x00
INT 0x13 ; 重置驅動器
JMP retry
next:
MOV AX,ES
ADD AX,0x0020 ; 把內存地址后移0x200
MOV ES,AX ; 因為沒有(ADD ES,0x20)
ADD CL,1
CMP CL,18
JBE readloop ; 還沒有讀完一面
MOV CL,1
ADD DH,1 ; 讀磁盤另一面
CMP DH,2
JB readloop
MOV DH,0
ADD CH,1
CMP CH,CYLS ; 讀CYLS個柱面
JB readloop
MOV [0x0ff0],CH ; 記錄讀了多少個柱面
jmp 0xc200 ; 調到主程序
error:
MOV SI,errmsg
errloop:
MOV AL,[SI]
ADD SI,1 ; 給SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 顯示一個文字
MOV BX,15 ; 指定字符顏色
INT 0x10 ; 調用顯卡BIOS
JMP errloop
fin:
HLT
JMP fin
msg:
DB 0x0a, 0x0a ; 換行2次
DB "hello, world"
DB 0x0a ; 換行
DB 0
errmsg:
DB 0x0a, 0x0a ; 換行2次
DB "disk error"
DB 0x0a ; 換行
DB 0
marker:
RESB 0x1fe-(marker-start)
DB 0x55, 0xaa
end:
RESB 1474560-(end-start)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
博文把禁用中斷、開啟A20管腳、初始化段、開啟保護模式、改變屏幕顏色都講得非常清楚。
在全局變量表部分,我們有兩個段條目,分別是:
DW 0xffff,0x0000,0x9200,0x00cf ; 數據段
DW 0xffff,0x0000,0x9a28,0x0047 ; 代碼段
因為內存是以小端方式存數據的,所以實際內存地址從小到大真正存儲的數據為:
ff ff 00 00 00 92 cf 00
ff ff 00 00 28 92 47 00
數據段的起始位置為0x00000000,容量為0xfffff000,保護模式為0。
代碼段的起始位置為0x00280000,容量為0xffff,保護模式為28。
在asmhead.nas程序中還有一句跳轉指令
MOV ESP,0xffff ; 設置棧地址
JMP DWORD 2*8:0x00000000 ; 跳轉到0x280000
我們設置ESP為0xffff,這剛好為數據段的最大地址,因為棧的是向內存地址減小的方向增長的。
JMP DWORD 2*8:0x00000000 是一個長跳轉指令,因此要加DWORD把內存尋址范圍變成32位,否則會被截斷成20位的地址。2*8表示GDT(全局變量表)中的偏移地址,剛好是代碼段,因此,代碼跳轉到0x280000。
程序二、asmhead.nas
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; haribote-os boot asm
; TAB=4
BOTPAK EQU 0x00280000 ; 主程序地址
DSKCAC EQU 0x00100000 ;
DSKCAC0 EQU 0x00008000 ;
; 有關BOOT_INFO
CYLS EQU 0x0ff0 ; 設定啟動區
LEDS EQU 0x0ff1 ; 鍵盤狀態
VMODE EQU 0x0ff2 ; 顏色位數
SCRNX EQU 0x0ff4 ; 分辨率X (screen x)
SCRNY EQU 0x0ff6 ; 分辨率Y (screen y)
VRAM EQU 0x0ff8 ; 圖像緩沖區開始地址,顯卡內存
ORG 0xc200
; 設定Graphic Mode
MOV AL,0x13 ; VGA顯卡,320x200x8位彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
; 返回鍵盤狀態
MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL
; PIC 可編程中斷控制器 有兩個PIC 每個PIC有8個輸入0-7
; cli關閉所有中斷,sti打開所有中斷
MOV AL,0xff
OUT 0x21,AL ; PCI1 data
NOP ; 太快可能會有問題
OUT 0xa1,AL ; PCI2 data
CLI ; 關閉全部中斷
; 蛋疼的鍵盤A20 address enable
CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout
; 切換到保護模式
LGDT [GDTR0]
MOV EAX,CR0
AND EAX,0x7fffffff ; 禁止分頁
OR EAX,0x00000001
MOV CR0,EAX
JMP pipelineflush
pipelineflush:
MOV AX,1*8 ; 取數據段偏移
MOV DS,AX ; 數據段
MOV ES,AX ; 數據段(字符操作目標)
MOV FS,AX ; 數據段
MOV GS,AX ; 數據段
MOV SS,AX ; 棧段
; 主程序加載到0x280000
MOV ESI,bootpack
MOV EDI,BOTPAK
MOV ECX,512*1024/4
CALL memcpy
; boot程序加載到0x100000
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 ; 扇區數量*512/4
SUB ECX,512/4 ; 去掉引導扇區
CALL memcpy
; 跳轉主程序
MOV ESP,0xffff ; 設置棧地址
JMP DWORD 2*8:0x00000000 ; 跳轉到0x280000
waitkbdout:
IN AL,0x64
AND AL,0x02 ; cpu可向鍵盤寫命令時為1
JNZ waitkbdout ;
RET
memcpy:
MOV EAX,[ESI]
ADD ESI,4
MOV [EDI],EAX
ADD EDI,4
SUB ECX,1
JNZ memcpy
RET
; 全局變量表
ALIGNB 16 ; 16字節對齊 bss段
GDT0:
RESB 8 ; 第一項為0,這是規定
DW 0xffff,0x0000,0x9200,0x00cf ; 數據段
DW 0xffff,0x0000,0x9a28,0x0047 ; 程序段
DW 0
GDTR0:
DW 8*3-1 ; 表的大小(字節)減1
DD GDT0 ; 表的地址
ALIGNB 16
bootpack:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
標簽bootpack后面的程序被復制到了0x280000處,所以其實我們要跳轉進入的程序就是在標簽bootpack后面,因此我們只要把之后要跳轉的程序放到到asmhead.nas就能夠保證被執行。
在上面的程序里,我們設置了圖像模式,320x200x8位彩色。
屏幕上的每個像素可由內存地址0xa0000-0xaffff對應。我們只要往對應的內存寫入相應值就可以改變屏幕上每個點的顏色。
程序三、bookpack.c
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
extern void io_hlt(void);
extern void write_mem8(int addr, int data);
void HariMain(void) {
int i;
for (i=0xa0000; i<=0xaffff; i++) {
write_mem8(i, i & 0x0f);
}
for (;;) {
io_hlt();
}
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
用c語言寫果然直觀很多,不過我們還需要調用兩個函數,這兩個函數是由匯編寫的。
這里的匯編就是32位的了,需要在代碼前面添加[BITS 32]來告訴nasm編譯成32位代碼,否則nasm會當作16位代碼,並在出現32位寄存器的指令前面添加66、67之類的數字,cpu就沒法好好執行了。
程序四、func.nas
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; func
; TAB=8
[BITS 32] ; 制作32位模式用的機械語言
GLOBAL io_hlt ; 程序中包含的函數名
GLOBAL write_mem8
[SECTION .text]
io_hlt: ; void io_hlt(void);
hlt
ret
write_mem8: ; void write_mem*(int addr, int data);
mov ecx,[esp+4]
mov al,[esp+8]
mov [ecx],al
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
對有些連接器,需要在匯編程序中把c中用到的函數前面加_,而對於我所使用的ld是不需要加的,加了反而會報錯,所以如果你不能通過鏈接的話就好好看看錯誤提示吧。
下面我們開始把所有程序編譯並鏈接到一起。
首先是兩個靜態的匯編程序:
nasm -f bin ipl.nas -o ipl.img -l ipl.lst
nasm -f bin asmhead.nas -o asmhead.o -l asmhead.lst
我裝的安裝目錄是/opt/local
我在/opt/local/bin中添加了ld、objcopy、objdump的符號引用i386-elf-ld、i386-elf-objcopy、i386-elf-objdump。同時我還把該目錄添加到了path中。
我們需要把elf格式的目標文件鏈接到一起再提取出其中的二進制代碼:
i386-elf-gcc -Wall -c bootpack.c -o bootpack.o
nasm -f elf func.nas -o func.o -l func.lst
i386-elf-ld bootpack.o func.o -o bootpack
i386-elf-objcopy -S -O binary bootpack bootpack.bin
再把bootpack.bin添加到asmhead.o中:
cat bootpack.bin >> asmhead.o
下兩步在上一篇文章中已經說明過了:
dd if=asmhead.o of=ipl.img bs=512 seek=33 conv=notrunc
qemu-system-i386 -fda ipl.img -boot a
整個過程的Makefile文件如下:
程序五、Makefile
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
default:
make run
run: ipl.nas asmhead.nas func.nas bootpack.c
nasm -f bin ipl.nas -o ipl.img -l ipl.lst
nasm -f bin asmhead.nas -o asmhead.o -l asmhead.lst
i386-elf-gcc -Wall -c bootpack.c -o bootpack.o
nasm -f elf func.nas -o func.o -l func.lst
i386-elf-ld bootpack.o func.o -o bootpack
i386-elf-objcopy -S -O binary bootpack bootpack.bin
cat bootpack.bin >> asmhead.o
dd if=asmhead.o of=ipl.img bs=512 seek=33 conv=notrunc
qemu-system-i386 -fda ipl.img -boot a
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
運行結果: