上一篇博文我們用了很大的篇幅說了加載器,這一篇我們該說說用戶程序了。
先看作者的源碼吧。
~~~
;代碼清單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 ; space
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:
~~~
接下來我們分塊分析。
~~~
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:
~~~
這段代碼用來定義用戶程序的頭部。頭部格式在上一篇博文已經說過了。
因為標號program_end所在的段沒有指定vstart==XX,所以program_end的匯編地址就從程序開頭(=0)開始計算,它所代表的匯編地址就是整個程序的大小(以字節計算)。
每個段都有一個匯編地址,它是相對于整個程序開頭(0)的,為了方便取得某個段的匯編地址,NASM編譯器提供了如下表達式:
section.段名稱.start
如圖所示:

因為程序的入口點在code_1段中,所以是
~~~
dw start ;偏移地址[0x04]
dd section.code_1.start ;段地址[0x06]
~~~
其他語句源碼中都有注釋,很好理解。
~~~
put_char: ;顯示一個字符
;輸入:cl=字符ascii,ch=字符屬性
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
mov [es:bx+1],ch ;這句是我自己加的,我想讓字符屬性通過ch傳遞
;以下將光標位置推進一個字符
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 ; space
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
~~~
以上這段代碼是為了在光標位置處顯示一個字符,并推進光標到下一個字符,還考慮到了滾屏。這段代碼書中有詳細的講解,這里就不贅述了。
唯一需要說明的是,我希望可以顯示不同顏色的字,所以在里面加了一句 mov [es:bx+1],ch; ch是字符屬性
~~~
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,看來過程是可以嵌套的!
or cl,cl 這句指令不會影響到cl里面的值,但計算結果會影響標志寄存器的某些位。如果ZF置位,說明cl的內容為0,也就是串結束標志。
~~~
我們從程序入口處看,要強調的是,當加載器把執行權交給用戶程序的時候,DS和ES都指向用戶程序的頭部段,也就是指向用戶程序的最開始。
start:
;初始執行時,DS和ES指向用戶程序頭部段
mov ax,[stack_segment] ;設置到用戶程序自己的堆棧
mov ss,ax
mov sp,stack_end
~~~
mov ax,[stack_segment] ;這句指令是到段的重定位表中取修正后的SS段的段基址。
~~~
mov ax,[data_1_segment] ;設置到用戶程序自己的數據段
mov ds,ax
mov bx,msg0
call put_string ;顯示第一段信息
~~~
從重定位表中獲得重定位之后data_1段的基址,賦值給ds,這樣之后,ds就不再指向用戶程序的頭部,而是指向data_1段。
~~~
push word [es:code_2_segment]
mov ax,begin
push ax ;可以直接push begin,80386+
retf ;轉移到代碼段2執行
~~~
這段代碼用段超越前綴 es:code_2_segment 訪問重定位表,把code_2段的基地址壓棧。(因為此時ds已經不再指向用戶頭部了,但是es還是指向用戶頭部);然后再把code_2段內的一個標號begin(代表偏移地址)也壓棧。
cpu執行retf指令時,相當于執行
pop ip
pop cs
這樣,執行retf的時候,程序就相當于轉移了,轉移到代碼段2執行。
~~~
;======================================================
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接著執行
~~~
代碼段2其實什么也沒有干,干的事情就是轉移到代碼段1的continue處,原理和上面一樣。
于是開始執行:
~~~
continue:
mov ax,[es:data_2_segment] ;段寄存器DS切換到數據段2
mov ds,ax
mov bx,msg1
call put_string ;顯示第二段信息
jmp $
~~~
這段代碼就是調用過程,顯示信息。
好了,下面我們可以把代碼修改一下,顯示自己想要的東西。
比如在顯示字符串前,給ch賦值,0X02表示綠色,0X04表示紅色。

我們也可以自定義要顯示的字符。
我修改后的用戶代碼如下:
~~~
;代碼清單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=串地址
;ch:屬性
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
mov [es:bx+1],ch
;以下將光標位置推進一個字符
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 ; space
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
mov ch,0x02 ;green
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
mov ch,0x04 ;red
call put_string ;顯示第二段信息
;這里我們顯示出多彩的Hello
mov cx,128 ;循環次數
mov ah,0
@1:
push cx
mov bx,msg2
mov ch,ah
call put_string ;顯示Hello
pop cx
inc ah ;屬性值增加1
loop @1
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 0x0d,0x0a,0x0d,0x0a
db 0
msg2 db 'Hello'
db 0
;===============================================================================
SECTION stack align=16 vstart=0
times 256 db 0
stack_end:
;===============================================================================
SECTION trail align=16
program_end:
~~~
OK,看一下結果吧,這就是多彩的Hello

【the end 】