源程序来源
https://www.jianshu.com/p/72c151606908
加载程序功能
- 加载程序 知道 用户程序位于 虚拟硬盘的LBA逻辑扇区100 处;
- 加载程序 知道 虚拟机 内存物理地址
0x10000
处空闲; - 加载程序 要把 用户程序 从虚拟硬盘里取出来,然后放到 虚拟机里空闲的内存空间那里;
- 加载程序 知道 虚拟机 开机后会读取 虚拟硬盘主引导扇区(LBA模式逻辑扇区号0) 的内容,将其复制到内存
0x0000:0x7c00
处开始执行;
(我们)要做的事情
- 利用工具
nasmide.exe
编译加载程序的源文件.asm
,生成一个.bin
二进制文件,将.bin
文件利用工具fixvhdwr.exe
写入到虚拟硬盘的主引导扇区(LBA模式逻辑扇区号0)。
加载程序:增加注释
;
;文件名 c08-1.asm
;文件说明:硬盘主引导扇区代码(加载程序)
;创建日期:9:12 2018/5/23
app_lba_start equ 100 ;用户程序源地址的起始逻辑扇区号
SECTION mbr align=16 vstart=0x7c00
;设置栈段和栈指针
mov ax,0
mov ss,ax
mov sp,ax
mov ax,[cs:phy_base]
mov dx,[cs:phy_base+0x02]
mov bx,16
div bx ;物理地址0x10000 转换为 段地址0x1000
mov ds,ax
mov es,ax
;以下读取程序的起始部分
xor di,di ;28位起始逻辑扇区号的高12位
mov si,app_lba_start ;28位起始逻辑扇区号的低16位
xor bx,bx ; ???
call read_hard_disk_0
;以下判断整个用户程序有多大
mov dx,[2] ; 32位用户程序长度的高16位
mov ax,[0] ; 32位用户程序长度的低16位
mov bx,512 ; 1个扇区512字节
div bx
cmp dx,0 ; dx里存着余数,余数不为0代表没有除尽
jnz @1
dec ax
@1:
cmp ax,0 ; 小于1个扇区或者长度为512的整数倍时ax = 0
jz direct
; 读取剩余的扇区
push ds ; 用户程序的开头是基于LBA逻辑扇区号计算出来的段地址
mov cx,ax ; 循环次数(剩余的扇区数)
@2:
mov ax,ds
add ax,0x20 ; 512D = 0x20
mov ds,ax ; 得到下一个以512字节为边界的段地址
xor bx,bx ; 新的一段开始,偏移地址都是从0x0000开始
inc si ; 新的LBA逻辑扇区号,最初的是app_lba_start
call read_hard_disk_0 ; 不重新设置di,是因为di不需要修改,di = 0
loop @2 ; 循环读,直到读完整个功能程序(即用户程序)
pop ds ; 恢复数据段基址到用户程序头部段
;计算入口点代码段基址
direct: ; ds 指向用户程序头部段
mov dx,[0x08]
mov ax,[0x06] ; 用户程序 "code_1段"(代码段) 相对于用户程序开头的 偏移量
call calc_segment_base ; 结合用户程序目的地址,计算 "code_1段"(代码段) 的 段地址
mov [0x06],ax ; 将 "code_1段"(代码段) 的段地址 回写
; 开始处理段重定位表
mov cx,[0x0a] ; 需要重定位的表项数
mov bx,0x0c ; 需要重定位表项相对用户程序开头的偏移量
realloc:
mov dx,[bx+0x02] ; 32位表项偏移量,高16位
mov ax,[bx] ; 32位表项偏移量,低16位
call calc_segment_base ; 结合用户程序目的地址, 计算 表项 的段地址
mov [bx],ax
add bx,4 ; 下一个重定位项 (每项占4个字节)
loop realloc
jmp far [0x04] ; 跳转到用户程序: code_1段 标号start处开始执行用户程序
;-----------------------------------------------------------------
read_hard_disk_0:
push ax
push bx
push cx
push dx
;设置要读取的扇区数为1
mov dx,0x1f2 ; 0x1f2
mov al,1 ; 扇区数
out dx,al
;设置起始的LBA扇区号
inc dx ; 0x1f3
mov ax,si
out dx,al ; LBA地址7~0
inc dx ; 0x1f4
mov al,ah
out dx,al ; LBA地址15~8
inc dx ;0x1f5
mov ax,di
out dx,al ; LBA地址23~16
inc dx ; 0x1f6
mov al,0xe0 ; LAB 主硬盘
or al,ah
out dx,al
; 请求硬盘读
inc dx ; 0x1f7 [命令端口]
mov al,0x20 ; 读命令
out dx,al
;查看状态
.waits:
in al,dx ; 0x1f7 [状态端口]
and al,0x88 ; 1000 1000 保留 BSY ... DRQ...
cmp al,0x08 ; 0x08 硬盘已准备好与主机交换
jnz .waits
; 连续读取数据
mov cx,256 ; 总共要读取的字数256字=512字节
mov dx,0x1f0 ; 0x1f0 [数据端口]
.readw:
in ax,dx ; 在子程序调用前已经清零xor bx,bx
mov [bx],ax ; 指定数据段 DS 指向用户程序目标地址的段地址
add bx,2
loop .readw
pop dx
pop cx
pop bx
pop ax
ret
;-----------------------------------------------------------------
calc_segment_base: ;计算16位段地址
;输入 DX:AX = 32位物理地址
;返回 AX = 16位段基地址
push dx
add ax,[cs:phy_base]
adc dx,[cs:phy_base+0x02] ; 用户程序开头的物理地址是0x10000位于标号phy_base处
shr ax,4
ror dx,4
and dx,0xf000
or ax,dx
pop dx
ret
;-----------------------------------------------------------------
phy_base dd 0x10000 ;用户程序目的地址的物理起始地址
times 510-($-$$) db 0
db 0x55,0xaa
用户程序 同 源程序来源
c08.asm
加载程序 需要结合 用户程序 理解的部分 跨越文件 通过内存读取数据
代码说明
-
phy_base dd 0x10000
: 用户程序目的地址的物理起始地址 寻址内存
物理地址 0x10000
使用 【段地址:偏移地址 = 0x1000:0x0000】 来映射
---------------------------------------------------------------------------
完成计算 段地址 的代码片段
mov ax,[cs:phy_base]
mov dx,[cs:phy_base+0x02]
mov bx,16
div bx ;物理地址0x10000 转换为 段地址0x1000
mov ds,ax
mov es,ax
-
app_lba_start equ 100
: 用户程序源地址的起始LBA逻辑扇区号 寻址硬盘
28位 LBA 逻辑扇区号 100
0000 0000 0000 0000 0000 0000 0100
27~24 23~16 15~8 7~0
----- DI --------- -------- SI ----------
-------------------------------------------------------------
;以下读取程序的起始部分
xor di,di ;28位起始逻辑扇区号的高12位
mov si,app_lba_start ;28位起始逻辑扇区号的低16位
- 设置起始的LBA扇区号
;设置起始的LBA扇区号
inc dx ; 0x1f3
mov ax,si
out dx,al ; LBA地址7~0
inc dx ; 0x1f4
mov al,ah
out dx,al ; LBA地址15~8
inc dx ;0x1f5
mov ax,di
out dx,al ; LBA地址23~16
inc dx ; 0x1f6
mov al,0xe0 ; LAB 主硬盘
or al,ah
out dx,al
《x86汇编语言:从实模式到保护模式》 第126页
- 查看硬盘状态
;查看状态
.waits:
in al,dx ; 0x1f7 [状态端口]
and al,0x88 ; 1000 1000 保留 BSY ... DRQ...
cmp al,0x08 ; 0x08 硬盘已准备好与主机交换
jnz .waits
《x86汇编语言:从实模式到保护模式》 第127页
- 子程序
call read_hard_disk_0
子程序
call read_hard_disk_0
参数
di:si 是用户程序位于硬盘的源地址的[LBA逻辑扇区号]
di : 28位起始逻辑扇区号的高12位
si : 28位起始逻辑扇区号的低16位
ds:bx 是用户程序将被送往内存的目的地址的[段地址:偏移地址]
隐藏参数 ds
bx : 固定初始化为0x0000,因为每一段的偏移地址都是从0x0000开始
疯狂Debug
-
两个文件,如果按照相同的行数输入相同的代码,编译出来的机器码连同行数都会一模一样的,但是这里虽然机器码相同,行数却不同,预感这里有一个BUG:果然把寄存器DX写成了寄存器AX;
phy_base dd 0x10000
, 用的是dd 型数据,双字,4个字节;