試着解讀 Win7 的 MBR(NASM 反彙編)
; int 19h 會將引導扇區(MBR)的內容讀到 0x7c00 位置,然後開始執行
; 0x7c00 位置的代碼就是這裏的代碼
; 段指針 ss,ds,es 都爲 0
; 棧頂 = 0x7c00
00000000 33C0 xor ax,ax
00000002 8ED0 mov ss,ax
00000004 BC007C mov sp,0x7c00
00000007 8EC0 mov es,ax
00000009 8ED8 mov ds,ax
; ==============================
; 將引導扇區複製一份到 0x600 處,然後從副本中繼續執行
; 0x500-0x7BFF 是自由空間,可以隨意使用
; movsb 源地址 DS:SI = 0:0x7c00
; movsb 目標地址 DS:DI = 0:0x600
; movsb 長度 CX = 0x200 // 即 512
0000000B BE007C mov si,0x7c00
0000000E BF0006 mov di,0x600
00000011 B90002 mov cx,0x200
; 設置 SI、DI 爲自增模式(std 爲自減模式)
00000014 FC cld
; 複製字節 DS:SI -> DS:DI,重複 CX 次
00000015 F3A4 rep movsb
; 跳轉到 0:0x61C 處繼續執行
00000017 50 push ax
00000018 681C06 push word 0x61c
; pop IP; pop CS
0000001B CB retf
; ==============================
; 0:0x61C 就是這裏
; 允許 CPU 響應可屏蔽中斷
0000001C FB sti
; ==============================
; 查找活動分區
; 只有 4 個分區表項,所以循環 4 次
0000001D B90400 mov cx,0x4
; 從 0x600 開始的 0x1BE 位置,即後面的分區表位置
00000020 BDBE07 mov bp,0x7be
; 循環開始
; 分區表項的第一字節爲“活動分區標記”:
; 0x80-0xFF 表示活動分區(有符號數爲 -128 ~ -1)
; 0x00 表示非活動分區
; 其它值(0x00 - 0x7F)爲無效標記(有符號數爲 1 ~ 127)
; 判斷標記是否小於 0,即判斷標記是否在 0x80-0xFF 範圍內
; cmp 是有符號數比較
00000023 807E0000 cmp byte [bp+0x0],0x0
; 如果在 0x80-0xFF 範圍內,則跳轉到 0x34 開始讀取“分區引導記錄 PBR”的代碼(一個扇區)
00000027 7C0B jl 0x34
; 不在 0x80-0xFF 範圍內,也不是 0,肯定在 0x01-0x7F 範圍內,則跳轉到 0x13B,打印錯誤信息
; short、near、far 在反彙編中體現不出差別,都會跳轉到目標所指的地址
00000029 0F850E01 jnz near 0x13b
; 如果是 0,則表示非活動分區,繼續分析下一個分區表項
; 每個分區表項的長度爲 16 字節,也就是 0x10
0000002D 83C510 add bp,byte +0x10
; 檢查下一個分區表項,直到 CX = 0
00000030 E2F1 loop 0x23
; ==============================
; 循環 4 次都沒有發現活動分區,無法啓動 OS,按照規範調用 Int 18h
; 早期的 BIOS 的 Int 18h 中斷服務程序就是啓動 ROM-Basic,
; 現在的 BIOS 一般是打印錯誤信息,然後 hlt(掛起 CPU)。
; ROM BASIC 是一個 ROM 解釋器,允許用戶運行和創建 BASIC 程序。
; 今天的電腦不再包括 BASIC ROM; 但是,使用 IBM 兼容計算機的用戶
; 仍可能會遇到與 ROM BASIC 相關的錯誤消息。
00000032 CD18 int 0x18
; ==============================
; 找到活動分區,嘗試讀取活動分區的第一個扇區
; 將啓動設備的“設備號”寫入 [bp+0x0](分區表項的第一個字節,活動分區標記的位置)
; dl 中的值是 int 19h 留下的“啓動設備號”
00000034 885600 mov [bp+0x0],dl
; 保存當前分區表項的地址(int 13h 可能會修改 bp)
00000037 55 push bp
; 分區表項之後的第二個字節(用來存儲嘗試次數)
; 如果讀取扇區失敗,會再次嘗試讀取,總共嘗試 5 次
00000038 C6461105 mov byte [bp+0x11],0x5
; 分區表項之後的第一個字節(用來標記是否支持 int 13h 擴展)
0000003C C6461000 mov byte [bp+0x10],0x0
; ==============================
; 檢查是否支持 int 13h 擴展
; int 13h 的 41h 號功能:檢查是否支持 Int 13H 擴展
00000040 B441 mov ah,0x41
; int 13h 的參數:固定值 55AAh
; 另一個參數 DL:驅動器號
00000042 BBAA55 mov bx,0x55aa
; 檢查是否支持 int 13h 擴展
; 返回值:成功 CF = 0,失敗 CF = 1
; 成功 BX = 0xaa55,失敗 BX = 0x55aa
00000045 CD13 int 0x13
; 恢復當前分區表項的地址
00000047 5D pop bp
; ==============================
; 如果進位標誌(即 CF)爲 1,則表示不支持 int 13h 的擴展功能,
; 則跳轉到 0x59,使用普通 int 13h 讀磁盤
00000048 720F jc 0x59
; 如果返回值 BX 不是 AA55h,則表示不支持 int 13h 的擴展功能,
; 則跳轉到 0x59,使用普通 int 13h 讀磁盤
0000004A 81FB55AA cmp bx,0xaa55
0000004E 7509 jnz 0x59
; 若支持 Int 13h 擴展,則返回值 CX 的第 1 位表示是否支持第一個子集,
; 第 2 位表示是否支持第二個子集。
; test 指令將兩個操作數進行邏輯與運算,並根據運算結果設置相關的標誌位。
; 這裏檢查 int 13h 是否支持第一個子集。
00000050 F7C10100 test cx,0x1
; 若不支持,則跳轉到 0x59,使用普通 int 13h 讀磁盤
00000054 7403 jz 0x59
; 將 [bp+0x10] 的值設置爲非 0,表示支持 int 13h 擴展
00000056 FE4610 inc byte [bp+0x10]
; ==============================
; 開始嘗試讀取扇區,最多嘗試 5 次
; pushad 指令壓入 32 位寄存器(保護現場)
; 入棧順序是:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI。
00000059 6660 pushad
; 判斷是否支持 int 13h 擴展(非 0 支持,0 不支持)
0000005B 807E1000 cmp byte [bp+0x10],0x0
; 若不支持,則跳轉到 0x87,使用普通 int 13h 的 CHS 方式讀取數據
0000005F 7426 jz 0x87
; ==============================
; 下面開始使用 int 13h 的擴展功能讀取分區引導記錄(一個扇區)
; 擴展 Int 13h 通過 LBA(邏輯塊地址)讀取磁盤數據
; 在棧中填寫“磁盤地址數據包”(這裏的塊大小爲扇區大小,即 512 字節)
; struct DiskAddressPacket {
; BYTE PacketSize; // 本結構體大小(值爲 16)
; BYTE Reserved; // 保留爲 0
; WORD BlockCount; // 要讀取多少個塊
; DWORD BufferAddr; // 讀取後保存到哪裏(段:偏移)
; QWORD BlockNum; // 從哪個塊開始讀取(塊號從 0 開始計數)
; }
; 從哪個塊開始讀取(高 32 位)
00000061 666800000000 push dword 0x0
; 從哪個塊開始讀取(低 32 位)
; [bp+0x8] 是活動分區的起始扇區位置(相對於磁盤開頭的偏移量,從 0 開始計數)
00000067 66FF7608 push dword [bp+0x8]
; 讀取後保存到哪裏(段地址)
0000006B 680000 push word 0x0
; 讀取後保存到哪裏(偏移地址)
0000006E 68007C push word 0x7c00
; 讀多少個塊
00000071 680100 push word 0x1
; 結構體大小:16 字節(同時保留的字節爲 0)
00000074 681000 push word 0x10
; ==============================
; int 13h 的 42h 號功能:擴展讀
00000077 B442 mov ah,0x42
; int 13h 的參數 DL:驅動器號
00000079 8A5600 mov dl,[bp+0x0]
; int 13h 的參數 DS:SI = 0:SP 表示“磁盤地址數據包”的地址(之前在棧中填寫的數據)
0000007C 8BF4 mov si,sp
; 開始擴展讀
0000007E CD13 int 0x13
; ==============================
; 將標誌寄存器的低 8 位寫入 AH 中(保護現場)
00000080 9F lahf
; 擴展讀完畢,將 SP 指向下一個分區表項(add 指令會影響標誌寄存器)
00000081 83C410 add sp,byte +0x10
; 將 AH 的內容寫入標誌寄存器的低 8 位(恢復現場)
00000084 9E sahf
; 跳過普通 int 13h 調用,跳轉到 0x9b,檢查是否讀取成功
00000085 EB14 jmp short 0x9b
; ==============================
; 下面開始使用普通 int 13h 讀取分區引導記錄(一個扇區)
; 普通 int 13h 通過 CHS(柱面、磁頭、扇區)讀取磁盤數據
; 通過 int 13h 的 02h 號功能讀取扇區
; AL = 01 表示要讀取的扇區數
00000087 B80102 mov ax,0x201
; ES:BX 表示讀取後保存到哪裏
0000008A BB007C mov bx,0x7c00
; DL 驅動器號(00H-7FH:表示可移動磁盤,80H-0FFH:表示固定磁盤)
0000008D 8A5600 mov dl,[bp+0x0]
; DH 磁頭
00000090 8A7601 mov dh,[bp+0x1]
; CL 扇區(其中低 6 位是扇區,高 2 位是柱面的高 2 位)
00000093 8A4E02 mov cl,[bp+0x2]
; CH 柱面(柱面的低 8 位,加上前面的高 2 位,共 10 位)
00000096 8A6E03 mov ch,[bp+0x3]
; 開始讀取
00000099 CD13 int 0x13
; ==============================
; 讀取完畢,恢復 32 位寄存器(恢復現場)
; 與入棧順序 EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI 相反
0000009B 6661 popad
; 無論是普通 int 13h 還是擴展 int 13h,成功則 CF = 0,失敗則 CF = 1
; 如果讀取成功,則跳轉到 0xbb,檢查讀取的扇區是否有啓動標記
0000009D 731C jnc 0xbb
; 如果讀取失敗,則嘗試次數 -1
0000009F FE4E11 dec byte [bp+0x11]
; 若嘗試次數未達到 5 次,則跳轉到 0xb0 處復位磁盤,再次嘗試讀取
000000A2 750C jnz 0xb0
; ==============================
; 若 5 次嘗試都失敗,此時 [bp] 中存儲的是 int 19h 給出的啓動設備號。
; 如果啓動設備是 0x80 則跳轉到 0x136,打印錯誤信息。
000000A4 807E0080 cmp byte [bp+0x0],0x80
000000A8 0F848A00 jz near 0x136
; 如果啓動設備不是 0x80(而是 0x81-0xFF 之間的設備)
; 則嘗試從 0x80 讀取數據,而不是從 int 19h 給出的啓動設備中讀取數據。
000000AC B280 mov dl,0x80
000000AE EB84 jmp short 0x34
; ==============================
; 復位磁盤驅動器,再次嘗試讀取
; 保存 bp(保護現場),因爲 int 13h 可能會改寫 bp
000000B0 55 push bp
; int 13h 的第 0 號功能:復位磁盤系統
000000B1 32E4 xor ah,ah
; int 13h 參數:DL = 驅動器號
000000B3 8A5600 mov dl,[bp+0x0]
; 開始復位磁盤
000000B6 CD13 int 0x13
; 恢復 bp(恢復現場)
000000B8 5D pop bp
; 再次嘗試讀取扇區
000000B9 EB9E jmp short 0x59
; ==============================
; 讀取成功
; 檢查讀取的扇區是否含有啓動標記 55 AA
000000BB 813EFE7D55AA cmp word [0x7dfe],0xaa55
; 如果沒有,則跳轉到 0x131,打印出錯信息
000000C1 756E jnz 0x131
; 如果有,則保存啓動設備號([bp+0x0] 在 word 的低 8 位)
; push 指令一次至少壓入 2 個字節,不允許只壓入 1 個字節(也就是 2 字節對齊)
000000C3 FF7600 push word [bp+0x0]
; ==============================
; 打開 A20 地址線(打開後才能訪問 1M 以上的內存)
; 調用函數 0x156,等待鍵盤的“輸入緩衝區”爲空
; 只有當輸入緩衝區爲空時纔可以向其端口寫入命令
000000C6 E88D00 call 0x156
; 如果鍵盤的“輸入緩衝區”始終爲滿,則跳轉到 0xE2
000000C9 7517 jnz 0xe2
; 禁止中斷
000000CB FA cli
; 通知鍵盤,準備向鍵盤的輸出端口寫入數據
; 0xD1 命令碼:表示要寫數據到鍵盤控制器的 P2 端口
; P2 端口的 1 位用於打開 A20 線
000000CC B0D1 mov al,0xd1
000000CE E664 out 0x64,al
; 調用函數 0x156,等待鍵盤的輸入緩衝區爲空
; 緩衝區爲空,表示上面的命令被接受
000000D0 E88300 call 0x156
; 打開 A20 地址線
000000D3 B0DF mov al,0xdf
000000D5 E660 out 0x60,al
; 調用函數 0x156,等待鍵盤的輸入緩衝區爲空
; 緩衝區爲空,表示上面的命令被接受
000000D7 E87C00 call 0x156
; 0xff 不知道什麼作用
000000DA B0FF mov al,0xff
000000DC E664 out 0x64,al
; 調用函數 0x156,等待鍵盤的輸入緩衝區爲空
; 緩衝區爲空,表示上面的命令被接受
000000DE E87500 call 0x156
; 允許中斷
000000E1 FB sti
; ==============================
; int 1ah 的 bbh 號功能:
; 不知道做什麼用的
000000E2 B800BB mov ax,0xbb00
000000E5 CD1A int 0x1a
; 如果 EAX 不爲 0 則跳轉到 0x127,開始執行分區引導記錄
; 不知道做什麼用的
000000E7 6623C0 and eax,eax
000000EA 753B jnz 0x127
; 如果 EBX 不爲 0x41504354 則跳轉到 0x127,開始執行分區引導記錄
; 不知道做什麼用的
000000EC 6681FB54435041 cmp ebx,0x41504354
000000F3 7532 jnz 0x127
; 如果 CX 不爲 0x102 則跳轉到 0x127,開始執行分區引導記錄
; 不知道做什麼用的
000000F5 81F90201 cmp cx,0x102
000000F9 722C jc 0x127
; ==============================
; 填寫 32 位寄存器(EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI)
000000FB 666807BB0000 push dword 0xbb07
00000101 666800020000 push dword 0x200
00000107 666808000000 push dword 0x8
0000010D 6653 push ebx
0000010F 6653 push ebx
00000111 6655 push ebp
00000113 666800000000 push dword 0x0
00000119 6668007C0000 push dword 0x7c00
0000011F 6661 popad
; int 1ah 的 bbh 號功能:
; 不知道做什麼用的
00000121 680000 push word 0x0
00000124 07 pop es
00000125 CD1A int 0x1a
; ==============================
; 恢復設備驅動器號到 DL
00000127 5A pop dx
00000128 32F6 xor dh,dh
; 跳轉到分區引導記錄,繼續引導
0000012A EA007C0000 jmp 0x0:0x7c00
; ==============================
; 沒有發現活動分區,無法啓動 OS,按照規範調用 Int 18h
; 早期的 BIOS 的 Int 18h 中斷服務程序就是啓動 ROM-Basic,
; 現在的 BIOS 一般是打印錯誤信息,然後 hlt。
; ROM BASIC 是一個 ROM 解釋器,允許用戶運行和創建 BASIC 程序。
; 今天的電腦不再包括 BASIC ROM; 但是,使用 IBM 兼容計算機的用戶
; 仍可能會遇到與 ROM BASIC 相關的錯誤消息。
0000012F CD18 int 0x18
; ==============================
; 打印出錯信息(索引號 [0x7b7] = 9A)"Missing operating system"
00000131 A0B707 mov al,[0x7b7]
00000134 EB08 jmp short 0x13e
; 打印出錯信息(索引號 [0x7b6] = 7B)"Error loading operating system"
00000136 A0B607 mov al,[0x7b6]
00000139 EB03 jmp short 0x13e
; 驅動器號在 0x01-0x7F 範圍(索引號 [0x7b5] = 63)"Invalid partition table"
0000013B A0B507 mov al,[0x7b5]
; ==============================
; 打印出錯信息
; 計算字符串起始地址:ax = 索引號
0000013E 32E4 xor ah,ah
; 計算字符串起始地址:ax = 0x700 + 索引號
00000140 050007 add ax,0x700
; DS:SI 指向字符串起始地址
00000143 8BF0 mov si,ax
; 把 DS:SI 指向的存儲單元讀入 AL,然後 SI 自動 +1
; al = [0:0x700]
00000145 AC lodsb
; al = 要顯示的字符(判斷當前字符是否爲 0x00)
00000146 3C00 cmp al,0x0
; 0x00 表示字符串顯示完畢,停止 CPU
00000148 7409 jz 0x153
; bh = 0 表示頁碼,bl = 7 表示背景色
0000014A BB0700 mov bx,0x7
; 功能號 0x0E 表示在 Teletype 模式下顯示字符
0000014D B40E mov ah,0xe
; 顯示一個字符
0000014F CD10 int 0x10
; 繼續顯示下一個字符
00000151 EBF2 jmp short 0x145
; 暫停 CPU,等待指令,死循環
00000153 F4 hlt
00000154 EBFD jmp short 0x153
; ==============================
; 等待鍵盤的“輸入緩衝區”爲空
; 循環次數(65536 次)
00000156 2BC9 sub cx,cx
; 從 64H 端口讀取 1 個字節到 al
; 0x64 端口是“鍵盤控制器”的“讀取狀態寄存器”。下面是它各個位的含義:
; 7 = 1 從鍵盤傳輸時發生奇偶校驗錯誤
; 6 = 1 常規超時
; 5 = 1 鼠標輸出緩衝區已滿
; 4 = 0 鍵盤抑制
; 3 = 1 輸入寄存器中的數據爲命令
; 0 輸入寄存器中的數據爲數據
; 2 = 系統標誌狀態:0=加電或復位,1=自檢正常
; 1 = 輸入緩衝區已滿
; 0 = 輸出緩衝區已滿
00000158 E464 in al,0x64
; 延時,相當於 nop 指令
0000015A EB00 jmp short 0x15c
; 檢查狀態寄存器第 2 位(輸入緩衝區已滿)是否爲 0
; 若爲 0 則 ZF=1,否則 ZF=0
0000015C 2402 and al,0x2
; CX=CX-1,若 CX != 0 且 ZF=0, 則跳轉到 0x158 繼續下一輪循環
0000015E E0F8 loopne 0x158
; 直到緩衝區爲空,或者 CX 爲 0(CX 爲 0 表示循環了 65536 次)
00000160 2402 and al,0x2
00000162 C3 ret
; ==============================
; 常量
00000163 49 db "Invalid partition table", 0
0000017B 45 db "Error loading operating system", 0
0000019A 4D db "Missing operating system", 0
000001B3 00 db 0, 0
000001B5 63 db 0x63 ; 第 1 個字符串的地址
000001B6 7B db 0x7B ; 第 2 個字符串的地址
000001B7 9A db 0x9A ; 第 3 個字符串的地址
000001B8 54 db 0x54
000001B9 36 db 0x36
000001BA 55 db 0x55
000001BB 36 db 0x36
000001BC 36 db 0, 0
; ==============================
; 分區表
000001BE 00 times 16 db 0
000001CE 00 times 16 db 0
000001DE 00 times 16 db 0
000001EE 00 times 16 db 0
; ==============================
; 引導標記
000001FE 55AA db 0x55, 0xAA