[008][x86匯編語言]硬盤與顯卡的訪問與控制 加載程序c08_mbr.asm 用戶程序c08.asm

學習筆記

《x86匯編語言:從實模式到保護模式》
http://www.lxweimin.com/p/d481cb547e9f

編譯與運行程序

在真實機器上編譯源程序 將二進制文件寫入虛擬硬盤
虛擬機開機后 自動讀取主引導扇區的加載程序 將其放入內存 之后根據主引導扇區 去讀取用戶程序 將其放入內存 之后跳轉過去執行 用戶程序
  • 1、編譯源程序 c08_mbr.asm,將編譯后得到的結果 c08_mbr.bin 文件寫到虛擬硬盤 主引導扇區(邏輯0扇區)
  • 2、編譯源程序 c08.asm,將編譯后得到的結果 c08.bin文件寫到虛擬硬盤的 邏輯100扇區
1、編譯加載程序源程序.png
2、將加載程序二進制文件.bin寫入虛擬硬盤.png
3、加載程序二進制文件(即機器碼)寫入LBA邏輯扇區號0(即主引導扇區).png
4、同理,用戶程序.bin 要寫入到 LBA邏輯扇區 100.png
5、運行和編譯程序.png

加載程序 源程序 c08_mbr.asm

         ;代碼清單8-1
         ;文件名:c08_mbr.asm
         ;文件說明:硬盤主引導扇區代碼(加載程序) 
         ;創建日期:2011-5-5 18:17
         
         app_lba_start equ 100           ;聲明常數(用戶程序起始邏輯扇區號)
                                         ;常數的聲明不會占用匯編地址
                                    
SECTION mbr align=16 vstart=0x7c00                                     

         ;設置堆棧段和棧指針 
         mov ax,0      
         mov ss,ax
         mov sp,ax
      
         mov ax,[cs:phy_base]            ;計算用于加載用戶程序的邏輯段地址 
         mov dx,[cs:phy_base+0x02]
         mov bx,16        
         div bx            
         mov ds,ax                       ;令DS和ES指向該段以進行操作
         mov es,ax                        
    
         ;以下讀取程序的起始部分 
         xor di,di
         mov si,app_lba_start            ;程序在硬盤上的起始邏輯扇區號 
         xor bx,bx                       ;加載到DS:0x0000處 
         call read_hard_disk_0
      
         ;以下判斷整個程序有多大
         mov dx,[2]                      ;曾經把dx寫成了ds,花了二十分鐘排錯 
         mov ax,[0]
         mov bx,512                      ;512字節每扇區
         div bx
         cmp dx,0
         jnz @1                          ;未除盡,因此結果比實際扇區數少1 
         dec ax                          ;已經讀了一個扇區,扇區總數減1 
   @1:
         cmp ax,0                        ;考慮實際長度小于等于512個字節的情況 
         jz direct
         
         ;讀取剩余的扇區
         push ds                         ;以下要用到并改變DS寄存器 

         mov cx,ax                       ;循環次數(剩余扇區數)
   @2:
         mov ax,ds
         add ax,0x20                     ;得到下一個以512字節為邊界的段地址
         mov ds,ax  
                              
         xor bx,bx                       ;每次讀時,偏移地址始終為0x0000 
         inc si                          ;下一個邏輯扇區 
         call read_hard_disk_0
         loop @2                         ;循環讀,直到讀完整個功能程序 

         pop ds                          ;恢復數據段基址到用戶程序頭部段 
      
         ;計算入口點代碼段基址 
   direct:
         mov dx,[0x08]
         mov ax,[0x06]
         call calc_segment_base
         mov [0x06],ax                   ;回填修正后的入口點代碼段基址 
      
         ;開始處理段重定位表
         mov cx,[0x0a]                   ;需要重定位的項目數量
         mov bx,0x0c                     ;重定位表首地址
          
 realloc:
         mov dx,[bx+0x02]                ;32位地址的高16位 
         mov ax,[bx]
         call calc_segment_base
         mov [bx],ax                     ;回填段的基址
         add bx,4                        ;下一個重定位項(每項占4個字節) 
         loop realloc 
      
         jmp far [0x04]                  ;轉移到用戶程序  
 
