下面的彙編代碼是一個程序加載器,類似於一個最簡易的操作系統,它負責加載另一個用戶程序,需要注意的是,這個這個彙編要位於主扇區。
;代碼清單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:
成功運行後,效果如下