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
AppEntryOff equ 0x04
AppCodeSegOff equ 0x06
AppSegNumsOff equ 0x0A
AppRelocTab equ 0x0C
;==========================
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
ReadFromDisk:
call SetSectAddr
;发读命令
mov dx,DiskCmdStatReg
mov al,DiskReadCmd
out dx,al
call WaitDiskReady
xor bx,bx ;准备拷贝,bx做索引
;读取保存在扇区上的数据的有效长度
mov dx,DiskDataReg
in ax,dx
mov [bx],ax ;程序的长度也要保存起来,要不然用户头部都不完整了
add bx,2
shr ax,0x01
;比较要读取数据的长度和有效数据的长度,取最短的
;磁盘上保存的数据,长度按字节计数,但是DiskDataReg
;端口是16位端口,每次读取1字,因此循环次数需要除2
mov cx,ax
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
;从4号扇区先读一个扇区,扇区里前部是程序头,感觉跟pe文件头有点像
call ReadFromDisk
;恢复堆栈
add sp,0x08
;从0x1000:0000开始读
xor bx,bx
mov ax,word [bx]
mov dx,word [bx+0x02]
;从用户程序的总长度决定还有多少扇区要读取
mov bx,0x200
div bx
;ax:程序占了完整的几个扇区,dx:还多余几个字节
;多余的字节占用一个扇区,故,共占用ax+1个扇区
;前面已经读取了一个扇区,剩下ax个扇区还要读取
cmp ax,0x0000
jz ReadSectComplete
;循环读取次数
mov cx,ax
xor di,di
mov di,ProgLocSect
;段地址每次增加0x200,存储扇区内容
mov ax,ds
RemainSector:
add ax,0x20
mov ds,ax
;继续读剩下的扇区
push word 0x0001
push word ProgLocSect
push word 0xe000
push word 0x0200
call ReadFromDisk
add sp,0x08
loop RemainSector
;现在程序全在从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]
LoadPhyBase dd 0x10000
times 510-($-$$) db 0
db 0x55,0xaa