Chapter 02:MBR === :::info Master Boot Record(MBR),一切軟體的起點,千里之行,始於足下。 MBR 主要有兩個任務: &emsp; 1. Load loader。 &emsp; 2. Jump to loader。 ::: >[time=Sun, Aug 17, 2025 12:04 AM] --- https://youtu.be/emxQnZTVUTo <iframe width="560" height="315" src="https://www.youtube.com/embed/emxQnZTVUTo?si=Db742bB-u822GDyK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> # Background Knowledge 首先,先補充一些非常基礎的背景知識~ ## Von Neumann Architecture ==1945.06.30 由美籍猶太人 Von Neumann 所提出==,乃至今日的 Computer Science 都是以此架構為基礎。CPU 都是從 memory 提取指令來執行,並且按照順序沿著 memory 從低位址到高位址循序執行。除此之外,指令本身可以要求 CPU 去讀取 device 資料,並且寫資料到 device。CPU 都是先把資料放在內部 register,然後再進行運算。 ![截圖 2025-08-17 凌晨12.50.38](https://hackmd.io/_uploads/HJr-P40_ex.png) 這邊推薦一個遊戲「Human Resource Machine」。 這款遊戲會讓你對於此架構,乃至於整個 CPU 的運作更加印象深刻。 <iframe width="560" height="315" src="https://www.youtube.com/embed/vHhlwPee6zA?si=vHrBswDr3f59qv-g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> ## CPU Architecture 可以想像一台家用電器,它的功能在出廠的時候就已經決定一切了,使用者能做的就是透過提供的介面按鈕去設定和操控家用電器。CPU 也是如此,晶片出廠的時候線路設計都已經大致抵定,差別是我們軟體可以透過 machine language(機器語言) 去操控 CPU 硬體去完成特定任務。但是 machine language 是由一群 01 組成的,非常不利於人類閱讀和理解,因此出現 assembly language(組合語言) 方便程式設計師開發,而這中間的轉換過程,就是靠 assembler(組譯器) 來協助完成。 ![ZF4p5](https://hackmd.io/_uploads/BkUtWHAdee.jpg) 目前世界上有兩個主流的 CPU Architecture: x86(CISC) and ARM(RISC)。可以想像這兩大 CPU 架構的操作方式是截然不同的,因此他們的 assembly language and assembler 也是不同的。 * x86 使用 ==nasm== * ARM 使用 ==armasm== >[!Note]本次專案專注於 x86 CPU architecture。 ## Assembly Language (x86) > Reference: [x86 Assembly Guide](https://www.cs.virginia.edu/~evans/cs216/guides/x86.html) * section/label: 僅是在邏輯上供開發人員方便整理程式之用。 * vstart: 並不是真正載入的位置,而是告訴編譯器,後面所有的指令都從 vstart 開始編吧!編譯器只負責編址,不具備載入的功能。 * align=16: 保證編譯出來的位址是 16 的整數倍,換句話說,最右邊 4 bits 一定為 0。 * DB - Define 1 byte. * DW - Define 2 bytes. * DD - Define 4 bytes. * DQ - Define 8 bytes. ```asm= section aaa vstart=0x1234 mov ax, $$ mov ax, section.bbb.start mov ax, [var1] mov ax, [var2] jmp $ section ddd align=16 mov ax, $$ mov ax, section.aaa.start ccc: jmp ccc section bbb var1 dw 0x9527 var2 dd 0x12345678 ``` ``` nasm test.s -o test.o ndisasm test.o ``` ![截圖 2025-08-18 下午6.46.30](https://hackmd.io/_uploads/Byx3VYeKee.png) ## Instruction Set (x86) > Reference: > [Wiki](https://en.wikipedia.org/wiki/X86_instruction_listings) > [Intel 官方手冊](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) > [有人整理成網頁](https://c9x.me/x86/) 這邊要先提醒一點,==CPU 的運算主要是 CPU 內部的 registers==,因此資料要先從外部裝置載到 register 上,運算完後再儲存回外部裝置。 ![1_b08g8TfyCK54sk-9lszgfA](https://hackmd.io/_uploads/SJ2EISCOee.png) 這邊列出要完成 MBR 所需的 CPU instructions。 ### Data Movement ![截圖 2025-08-17 凌晨2.10.11](https://hackmd.io/_uploads/HJ0oYB0dee.png) ![截圖 2025-08-17 下午6.19.09](https://hackmd.io/_uploads/ByHp2Q1Fge.png) ![截圖 2025-08-17 下午6.20.02](https://hackmd.io/_uploads/B1fxa7ktee.png) ### Arithmetic/Logic ![截圖 2025-08-17 下午6.15.01](https://hackmd.io/_uploads/rkLpjQkFel.png) ![截圖 2025-08-17 下午6.15.55](https://hackmd.io/_uploads/Bysgh7yKgx.png) ![截圖 2025-08-17 下午6.16.28](https://hackmd.io/_uploads/BycS371Kgl.png) ![截圖 2025-08-17 下午6.21.02](https://hackmd.io/_uploads/B16mTQ1tlg.png) ![截圖 2025-08-17 下午6.26.01](https://hackmd.io/_uploads/HJfwC7JYll.png) ![截圖 2025-08-20 下午4.29.52](https://hackmd.io/_uploads/SkuhPWQtex.png) ### Control Flow ![截圖 2025-08-17 凌晨2.11.27](https://hackmd.io/_uploads/rJlxcSC_eg.png) ![截圖 2025-08-17 凌晨2.12.54](https://hackmd.io/_uploads/SyLrcB0_el.png) ![截圖 2025-08-17 凌晨2.15.07](https://hackmd.io/_uploads/Hk669B0_el.png) ![截圖 2025-08-17 凌晨2.14.23](https://hackmd.io/_uploads/SJFq5r0Oll.png) ![截圖 2025-08-17 下午6.22.46](https://hackmd.io/_uploads/Hk3n6QkFex.png) ![截圖 2025-08-17 下午6.23.13](https://hackmd.io/_uploads/rJAhpmktlg.png) ![截圖 2025-08-17 下午6.27.29](https://hackmd.io/_uploads/S1bhC71Kge.png) ## CPU Registers (x86) > Reference: https://wiki.osdev.org/CPU_Registers_x86 ### General-Purpose Registers ![截圖 2025-08-17 下午6.00.22](https://hackmd.io/_uploads/rkewOXkKgx.png) ### Segment Registers ![six_segments](https://hackmd.io/_uploads/r1sJ4QyFlg.png) ### Status Register (8086) ![0wE3s](https://hackmd.io/_uploads/rJQuYX1tex.png) ### Control Register ![062111_1434_x86x8664CPU1](https://hackmd.io/_uploads/Bk52qQ1txx.png) ## Memory Addressing Memory Addressing(記憶體定址) 是指 CPU 存取記憶體中資料的方法,每個記憶體位置都有一個唯一的編號,稱為記憶體位址,就像是資料的門牌號碼。Address bus(位址匯流排) 的寬度決定了可以定址的範圍。例如: | Address Bus | Memory Address Range | - | - | 1 bit | 2 ($2^1$) | 2 bits | 4 ($2^2$) | 4 bits | 16 ($2^4$) | 6 bits | 64 ($2^6$) | 8 bits | 256 ($2^8$) | 10 bits | ==1K ($2^{10}$)== | 16 bits | 64K ($2^{16}$) | 20 bits | ==1M ($2^{20}$)== | 24 bits | 16M ($2^{24}$) | 30 bits | ==1G ($2^{30}$)== | 32 bits | 4G ($2^{32}$) ## 讓我們回到 Intel 8086 (1978年) 要了解 x86 MBR 就必須沿著歷史脈絡回到當初 x86 系列最輝煌的時刻:Intel 8086(1978年)。因為很多我們現今看起來有點奇怪的設計,都是當時的時代背景所造成的,所以我們必須回到當時的時空背景,才能更深刻的瞭解 x86 MBR 的設計。Intel 8086 是由英特爾公司於1976年初開始設計,1978年年中發表的Intel第一款 16 位元微處理器。8086 是 Intel 最成功的 x86 架構處理器系列的開端。 ![Hardware-Intel-8086](https://hackmd.io/_uploads/Sk4H4XJtxl.jpg) ## Memory Segmentation (8086) 首先,最簡單單純的做法就是根據 CPU address bus(位址匯流排) 所支援的寬度直接記憶體定址。 但是這種做法會有什麼問題呢? 如果 CPU memory addressing 沒有 Segmentation(分段)的概念,編譯過後的程式,遇到指令中含有位址的數字,==只能用絕對實體位址來看待它==,因此整個程式必須放在記憶體中固定的地方;如果放到記憶體的其他地方,會導致存取到錯誤的絕對實體位址。 因此為了方便記憶體管理以及充分使用記憶體,Intel 8086(1978年)開始導入 memory segmentation 技術。CPU 對於編譯後的程式,==有能力以相對實體位址來看待它==。 ![real_mode_mem_seg (1)](https://hackmd.io/_uploads/HyeJ-VJYex.jpg) 記憶體容量當時正在從 KB 轉成 MB 的時代,所以 8086 為了能夠支援最大 1MB 的記憶體定址能力,address bus 從 16 bits 擴充到 20 bits。這也是為何 segment base address 需要左移 4 bits,如此才能讓 ==CPU 最大定址空間提升到 1 MB==。 記憶體發展史: | 時代 | 個人電腦典型記憶體 | 技術 | | ------ | --------------------------- | ----------------- | | 1950s | 幾 KB | 磁芯記憶體 | | 1970s | 4–64 KB | DRAM 問世 | | 1980s | 256 KB–4 MB | IBM PC、640 KB 限制 | | 1990s | 4–128 MB | Windows 95/98 | | 2000s | 256 MB–4 GB | DDR, DDR2 | | 2010s | 4–16 GB | DDR3, DDR4 | | 2020s | 16–64 GB (PC),12–16 GB (手機) | DDR5, LPDDR5, HBM | | 2030s? | 128 GB+ (PC),32 GB+ (手機) | DDR6, 3D 堆疊記憶體 | ## Memory Map (8086) >Reference: [Memory Map (x86)](https://wiki.osdev.org/Memory_Map_(x86)) 典型的 Intel 8086 記憶體分配如下: | 位址範圍 (Hex) | 大小 | 用途 | | ----------------- | ------ | ----------------------------------------| | `0xF_0000 – 0xF_FFFF` | 64 KB | **系統 BIOS ROM**| | `0xC_0000 – 0xE_FFFF` | 192 KB | **顯示卡 BIOS ROM**| | `0xA_0000 – 0xB_FFFF` | 128 KB | **顯示記憶體 (Video RAM)**| | `0x0_0000 – 0x9_FFFF` | 640 KB | **可用 RAM** (程式與資料)| ![IMG_3389](https://hackmd.io/_uploads/BkF_bj25el.jpg) Intel 8086 架構實際把這 1 MB 切成 640 KB RAM + 384 KB I/O/ROM,形成「==640 KB memory barrier==」。因此就實務上而言,8086+DOS 1.0 可以搭載的 RAM 大小為 ==32 KB~640 KB==。 ## Power Reset (8086) 當 8086/8088 重置 (RESET) 時: CS = `0xF000` IP = `0xfff0` 實體位址 = CS × 16 + IP = `0xf_fff0` 👉 也就是 1 MB 空間的最後 16 bytes(`0xf_fff0 ~ 0xf_ffff`),CPU 從這裡開始執行。 這段區域通常在 BIOS ROM 裡,只會放一個跳躍指令,跳到 BIOS 主要程式。 ``` jmp f000:e05b ``` ## BIOS (8086) > Reference: [bochs BIOS source code](https://github.com/ipxe/bochs/blob/master/bios/rombios32.c) >[!Note] bochs 採用 x86 BIOS open source: SeaBIOS >Reference: >[Wiki](https://en.wikipedia.org/wiki/SeaBIOS) >[SeaBIOS](https://www.seabios.org/downloads/) 1. BIOS 會進行 POST (Power-On Self Test),檢查記憶體、硬體。 2. 建立中斷向量表 Interrupt Vector Table(IVT)。 3. 接著 BIOS 會搜尋可開機裝置(磁碟、磁帶、ROM 卡)。 4. 若是磁碟開機,BIOS 會去讀取 磁碟第 0 磁區 (MBR, Master Boot Record)。 5. 將 MBR 程式載入記憶體位址 `0x7c00`,並且跳到該處開始執行 MBR 程式。 ## BIOS IVT (8086) >Reference: [BIOS 中斷呼叫](https://zh.wikipedia.org/zh-tw/BIOS%E4%B8%AD%E6%96%B7%E5%91%BC%E5%8F%AB) BIOS 會在 `0x000 - 0x3FF` 處建立中斷向量表 IVT,並填寫中斷常式。 可支援 256 個中斷,每個中斷儲存著 4 bytes 中斷常式地址,總共 1 KB。 IVT 的內容大概長這樣: | 中斷號 | Handler CS:IP | 功能 | | --- | -------------------| ----------------------- | | 00h | F000:FF53 | 除零錯誤 (Divide Error) | | 01h | F000:FF53 | 單步除錯 (Debug Exception) | | 02h | F000:FF53 | NMI 非屏蔽中斷 | | 03h | F000:FF53 | Breakpoint (INT3) | | 04h | F000:FF53 | Overflow (INTO) | | 05h | F000:FF53 | Print Screen / Bound | | 06h | F000:FF53 | 無效 Opcode | | 07h | F000:FF53 | Device Not Available | | 08h | F000:FEA5 | IRQ0 系統計時器 | | 09h | F000:E987 | IRQ1 鍵盤 | | 0Ah | F000:E9DF | IRQ2 (PIC 級聯) | | 0Bh | F000:E9DF | IRQ3 (COM2/COM4) | | 0Ch | F000:E9DF | IRQ4 (COM1/COM3) | | 0Dh | F000:E9DF | IRQ5 (LPT2/Sound) | | 0Eh | F000:EF57 | IRQ6 (Floppy) | | 0Fh | F000:E9DF | IRQ7 (LPT1) | | 10h | C000:014A | BIOS 視訊服務 | | 11h | F000:F84D | BIOS 設備清單 | | 12h | F000:F841 | BIOS 記憶體大小 | | 13h | F000:E3FE | BIOS 磁碟服務 | | 14h | F000:E739 | BIOS 串列埠服務 | | 15h | F000:F859 | BIOS 系統服務 (A20, etc.) | | 16h | F000:E82E | BIOS 鍵盤服務 | | 17h | F000:EFD2 | BIOS 印表機服務 | | 18h | F000:969B | ROM BASIC | | 19h | F000:E6F2 | 開機載入 (Bootstrap) | | 1Ah | F000:FE6E | RTC / 系統時鐘 | | 1Bh | F000:FF53 | Ctrl-Break Handler | | 1Ch | F000:FF53 | Timer Tick Hook | BIOS 跑起來建立完 IVT 後,RAM 會長這樣: | 位址範圍 (Hex) | 大小 | 用途 | | ----------------- | ------- | ----------------------------------------| | `0xF_0000 – 0xF_FFFF` | 64 KB | **系統 BIOS ROM**| | `0xC_0000 – 0xE_FFFF` | 192 KB | **顯示卡 BIOS ROM**| | `0xA_0000 – 0xB_FFFF` | 128 KB | **顯示記憶體 (Video RAM)** | | `0x0_0500 – 0x9_FFFF` | 638.75 KB | **可用 RAM** (程式與資料)| | `0x0_0400 - 0x0_04FF` | 256 B | ==BDA (BIOS data area)==| | `0x0_0000 - 0x0_03FF` | 1 KB | ==BIOS IVT== ## 0x7C00 的由來 個人電腦一定要有個作業系統來完成任務,而當時(1981年) IBM PC 搭配 Intel 8086 的作業系統是 DOS 1.0,而 ==DOS 1.0 最低記憶體需求為 32 KB==,因此當時的 BIOS 就是以最低 32 KB RAM 大小來設計的。MBR 為了避免過早被其他程式覆蓋,所以 MBR 盡可能地放在 RAM 的頂端。雖然 MBR 本身只有 512 B 大小,但是要為堆疊預留一點空間,所以當初設計在 32 KB 的頂端預留 1 KB 給 MBR 使用,32 KB - 1 KB(`0x8000 - 0x400 = 0x7C00`),這就是為何 BIOS 設計成將 MBR 載入到 `0x7C00` 的原因,為了 x86 的相容,此設計也就保留下來了。 ![截圖 2025-08-18 晚上8.35.04](https://hackmd.io/_uploads/B1eEC9gYxg.png) BIOS 成功載入 MBR 後,RAM 會長這樣: | 位址範圍 (Hex) | 大小 | 用途 | | ----------------- | ------ | ----------------------------------------| | `0xF_0000 – 0xF_FFFF` | 64 KB | **系統 BIOS ROM**| | `0xC_0000 – 0xE_FFFF` | 192 KB | **顯示卡 BIOS ROM**| | `0xA_0000 – 0xB_FFFF` | 128 KB | **顯示記憶體 (Video RAM)**| | `0x0_8000 - 0x9_FFFF` | 608 KB | **可用 RAM** (程式與資料) | | `0x0_7E00 - 0x0_7FFF` | 512 B | ==MBR stack== | | `0x0_7C00 - 0x0_7DFF` | 512 B | ==MBR== | | `0x0_0500 – 0x9_FFFF` | 29.75 KB | **可用 RAM** (程式與資料)| | `0x0_0400 - 0x0_04FF` | 256 B | ==BDA (BIOS data area)==| | `0x0_0000 - 0x0_03FF` | 1 KB | BIOS IVT ![截圖 2025-08-20 凌晨2.15.19](https://hackmd.io/_uploads/S1dw1Bztxx.png) ## MBR Magic Number >Reference: [Wiki](https://en.wikipedia.org/wiki/Master_boot_record) BIOS 會去檢查每個一顆硬碟的第 0 sector 看有沒有 MBR 程式,檢查方式就是 512 bytes 最後兩個 byte 是不是 `0x55` 和 `0xaa`。 # Boot Disk ## 製作 Boot Disk ``` cd bochs-2.8/ ./bximage ``` ![截圖 2025-08-19 下午2.57.36](https://hackmd.io/_uploads/SJYgzoZtgl.png) ## 設定 bochsrc ``` cd bochs-2.8/ vim .bochsrc ``` ![截圖 2025-08-19 下午3.07.13](https://hackmd.io/_uploads/SJuJXoZtxl.png) ## Checkpoint >[!Caution] 這個錯誤是因為我們還沒製作 MBR。 ![截圖 2025-08-19 下午3.09.56](https://hackmd.io/_uploads/HykOQsbtle.png) # Load & Jump MBR 終於要開始 coding 了!這邊簡單設計一下我的檔案目錄架構。我在 shared folder 下新增一個目錄 `code/`,原因是如果你想要把 source code 從 Guest(Ubuntu) 抓到 Host(mac OS) 上的話相當方便,另外盡量把 source code 和 bochs 程式主體分開,原因是這樣也方便你用同一份 source code 跑在不同版本的 bochs 上面。 ![截圖 2025-08-19 下午4.49.36](https://hackmd.io/_uploads/HyyAqnZtgx.png) >[!Note] 理論上你會需要先安裝 tree 才能使用 tree command。 ``` sudo apt install tree ``` `code/` 底下的架構規劃長這樣。 * boot: 所有 kernel 之前的 source code。 * out: 準備放到 hard disk 的 output bin file。 ![截圖 2025-08-19 下午4.55.55](https://hackmd.io/_uploads/B1XDn2WYee.png) ## Source code https://github.com/srhuang/a-os/commit/cecfd3192b1404d724afe171d92e49f81737c63b ## Compile ```sh= nasm boot/mbr.s -o out/mbr.bin ``` ## Put on hard disk ```sh= dd if=../code/out/mbr.bin of=./60mb.img bs=512 count=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-19 下午5.11.29](https://hackmd.io/_uploads/rk1lx6btgg.png) ![截圖 2025-08-19 下午6.51.06](https://hackmd.io/_uploads/BkOHv0WYlg.png) # MBR Print Log > Reference: > https://en.wikipedia.org/wiki/INT_10H > https://en.wikipedia.org/wiki/BIOS_color_attributes ## Source code https://github.com/srhuang/a-os/commit/5320745bf6a1eeace2d39c4c7d4fc0e54daa44b4 ## Compile ```sh nasm boot/mbr.s -o out/mbr.bin ``` ## Put on hard disk ```sh dd if=../code/out/mbr.bin of=./60mb.img bs=512 count=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-20 凌晨1.20.49](https://hackmd.io/_uploads/HkY9z4ftxx.png) # Loader Preparation 在這一章節,我們只規劃一個極度簡單的 loader,目的是讓 MBR 能夠順利的跳轉到 loader,繼續往核心載入前進。 * Hard Disk:MBR 放在第 0 sector,所以我直接將 loader 放在第 1 sector。 * RAM:規劃放在盡可能低位址空間,原因是 loader 可能會長到很大,因此如果放在高位址空間,會很難掌握要預留多少,如果屆時超出預期,有可能要改動 loader 的起始位址,這不是我們所預期要發生的事。另外,也要預留一點低位址空間可以作為 loader stack,而 stack 是往低位址空間慢慢長大的。綜合以上考量,loader 會放在 `0x0800`,而且 top of stack 也是 `0x0800`,stack 最大支援空間為 `0.75 KB`,loader 最大支援空間為`30 KB`。 ![截圖 2025-08-20 凌晨4.15.17](https://hackmd.io/_uploads/HyGcoUzYgl.png) ## Source code https://github.com/srhuang/a-os/commit/b4915fd39ef4e9415c4072370cdb372b35aab77f ## Compile ```sh nasm boot/loader.s -I boot/ -o out/loader.bin ``` ## Put on hard disk ```sh dd if=../code/out/loader.bin of=./60mb.img bs=512 count=1 seek=1 conv=notrunc ``` # Load $ Jump Loader MBR 最後的任務就是從 hard disk 載入 loader 到 RAM 上,然後跳轉過去執行 loader。首先我們要先學會如何操控 hard disk。 >Reference: >[Bochs' map of I/O ports to functions](https://bochs.sourceforge.io/techspec/PORTS.LST) >[osdev wiki](https://wiki.osdev.org/ATA_PIO_Mode#Addressing_Modes) ![截圖 2025-08-20 凌晨4.44.37](https://hackmd.io/_uploads/HJ4UMvMFex.png) ![upload_dabef89ac845856a2886aaafa2451acd](https://hackmd.io/_uploads/ByeQbDfKgl.png) ![截圖 2025-08-20 清晨5.09.21](https://hackmd.io/_uploads/HyIXuDzKee.png) ![截圖 2025-08-20 清晨5.09.56](https://hackmd.io/_uploads/ry8r_vfFgl.png) ![截圖 2025-08-20 下午4.22.41](https://hackmd.io/_uploads/HJYxLZmKeg.png) 主要分成以下五大步驟: * 設定 sector count,預計要讀取幾個 sector。 * 設定 hard disk LBA 地址。 * 寫入讀取指令。 * Polling 等待完成。 * 讀取資料到 RAM。 :::warning 另外要注意的是: * port number 8 bits : 可以直接使用立即數。 * port number 16 bits : 必須使用 register dx。 * in/out 使用的 data register 只能是 al(8 bits) 或是 ax(16 bits)。 ::: ## Source code https://github.com/srhuang/a-os/commit/b4915fd39ef4e9415c4072370cdb372b35aab77f ## Compile ``` nasm boot/mbr.s -I boot/ -o out/mbr.bin ``` ## Put on hard disk ``` dd if=../code/out/mbr.bin of=./60mb.img bs=512 count=1 conv=notrunc ``` ## Checkpoint ![截圖 2025-08-21 凌晨2.11.43](https://hackmd.io/_uploads/r1eMgqQFee.png)