[013][x86汇编语言]从硬盘主引导扇区启动,显示hello world的“迷你操作系统”

学习笔记

《x86汇编语言:从实模式到保护模式》
https://www.jianshu.com/p/d481cb547e9f

代码功能

  • 硬盘主引导扇区启动,显示hello world的 “迷你操作系统”

本文

  • 硬盘图片

[013][x86汇编语言]从硬盘主引导扇区启动,显示hello world的“迷你操作系统”_第1张图片
硬盘

http://www.sohu.com/a/63146493_115161

代码联动

  • 软盘主引导扇区启动,显示hello world的 “迷你操作系统”

https://www.jianshu.com/p/2c8ace0ee240

  • 软盘图片
[013][x86汇编语言]从硬盘主引导扇区启动,显示hello world的“迷你操作系统”_第2张图片
软盘.jpg

运行测试

[013][x86汇编语言]从硬盘主引导扇区启动,显示hello world的“迷你操作系统”_第3张图片
从硬盘主引导扇区启动,显示hello world的“迷你操作系统”.png

代码使用

  • 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

你可能感兴趣的:([013][x86汇编语言]从硬盘主引导扇区启动,显示hello world的“迷你操作系统”)