学习笔记
《x86汇编语言:从实模式到保护模式》
https://www.jianshu.com/p/d481cb547e9f
代码功能
- 从硬盘主引导扇区启动,显示
hello world
的 “迷你操作系统”
本文
- 硬盘图片
http://www.sohu.com/a/63146493_115161
代码联动
- 从软盘主引导扇区启动,显示
hello world
的 “迷你操作系统”
https://www.jianshu.com/p/2c8ace0ee240
- 软盘图片
运行测试
代码使用
1、使用配书工具 nasmide.exe 编译
加载程序 hello_mbr.asm
以及,生成二进制文件hello_mbr.bin
2、使用配书工具 nasmide.exe 编译
用户程序 hello.asm
,生成二进制文件hello.bin
3、使用配书工具 fixvhdwr.exe 将加载程序的二进制文件
hello_mbr.bin
写入虚拟硬盘的LBA逻辑扇区0(注意逻辑扇区号是零)4、使用配书工具 fixvhdwr.exe 将用户程序的二进制文件
hello.bin
写入虚拟硬盘的LBA逻辑扇区100(注意逻辑扇区号是一百)5、打开 Oracle VM VirtualBox,之前应该配置好启动硬盘,点击绿色按钮启动
补充、上述工具使用参考
https://www.jianshu.com/p/9cb95d936451
完整源码
加载程序 hello_mbr.asm
;------------------------------------------------------------------------
; 加载程序开始
;------------------------------------------------------------------------
; 文件名:hello_mbr.asm
; 文件说明:硬盘主引导扇区代码(加载程序)
; 创建日期:3:00 2018/5/25
;------------------------------------------------------------------------
;------------------------------------------------------------------------
; 全局变量
;------------------------------------------------------------------------
;------------------------------------------------------------------------
app_lba_start equ 100 ;(源地址):用户程序位于硬盘LBA逻辑扇区号100
SECTION mbr align=16 vstart=0x7c00
;------------------------------------------------------------------------
; 功能:准备工作
;------------------------------------------------------------------------
;------------------------------------------------------------------------
mov ax,0
mov ss,ax
mov sp,ax
;取得表示目的地址的32位物理地址
mov ax,[cs:phy_base]
mov dx,[cs:phy_base+0x02]
;将物理地址转换为 段地址:偏移地址 中的段地址
mov bx,16
div bx
mov ds,ax ; ds寄存器 = 0x1000
;------------------------------------------------------------------------
; 循环读取用户程序全部扇区
;------------------------------------------------------------------------
;------------------------------------------------------------------------
;------------------------------------------------------------------------
;取得表示源地址的28位LBA逻辑扇区号
;------------------------------------------------------------------------
xor di,di ;高12位全是零
mov si,app_lba_start ;低16位使用全局变量app_lba_start的值
;------------------------------------------------------------------------
;设置目的地址
;------------------------------------------------------------------------
xor bx,bx ;每个新的扇区偏移地址从0x0000算起
call read_hard_disk_0 ;完成1个扇区的加载,加载好用户程序开始的512字节内容
;------------------------------------------------------------------------
;从内存读出用户程序长度
;用户程序长度位于用户程序的头部段
;用户程序头部段位于用户程序的开始512字节,已经被加载到内存
;------------------------------------------------------------------------
;用户程序长度是 dd型 32位数据
mov dx,[0x02] ;用户程序长度 高16位
mov ax,[0x00] ;用户程序长度 低16位
mov bx,512
div bx ;除法 余数存在DX,商存在AX
cmp bx,0 ;余数不为0,代表没有除尽,程序所用总扇区数=AX+1
jnz @1
dec ax ;余数为0,说明扇区数就是AX,已经加载完一个扇区,所以减去1
@1:
cmp ax,0 ;程序总长度小于512字节或者为512整数倍时,AX=0
jz direct
;读取剩余的扇区
push ds ;压栈保护
mov cx,ax ;循环次数就是剩余扇区数
@2:
mov ax,ds
add ax,0x20 ;新读取一个扇区写入内存,段地址+0x20,偏移地址重新从0x0000算起
mov ds,ax
xor bx,bx ;新的一段开始,偏移地址都是从0x0000开始
xor di,di ;已知用户程序非常短,表示逻辑扇区号用不到高12位
inc si ;源地址LBA逻辑扇区号+1
call read_hard_disk_0
loop @2
pop ds ;弹处栈,恢复寄存器
;------------------------------------------------------------------------
; 回写
;------------------------------------------------------------------------
; 此时全部的用户程序已经加载到内存
;------------------------------------------------------------------------
direct:
;计算用户程序入口点 代码段code-1 段基址
;===================== 用户程序 ==============================
;code_entry dw start ;偏移地址 [0x04]
; dd section.code_1.start ;段地址 [0x06]
;===================== 用户程序 ==============================
mov dx,[0x08]
mov ax,[0x06] ; 用户程序 "code_1段"(代码段) 相对于用户程序开头的 偏移量
call calc_segment_base ; 结合用户程序目的地址,计算 "code_1段"(代码段) 的 段地址
mov [0x06],ax ; 将 "code_1段"(代码段) 的段地址 回写
;回写段重定位表
;===================== 用户程序 ==============================
; code_1_segment dd section.code_1.start ;[0x0c]
; data_1_segment dd section.data_1.start ;[0x10]
;===================== 用户程序 ==============================
mov cx,[0x0a] ;需要重新定位的表项数
mov bx,0x0c ;段重定位表起始偏移量
realloc:
mov dx,[bx+0x02] ;高16位
mov ax,[bx] ;低16位
call calc_segment_base
mov [bx],ax ;回写,填到低16位位置处即可
add bx,4 ;单个表项是4字节
loop realloc
;------------------------------------------------------------------------
; 跳转到用户程序执行
;------------------------------------------------------------------------
; 已经完成对入口地址的回写
;------------------------------------------------------------------------
;跳转到 用户程序 code_1段的 标号 start 处
jmp far [0x04]
;------------------------------------------------------------------------
; 子程序:read_hard_disk_0
;------------------------------------------------------------------------
; 功能: 从硬盘指定位置,以扇区为单位,读取数据放到内存指定位置
; 参数:
; 源地址 SI DI
; 目的地址 DS:[BX]
; 过程:
; (1)设置要读取的扇区数;
; (2)解读LBA模式扇区号;
; (3)请求硬盘读操作;
; (4)查询硬盘状态;
; (5)完成连续读取数据。
;------------------------------------------------------------------------
read_hard_disk_0:
; 寄存器压栈保护
push ax
push bx
push cx
push dx
;(1)设置要读取的扇区数;
mov dx,0x1f2 ;Ox1f2
mov al,1 ;扇区数=1
out dx,al
;(2)解读28位 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 ;0xe0 = 11100000B 第7位置1表示:LBA模式 第5位置1表示:从主硬盘读取数据
or al,ah
out dx,al
;(3)请求硬盘读操作;
inc dx ;0x1f7[命令端口]
mov al,0x20 ;读命令
out dx,al
;(4)查询硬盘状态;
.waits:
in al,dx ;0x1f7[状态端口]
and al,0x88 ;0x88 = 10001000B 保留第7位 BSY 以及 第3位DRQ
cmp al,0x08 ;0x08 说明硬盘已准备好与主机进行交换
jnz .waits ;条件转移指令
;(5)完成连续读取数据。
mov dx,0x1f0 ;0x1f0[数据端口]
mov cx,256 ;1个扇区是512字节=256字
.readw:
in ax,dx
mov [bx],ax ;加载到内存,新的段从偏移地址0x0000算起
add bx,2 ;以字为单位进行加载
loop .readw
;寄存器弹栈恢复
pop dx
pop cx
pop bx
pop ax
;子程序返回
ret
;------------------------------------------------------------------------
; 子程序:calc_segment_base
;------------------------------------------------------------------------
; 功能:将用户程序用SECTION标记的段的汇编地址转换为可以寻址内存的段基址
; 参数:
; DX 汇编地址的高16位
; AX 汇编地址的低16位
; 返回: AX作为16的段基址
;------------------------------------------------------------------------
calc_segment_base:
push dx
add ax,[cs:phy_base]
adc dx,[cs:phy_base+0x02]
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
;------------------------------------------------------------------------
; 加载程序结束
;------------------------------------------------------------------------
用户程序 hello.asm
;------------------------------------------------------------------------
; 用户程序开始
;------------------------------------------------------------------------
; 文件名:hello.asm
; 文件说明:功能程序代码(用户程序)
; 创建日期:3:38 2018/5/25
;------------------------------------------------------------------------
;------------------------------------------------------------------------
; 头部段
;------------------------------------------------------------------------
SECTION header vstart=0
;指令汇编地址是指令相对于用户程序开头的偏移量
;(1)用户程序长度
program_length dd program_end ;指令汇编地址[0x00]
;(2)用户程序入口地址
code_entry dw start ;偏移地址 [0x04]
dd section.code_1.start ;段地址 [0x06]
;(3)需要重新定位的表项数
realloc_tbl_len dw (header_end - code_1_segment)/4 ;[0x0a]
;(4)段重定位表项
code_1_segment dd section.code_1.start ;[0x0c]
data_1_segment dd section.data_1.start ;[0x10]
stack_segment dd section.stack.start ;[0x14]
header_end:
;------------------------------------------------------------------------
; 代码段
;------------------------------------------------------------------------
SECTION code_1 align=16 vstart=0
;用户程序入口
start:
;设置寄存器
mov ax,[stack_segment]
mov ss,ax
mov sp,stack_end
mov ax,[data_1_segment]
mov ds,ax
mov si,hello_world
call put_string
jmp $ ;无限循环
;------------------------------------------------------------------------
; 子程序:put_string
;------------------------------------------------------------------------
; 功能:在屏幕上显示字符串,字符串以0结尾
; 参数:DS:SI 指向字符串首地址
;------------------------------------------------------------------------
put_string:
push ax
push es
push si
mov ax,0xb800 ;显存
mov es,ax
mov bx,0
.put_char:
mov al,[si]
or al,al ;al=0?
jz .exit
mov byte [es:bx],al
inc si
add bx,2
jmp .put_char
.exit:
pop si
pop es
pop ax
ret
;------------------------------------------------------------------------
; 数据段
;------------------------------------------------------------------------
SECTION data_1 align=16 vstart=0
hello_world db 'H '
db 'E '
db 'L '
db 'L '
db 'O '
db ' '
db 'W '
db 'O '
db 'R '
db 'L '
db 'D '
db 0
;------------------------------------------------------------------------
; 栈段
;------------------------------------------------------------------------
SECTION stack align=16 vstart=0
resb 256
stack_end:
;------------------------------------------------------------------------
; 用户程序结束
;------------------------------------------------------------------------
SECTION trail align=16
program_end:
;------------------------------------------------------------------------
源码参考
[012][x86汇编语言]加载程序与用户程序
https://www.jianshu.com/p/e101fce6cdb5
[008][x86汇编语言]硬盘与显卡的访问与控制 加载程序c08_mbr.asm 用户程序c08.asm
https://www.jianshu.com/p/72c151606908
[009][x86汇编语言]学习加载程序的编写(c08_mbr.asm)
https://www.jianshu.com/p/add4f948a321
[010][x86汇编语言]学习用户程序的编写(c08.asm)
https://www.jianshu.com/p/4177e9ff49d6