DiskDataReg equ 0x01f0
DiskErrReg equ 0x01f1
DiskSectCntReg equ 0x01f2
DiskLoLBAAddr equ 0x01f3
DiskMeLBAAddr equ 0x01f4
DiskHiLBAAddr equ 0x01f5
DiskModReg equ 0x01f6
DiskCmdStatReg equ 0x01f7
DiskReadCmd equ 0x20
DiskWriteCmd equ 0x30
Arg1Off equ 0x06 ;第一个参数相对bp偏移
Arg2Off equ 0x08 ;第二个参数相对bp偏移
Arg3Off equ 0x0A ;第三个参数相对bp偏移
Arg4Off equ 0x0C ;第四个参数相对bp偏移
StackBase equ 0x0000
StackEnd equ 0x2000
DataBase equ 0x0300
DataEnd equ 0x1FFF
AppLenOff equ 0x10
AppEntryOff equ 0x14
AppCodeSegOff equ 0x16
AppSegNumsOff equ 0x1A
AppRelocTab equ 0x1C
;==========================
ProgLocSect equ 0x04
section bootloader align=16 vstart=0x7c00
jmp start
Relocation:
;前面取出没重定位前保存在用户程序中的地址,加上重定位的基址,得到最终的地址
;实模式下,地址20位。ax取低16位,dx取高16位,其中低4位有效
;20位右移4位,得到段地址。ax先右移4位,空出高位4位;
;dx低4位左移12位,即15-12位为段地址高位,再与ax的低12位相或,得到16为段地址
add ax,word [cs:LoadPhyBase]
adc dx,word [cs:LoadPhyBase+2]
shr ax,0x04
shl dx,0x0c
or ax,dx
ret
SetSectAddr:
;设置逻辑扇区的地址
push bp
mov bp,sp
push bx
;发扇区总数
mov dx,DiskSectCntReg
mov al,byte [bp+Arg4Off]
out dx,al
;发扇区地址
mov dx,DiskLoLBAAddr
mov al,byte [bp+Arg3Off]
out dx,al
mov dx,DiskMeLBAAddr
mov al,byte [bp+Arg3Off+1]
out dx,al
mov dx,DiskHiLBAAddr
mov al,byte [bp+Arg2Off]
out dx,al
mov dx,DiskModReg
mov al,byte [bp+Arg2Off+1]
out dx,al
pop bx
mov sp,bp
pop bp
ret
WaitDiskReady:
;等待磁盘就绪
mov dx,DiskCmdStatReg
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits
;判断是否有错误
mov dx,DiskErrReg
.getErr:
in al,dx
cmp al,0x00
;al不为0出错
jnz .resume
;没有出错 返回0
xor ax,ax
ret
.resume:
;出错 返回
mov ax,0x01
ret
CheckMagicNum:
mov si,MagicNum
mov di,0
mov cx, 0x0A
CheckValidAppLoop:
;si和di不在同一个段中,因此cmpsb用不了
mov ax,[di]
cmp word [cs:si],ax
jnz .notValidApp
inc si
inc di
loop CheckValidAppLoop
xor ax,ax
ret
.notValidApp:
mov ax,0x01
ret
ReadOneSection:
call SetSectAddr
;发读命令
mov dx,DiskCmdStatReg
mov al,DiskReadCmd
out dx,al
call WaitDiskReady
xor bx,bx ;准备拷贝,bx做索引
;读取保存在扇区上的数据的有效长度
mov cx,0x100
mov dx,DiskDataReg
.readw:
in ax,dx
mov [bx],ax
add bx,2
loop .readw
ret
start:
xor ax,ax
xor dx,dx
;计算用于加载程序的内存段地址
mov ax,word [cs:LoadPhyBase]
mov dx,word[cs:LoadPhyBase+2]
mov bx,0x10
div bx
;dx:ax/bx->商存放在ax中
;商即为段地址
;为后面用户程序设置段寄存器(es/ds),指向LoadPhyBase所指的内存起址
;如果没有设置ds,用户程序运行时,程序不知道到何处去取出段头信息,程序大小
mov ds,ax
mov es,ax
push word 0x0001
push word ProgLocSect
push word 0xe000
push word 0x0200
;加载用户程序所在的第一个扇区
call ReadOneSection
;恢复堆栈
add sp,0x08
call CheckMagicNum
cmp ax,0x00
jnz $ ;魔术字不匹配就在此循环
;程序从磁盘读到0x1000:0000开始的内存中,程序长度记录在0x10002处
mov ax,word [AppLenOff]
mov dx,word [AppLenOff+0x02]
;从用户程序的总长度决定还有多少扇区要读取
mov bx,0x200
div bx
;ax:程序占了完整的几个扇区,dx:还多余几个字节
;多余的字节占用一个扇区,故,共占用ax+1个扇区
;前面已经读取了一个扇区,剩下ax个扇区还要读取
cmp dx,0x0000
;如果没有零星字节
jz .totalSections
;如果有零星字节,向上调整
inc ax
.totalSections:
cmp ax,0x0001
jz ReadSectComplete
;循环读取次数
mov cx,ax
dec cx
xor di,di
;段地址每次增加0x200,存储扇区内容
;保存ds,ds最终还是要指向0x1000
mov ax,ds
push ax
mov bx,ProgLocSect
;上次已经加载了ProgLocSect指定的扇区
;这次加载下一个扇区
inc bx
RemainSector:
add ax,0x20
mov ds,ax
push cx ;cx的值可能会被子函数修改 保存起来
;继续读剩下的扇区
push word 0x0001
push word bx
push word 0xe000
push word 0x0200
call ReadOneSection
add sp,0x08
inc bx
pop cx
loop RemainSector
pop ax
mov ds,ax
;现在程序全在从LoadPhyBase开始的内存中,由ds指向
ReadSectComplete:
;用户程序重定位
RelocationCodeSeg:
mov ax,word [AppCodeSegOff]
mov dx,word [AppCodeSegOff+2]
call Relocation
;修正用户头中代码段基址,先清空以前的内容
mov word [AppCodeSegOff],0x0000
mov word [AppCodeSegOff+2],0x0000
mov word [AppCodeSegOff],ax
RelocationTabElem:
;修正用户头中重定位表中各项基址,先清空以前的内容
mov cx,word [AppSegNumsOff]
;重定位表偏移
xor bx,bx
mov bx,AppRelocTab
RelocRound:
mov ax,word [bx] ;表项低位
mov dx,word [bx+0x02] ;表项高位
call Relocation
mov word [bx],0x0000
mov word [bx+0x02],0x0000
mov word [bx],ax
add bx,0x04
loop RelocRound
;重定位结束,跳转到用户程序
;代码在[AppEntryOff]开始的位置
;开始时没加far,结果是近跳转,过不去
;远跳转,从内存中取出双字,修改cs:ip的值
jmp far [AppEntryOff]
MagicNum db 'M',0x2e,'A',0x2e,'G',0x2e,'I',0x2e,'C',0x2e
times 0x06 db 0x00
LoadPhyBase dd 0x10000
times 510-($-$$) db 0
db 0x55,0xaa
本文仅是对仿照着写个bootloader (三)的修改: