调试一天半,复刻成功!!!
;文件名:Program_Loader.asm
;文件说明:硬盘主引导扇区代码(加载程序)
;创建日期:2021-11-1
;用户程序起始逻辑扇区号
;段
;Main:
;设置堆栈
;计算用户程序加载的段地址
;设置程序读取的上文(先读取一个扇区,获取基本信息)
;利用基本信息,计算程序的总扇区数
;(若剩余扇区数>0)读取剩余扇区
;重置段表
;跳转至用户程序入口
;
;
application equ 10 ;用户程序所在扇区号
section app_load align=16 vstart=0x7c00
;设置堆栈指针
mov ax,0
mov ss,ax
mov sp,0x0
;设置用户程序加载位置
mov ax,[cs:phy_base] ;低位
mov dx,[cs:phy_base+0x2] ;高位
mov bx,0x10
div bx
mov ds,ax ;ds与es指向用户程序加载位置
;mov es,ax
;读取一个扇区大小的用户程序
mov di,0 ;扇区读取目标地址ds:di
mov si,application ;需读取扇区号
call read_HardDisk_0 ;开始读取
;计算用户程序大小
mov dx,[0x2] ;用户程序大小高地址
mov ax,[0x0] ;用户程序大小低地址
mov bx,512 ;一个扇区大小
div bx ;ax为商,dx为余
cmp dx,0x0 ;ax:所占用完整的扇区数
jnz @1 ;dx为0,则共需读取ax个扇区(别忘了已经读取过一个扇区)
dec ax ;dx不为0,则共需读取ax+1个扇区(别忘了已经读取过一个扇区)
;判断用户程序所占扇区大小
@1: ;若ax为0,则无需再读取
cmp ax,0
jz re_entry
mov cx,ax ;读取剩余扇区的程序
@2: ;此时di=512,ds=0x10000
inc si ;si为需读取扇区号
call read_HardDisk_0 ;读取第si个扇区
loop @2
;用户程序读取完毕
;用户程序起始地址:0x10000
re_entry: ;重置用户程序代码入口
mov dx,[0x8] ;代码段入口高地址
mov ax,[0x6] ;代码段入口低地址
call translaion_address ;返回段地址ax
mov [0x6],ax ;重写程序入口段基址
mov di, 0xc ;段基址重定位表首地址
mov cx,[0xa] ;需重置段基址数量
re_section:
mov dx,[di+0x2] ;代码段入口高地址
mov ax,[di] ;代码段入口低地址
call translaion_address ;返回段地址ax
mov [di],ax ;重写程序入口段基址
add di,4 ;下一需重定位段基址
loop re_section
jmp far [0x4] ;跳转至用户程序入口
;----------------------------------------------------------------------------------
;硬盘读取
read_HardDisk_0: ;si为所需扇区号
;ds:di为目标地址
push cx
mov al,0x1 ;读取扇区数
mov dx,0x1f2 ;0x01f2端口
out dx,al
inc dx
;0x01f3端口,0x01f4端口,0x01f5端口,0x01f6端口
;LBA地址0~7,LBA地址8~15,LBA地址16~23,LBA地址24~27
;0~27:LBA28逻辑扇区
;28~31:
;28:[0:主硬盘/1:从硬盘]
;29~31:[101:CHS/111:LBA]
mov ax,si
out dx,al ;0x01f3端口,LBA地址0~7
inc dx
mov al,0x0
out dx,al ;0x01f4端口,LBA地址8~15
inc dx
out dx,al ;0x01f5端口,LBA地址16~23
inc dx
mov al,0xe0
out dx,al ;0x01f6端口,LBA地址24~27
inc dx ;28~31,模式设置
mov al,0x20
out dx,al ;0x01f7端口,向端口写入0x20(写命令)
;等待硬盘数据准备
wait_:
in al,dx ;0x01f7端口为状态寄存器
and al,0x88 ;留下第7位,第4位
cmp al,0x8 ;第7位为0表示不忙,第4位为1表示数据以准备好
jnz wait_
mov dx,0x1f0 ;数据传送端口
mov cx,256
;开始读取硬盘 ;读取字数
read_:
in ax,dx
mov [di],ax
inc di
inc di
loop read_
pop cx
ret
;--------------------------------------------------------------------------------------
translaion_address: ;输入
;dx为16位高地址(2B)
;ax为16位低地址(2B)
;phy_base=0x10000(4B)
add ax,[cs:phy_base] ;低位相加,CF进位
adc dx,[cs:phy_base+0x2] ;高位相加,加上CF进位,由此完成32位加法
;此时dx:ax为内存中入口点代码段起始地址
;8086中仅有20位地址线(前20位有效)
;所以dx:ax仅有20位有效
;因为ax有16位有效
;于是dx虽有16位,dx仅有最后4位有效
;只取20位中高16位作为段地址
shl dx,12 ;dx低4位放于高4位返回
shr ax,4 ;保留ax高12位放于低12位返回
add ax,dx ;返回段地址ax
ret
;----------------------------------------------------------------------------------
phy_base dd 0x10000 ;用户程序加载位置,16字节对齐
db 510-($-$$) dup(0) ;补充满一个扇区
db 0x55,0xaa