;-------------------------------------------------------------------------------
read_hard_disk_0:                        ;從硬盤讀取一個邏輯扇區
                                         ;輸入:DI:SI=起始邏輯扇區號
                                         ;      DS:BX=目標緩沖區地址
         push ax
         push bx
         push cx
         push dx
      
         mov dx,0x1f2
         mov al,1
         out dx,al                       ;讀取的扇區數

         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                     ;LBA28模式,主盤
         or al,ah                        ;LBA地址27~24
         out dx,al

         inc dx                          ;0x1f7
         mov al,0x20                     ;讀命令
         out dx,al

  .waits:
         in al,dx
         and al,0x88
         cmp al,0x08
         jnz .waits                      ;不忙,且硬盤已準備好數據傳輸 

         mov cx,256                      ;總共要讀取的字數
         mov dx,0x1f0
  .readw:
         in ax,dx
         mov [bx],ax
         add bx,2
         loop .readw

         pop dx
         pop cx
         pop bx
         pop ax
      
         ret

;-------------------------------------------------------------------------------
calc_segment_base:                       ;計算16位段地址
                                         ;輸入:DX:AX=32位物理地址
                                         ;返回:AX=16位段基地址 
         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

用戶程序 編譯源程序 c08.asm

         ;代碼清單8-2
         ;文件名:c08.asm
         ;文件說明:用戶程序 
         ;創建日期:2011-5-5 18:17
         
;===============================================================================
SECTION header vstart=0                     ;定義用戶程序頭部段 
    program_length  dd program_end          ;程序總長度[0x00]
    
    ;用戶程序入口點
    code_entry      dw start                ;偏移地址[0x04]
                    dd section.code_1.start ;段地址[0x06] 
    
    realloc_tbl_len dw (header_end-code_1_segment)/4
                                            ;段重定位表項個數[0x0a]
    
    ;段重定位表           
    code_1_segment  dd section.code_1.start ;[0x0c]
    code_2_segment  dd section.code_2.start ;[0x10]
    data_1_segment  dd section.data_1.start ;[0x14]
    data_2_segment  dd section.data_2.start ;[0x18]
    stack_segment   dd section.stack.start  ;[0x1c]
    
    header_end:                
    
;===============================================================================
SECTION code_1 align=16 vstart=0         ;定義代碼段1(16字節對齊) 
put_string:                              ;顯示串(0結尾)。
                                         ;輸入:DS:BX=串地址
         mov cl,[bx]
         or cl,cl                        ;cl=0 ?
         jz .exit                        ;是的,返回主程序 
         call put_char
         inc bx                          ;下一個字符 
         jmp put_string

   .exit:
         ret

;-------------------------------------------------------------------------------
put_char:                                ;顯示一個字符
                                         ;輸入:cl=字符ascii
         push ax
         push bx
         push cx
         push dx
         push ds
         push es

         ;以下取當前光標位置
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         mov dx,0x3d5
         in al,dx                        ;高8位 
         mov ah,al

         mov dx,0x3d4
         mov al,0x0f
         out dx,al
         mov dx,0x3d5
         in al,dx                        ;低8位 
         mov bx,ax                       ;BX=代表光標位置的16位數

         cmp cl,0x0d                     ;回車符?
         jnz .put_0a                     ;不是。看看是不是換行等字符 
         mov ax,bx                       ;此句略顯多余,但去掉后還得改書,麻煩 
         mov bl,80                       
         div bl
         mul bl
         mov bx,ax
         jmp .set_cursor

 .put_0a:
         cmp cl,0x0a                     ;換行符?
         jnz .put_other                  ;不是,那就正常顯示字符 
         add bx,80
         jmp .roll_screen

 .put_other:                             ;正常顯示字符
         mov ax,0xb800
         mov es,ax
         shl bx,1
         mov [es:bx],cl

         ;以下將光標位置推進一個字符
         shr bx,1
         add bx,1

 .roll_screen:
         cmp bx,2000                     ;光標超出屏幕?滾屏
         jl .set_cursor

         mov ax,0xb800
         mov ds,ax
         mov es,ax
         cld
         mov si,0xa0
         mov di,0x00
         mov cx,1920
         rep movsw
         mov bx,3840                     ;清除屏幕最底一行
         mov cx,80
 .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls

         mov bx,1920

 .set_cursor:
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         mov dx,0x3d5
         mov al,bh
         out dx,al
         mov dx,0x3d4
         mov al,0x0f
         out dx,al
         mov dx,0x3d5
         mov al,bl
         out dx,al

         pop es
         pop ds
         pop dx
         pop cx
         pop bx
         pop ax

         ret

