彙編語言學習02-裸機上執行8086程序

下面的彙編代碼是一個程序加載器,類似於一個最簡易的操作系統,它負責加載另一個用戶程序,需要注意的是,這個這個彙編要位於主扇區。

         ;代碼清單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						;cmp 需要配合jnz纔有意義
         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                         ;循環讀,直到讀完整個功能程序 loop 需cx配合

         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          ;將低4位旋轉到高4位
         and dx,0xf000     
         or ax,dx         ;ax 拼接 dx中的高4位
         
         pop dx
         
         ret

;-------------------------------------------------------------------------------
         phy_base dd 0x10000             ;用戶程序被加載的物理起始地址
         
 times 510-($-$$) db 0
                  db 0x55,0xaa

下面的彙編代碼就是一個展示信息的作用,它由上面的加載器加載,它必須位於硬盤的100扇區。代碼如下

         ;代碼清單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					;光標寄存器的高8位
         out dx,al						;連接低8位的光標寄存器
         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                      ;光標位置在bx中
         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                            ;df清零
         mov si,0xa0					;160,從屏幕的160處開始
         mov di,0x00
         mov cx,1920
         rep movsw						;依據df的值,決定傳送方向
         mov bx,3840                     ;清除屏幕最底一行
         mov cx,80
 .cls:
         mov word[es:bx],0x0720      ;07黑底白字 20空格
         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:

成功運行後,效果如下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章