Chapter 3:增強 MBR
===
:::info
這是讀書筆記

作者:鄭鋼
出版社:佳魁資訊股份有限公司
出版日期:2017/05/31
:::
>[time=2025-04-16]
---
:::warning
這邊討論都是 x86 架構下的組合語言,組譯工具是 ``nasm``,
如果是 ARM 架構下的組合語言,組譯工具是 ``armasm``。
:::
# 位址、section、vstart
* 編譯器的主要工作就是給各種符號編址。
* CPU 無法判斷是指令還是一般資料。
* 組合語言裡面的 section 僅是在邏輯上方便程式開發人員整理程式用。
* vstart 是虛擬的起始位址,loader 應該要根據其位址載入到記憶體相對應位址。
# CPU 的真實模式

>參考資料:[CPU Registers x86](https://wiki.osdev.org/CPU_Registers_x86)
* CPU 分成三個部分:控制單元、運算單元、儲存單元。
* CPU 定址模式:
* 暫存器定址:存取暫存器
* 立即數定址:常數
* 記憶體定址:存取記憶體。
* 真實模式下的 ret/retf。
* 真實模式下的 call。
* Near call
* Far call
* 真實模式下的 jmp。
* jmp short
* jmp near
* jmp far
* 標示暫存器 flags。

* Conditional Jumps

# 顯示器
## x86 I/O 通訊
* Intel x86 的 I/O 是使用 PMI/O。
>[!Note] ARM 通常使用 MMI/O。
## 顯示卡、顯示記憶體、顯示器

## 改寫 MBR,直接操作顯示卡
### Source Code
```asm=
;主引導程序
;
;LOADER_BASE_ADDR equ 0xA000
;LOADER_START_SECTOR equ 0x2
;------------------------------------------------------------
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax
; 清屏
;利用0x06號功能,上卷全部行,則可清屏。
; -----------------------------------------------------------
;INT 0x10 功能號:0x06 功能描述:上卷窗口
;------------------------------------------------------
;輸入:
;AH 功能號= 0x06
;AL = 上卷的行數(如果為0,表示全部)
;BH = 上卷行屬性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;無返回值:
mov ax, 0600h
mov bx, 0700h
mov cx, 0 ; 左上角: (0, 0)
mov dx, 184fh ; 右下角: (80,25),
; 因為VGA文本模式中,一行只能容納80個字符,共25行。
; 下標從0開始,所以0x18=24,0x4f=79
int 10h ; int 10h
; 輸出背景色綠色,前景色紅色,並且跳動的字符串"1 MBR"
mov byte [gs:0x00],'1'
mov byte [gs:0x01],0xA4 ; A表示綠色背景閃爍,4表示前景色為紅色
mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'M'
mov byte [gs:0x05],0xA4
mov byte [gs:0x06],'B'
mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'R'
mov byte [gs:0x09],0xA4
jmp $ ; 通過死循環使程序懸停在此
times 510-($-$$) db 0
db 0x55,0xaa
```
### Compile
```
nasm -o mbr.bin mbr.S
```
### Hard Disk Image
```
dd if=../code/mbr.bin of=./sr_hd60m.img bs=512 count=1 conv=notrunc
```
### Result

# bochs 偵錯方法
```
./bochs -debugger
```
查看 help 資訊

查看 BIOS 的入口點:0xFFFF0

反組譯機器碼

查看 MBR 的入口點:0x7c00
>[!Important] 一開始是沒有值,BIOS 負責把 MBR 從硬碟載到 0x7c00
順便可以看看 BIOS 吐出來的資訊,而游標的位置就是上一章的範例,MBR 印出字元的位置。

# 硬碟介紹
>[!Tip]一些歷史和原理介紹我就不贅述了。
## 如何操控硬碟
>參考資料:[Bochs' map of I/O ports to functions](https://bochs.sourceforge.io/techspec/PORTS.LST)
0x1F7 / 0x177 Status Register for read.

## 資料傳送方式
* CPU 直接取資料:registers, memory.
* ==Polling==
* ==Interrupt==
* DMA (硬體需支援)
* I/O處理器 (硬體需支援)
# MBR 載入 Loader
## 載入硬碟資料
### Source Code
>boot.inc
```asm=
;------------- loader和kernel ----------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
```
>mbr.S
```asm=
;主引導程序
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax
; 清屏
;利用0x06號功能,上卷全部行,則可清屏。
; -----------------------------------------------------------
;INT 0x10 功能號:0x06 功能描述:上卷窗口
;------------------------------------------------------
;輸入:
;AH 功能號= 0x06
;AL = 上卷的行數(如果為0,表示全部)
;BH = 上卷行屬性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;無返回值:
mov ax, 0600h
mov bx, 0700h
mov cx, 0 ; 左上角: (0, 0)
mov dx, 184fh ; 右下角: (80,25),
; 因為VGA文本模式中,一行只能容納80個字符,共25行。
; 下標從0開始,所以0x18=24,0x4f=79
int 10h ; int 10h
; 輸出字符串:MBR
mov byte [gs:0x00],'1'
mov byte [gs:0x01],0xA4
mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4
mov byte [gs:0x04],'M'
mov byte [gs:0x05],0xA4 ;A表示綠色背景閃爍,4表示前景色為紅色
mov byte [gs:0x06],'B'
mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'R'
mov byte [gs:0x09],0xA4
mov eax,LOADER_START_SECTOR ; 起始扇區lba地址
mov bx,LOADER_BASE_ADDR ; 寫入的地址
mov cx,1 ; 待讀入的扇區數
call rd_disk_m_16 ; 以下讀取程序的起始部分(一個扇區)
jmp LOADER_BASE_ADDR
;-------------------------------------------------------------------------------
;功能:讀取硬盤n個扇區
rd_disk_m_16:
;-------------------------------------------------------------------------------
; eax=LBA扇區號
; ebx=將數據寫入的內存地址
; ecx=讀入的扇區數
mov esi,eax ;備份eax
mov di,cx ;備份cx
;讀寫硬盤:
;第1步:設置要讀取的扇區數
mov dx,0x1f2
mov al,cl
out dx,al ;讀取的扇區數
mov eax,esi ;恢覆ax
;第2步:將LBA地址存入0x1f3 ~ 0x1f6
;LBA地址7~0位寫入端口0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位寫入端口0x1f4
mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al
;LBA地址23~16位寫入端口0x1f5
shr eax,cl
mov dx,0x1f5
out dx,al
shr eax,cl
and al,0x0f ;lba第24~27位
or al,0xe0 ; 設置7~4位為1110,表示lba模式
mov dx,0x1f6
out dx,al
;第3步:向0x1f7端口寫入讀命令,0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第4步:檢測硬盤狀態
.not_ready:
;同一端口,寫時表示寫入命令字,讀時表示讀入硬盤狀態
nop
in al,dx
and al,0x88 ;第4位為1表示硬盤控制器已準備好數據傳輸,第7位為1表示硬盤忙
cmp al,0x08
jnz .not_ready ;若未準備好,繼續等。
;第5步:從0x1f0端口讀數據
mov ax, di
mov dx, 256
mul dx
mov cx, ax ; di為要讀取的扇區數,一個扇區有512字節,每次讀入一個字,
; 共需di*512/2次,所以di*256
mov dx, 0x1f0
.go_on_read:
in ax,dx
mov [bx],ax
add bx,2
loop .go_on_read
ret
times 510-($-$$) db 0
db 0x55,0xaa
```
### Compile
```shell
nasm -I inc/ -o mbr.bin mbr.S
```
### Hard Disk Image
```
dd if=../code/mbr.bin of=./sr_hd60m.img bs=512 count=1 conv=notrunc
```
## 準備一個簡單的 Loader
### Source Code
>loader.S
```asm=
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
; 輸出背景色綠色,前景色紅色,並且跳動的字符串"1 MBR"
mov byte [gs:0x20],'2'
mov byte [gs:0x21],0xA4 ; A表示綠色背景閃爍,4表示前景色為紅色
mov byte [gs:0x22],' '
mov byte [gs:0x23],0xA4
mov byte [gs:0x24],'L'
mov byte [gs:0x25],0xA4
mov byte [gs:0x26],'O'
mov byte [gs:0x27],0xA4
mov byte [gs:0x28],'A'
mov byte [gs:0x29],0xA4
mov byte [gs:0x2a],'D'
mov byte [gs:0x2b],0xA4
mov byte [gs:0x2c],'E'
mov byte [gs:0x2d],0xA4
mov byte [gs:0x2e],'R'
mov byte [gs:0x2f],0xA4
jmp $ ; 通過死循環使程序懸停在此
```
### Compile
```shell
nasm -I inc/ -o loader.bin loader.S
```
### Hard Disk Image
```shell
dd if=../code/loader.bin of=./sr_hd60m.img bs=512 count=1 seek=2 conv=notrunc
```
### Result