;-------------------------------------------------------------------------------
  start:
         ;初始執行時,DS和ES指向用戶程序頭部段
         mov ax,[stack_segment]           ;設置到用戶程序自己的堆棧 
         mov ss,ax
         mov sp,stack_end
         
         mov ax,[data_1_segment]          ;設置到用戶程序自己的數據段
         mov ds,ax

         mov bx,msg0
         call put_string                  ;顯示第一段信息 

         push word [es:code_2_segment]
         mov ax,begin
         push ax                          ;可以直接push begin,80386+
         
         retf                             ;轉移到代碼段2執行 
         
  continue:
         mov ax,[es:data_2_segment]       ;段寄存器DS切換到數據段2 
         mov ds,ax
         
         mov bx,msg1
         call put_string                  ;顯示第二段信息 

         jmp $ 

;===============================================================================
SECTION code_2 align=16 vstart=0          ;定義代碼段2(16字節對齊)

  begin:
         push word [es:code_1_segment]
         mov ax,continue
         push ax                          ;可以直接push continue,80386+
         
         retf                             ;轉移到代碼段1接著執行 
         
;===============================================================================
SECTION data_1 align=16 vstart=0

    msg0 db '  This is NASM - the famous Netwide Assembler. '
         db 'Back at SourceForge and in intensive development! '
         db 'Get the current versions from http://www.nasm.us/.'
         db 0x0d,0x0a,0x0d,0x0a
         db '  Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
         db '     xor dx,dx',0x0d,0x0a
         db '     xor ax,ax',0x0d,0x0a
         db '     xor cx,cx',0x0d,0x0a
         db '  @@:',0x0d,0x0a
         db '     inc cx',0x0d,0x0a
         db '     add ax,cx',0x0d,0x0a
         db '     adc dx,0',0x0d,0x0a
         db '     inc cx',0x0d,0x0a
         db '     cmp cx,1000',0x0d,0x0a
         db '     jle @@',0x0d,0x0a
         db '     ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0a
         db 0

;===============================================================================
SECTION data_2 align=16 vstart=0

    msg1 db '  The above contents is written by LeeChung. '
         db '2011-05-06'
         db 0

;===============================================================================
SECTION stack align=16 vstart=0
           
         resb 256

stack_end:  

;===============================================================================
SECTION trail align=16
program_end:

硬盤尋址 與 內存尋址

  • 為什么“用戶程序”源地址是用扇區號表示?而用戶程序將被加載到的目的地址卻是一個物理地址
用戶程序最初的形態是,是以 c08.asm 的源碼文件形式放在我們真實機器硬盤上的某個位置,
比如我的就是
E:\NASM\c08.asm,

使用配書工具nasmide.exe,可以生成一個對應的二進制文件 c08.bin,
這個.bin文件就是用戶程序的機器碼,
此時完整路徑是E:\NASM\c08.bin,

對于這個.bin二進制文件,
使用配書工具 fixvhdwr.exe 將其寫到 虛擬硬盤的LBA模式邏輯扇區100 那里,
直到這一步,才算是真正地把 【用戶程序的機器碼】
放到了 代表源地址的 虛擬硬盤上指定扇區-100 處;

而指令要執行,就必須將【機器碼】放到內存,
內存的尋址方式需要確定唯一的物理地址,
編程的時候使用 邏輯的段地址:偏移地址 來映射一個 真實的物理地址,
同樣的,知道 真實的物理地址,可以轉換成 段地址:偏移地址 形式,
比如,如果我們需要的 【物理地址是 0x10000】,
那么,是可以用 【段地址:偏移地址 = 0x1000:0000】來進行映射的,
因此,一般所稱的 段地址 其實是用來映射真實物理地址的那個0x1000;

訪問硬盤,用LAB模式的邏輯扇區號;
訪問內存,用物理地址(段地址:偏移地址);

要清醒地意識到,寫到虛擬硬盤邏輯扇區100處的已經是【用戶程序的機器碼,一個二進制文件.bin了】,
而之前的編譯工作是在我們真實的機器上完成的,
總的來說,不是把.asm文件寫到虛擬硬盤的邏輯扇區100,而是把生成的二進制.bin文件寫到那里。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。