上一篇博文我們用了很大的篇幅說了加載器,這一篇我們?cè)撜f說用戶程序了。 先看作者的源碼吧。
;代碼清單
8 -
2 ;文件名:c08.asm;文件說明:用戶程序 ;創(chuàng)建日期:
2011 -
5 -
5 18 :
17 ;===============================================================================
SECTION header vstart=
0 ;定義用戶程序頭部段 program_length dd program_end ;程序總長度[
0x00 ];用戶程序入口點(diǎn)code_entry dw start ;偏移地址[
0x04 ]dd section.code_1.start ;段地址[
0x06 ] realloc_tbl_len dw (header_end-code_1_segment)/
4 ;段重定位表項(xiàng)個(gè)數(shù)[
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 字節(jié)對(duì)齊)
put_string: ;顯示串(
0 結(jié)尾)。;輸入:DS:BX=串地址mov cl,[bx]
or cl,cl ;cl=
0 ?jz .
exit ;是的,返回主程序 call put_charinc bx ;下一個(gè)字符 jmp put_string.
exit :ret;-------------------------------------------------------------------------------
put_char: ;顯示一個(gè)字符;輸入:cl=字符ascii
push ax
push bx
push cx
push dx
push ds
push es;以下取當(dāng)前光標(biāo)位置mov dx,
0x3d4 mov al,
0x0e out dx,almov dx,
0x3d5 in al,dx ;高
8 位 mov ah,almov dx,
0x3d4 mov al,
0x0f out dx,almov dx,
0x3d5 in al,dx ;低
8 位 mov bx,ax ;BX=代表光標(biāo)位置的
16 位數(shù)cmp cl,
0x0d ;回車符?jnz .put_0a ;不是。看看是不是換行等字符 mov ax,bx ;此句略顯多余,但去掉后還得改書,麻煩 mov bl,
80 div blmul blmov bx,axjmp .set_cursor.put_0a:cmp cl,
0x0a ;換行符?jnz .put_other ;不是,那就正常顯示字符 add bx,
80 jmp .roll_screen.put_other: ;正常顯示字符mov ax,
0xb800 mov es,axshl bx,
1 mov [es:bx],cl;以下將光標(biāo)位置推進(jìn)一個(gè)字符shr bx,
1 add bx,
1 .roll_screen:cmp bx,
2000 ;光標(biāo)超出屏幕?滾屏jl .set_cursormov ax,
0xb800 mov ds,axmov es,axcldmov si,
0xa0 mov di,
0x00 mov cx,
1920 rep movswmov bx,
3840 ;清除屏幕最底一行mov cx,
80 .cls:mov word[es:bx],
0x0720 ; spaceadd bx,
2 loop .clsmov bx,
1920 .set_cursor:mov dx,
0x3d4 mov al,
0x0e out dx,almov dx,
0x3d5 mov al,bhout dx,almov dx,
0x3d4 mov al,
0x0f out dx,almov dx,
0x3d5 mov al,blout dx,al
pop es
pop ds
pop dx
pop cx
pop bx
pop axret;---------------------------------- 用戶程序入口 --------------------------------------------start:;初始執(zhí)行時(shí),DS和ES指向用戶程序頭部段mov ax,[stack_segment] ;設(shè)置到用戶程序自己的堆棧 mov ss,axmov sp,stack_endmov ax,[data_1_segment] ;設(shè)置到用戶程序自己的數(shù)據(jù)段mov ds,axmov bx,msg
0 call put_string ;顯示第一段信息
push word [es:code_2_segment]mov ax,begin
push ax ;可以直接
push begin,
80386 +retf ;轉(zhuǎn)移到代碼段
2 執(zhí)行
continue :mov ax,[es:data_2_segment] ;段寄存器DS切換到數(shù)據(jù)段
2 mov ds,axmov bx,msg1call put_string ;顯示第二段信息 jmp $ ;===============================================================================
SECTION code_2 align=
16 vstart=
0 ;定義代碼段
2 (
16 字節(jié)對(duì)齊)begin:
push word [es:code_1_segment]mov ax,
continue push ax ;可以直接
push continue ,
80386 +retf ;轉(zhuǎn)移到代碼段
1 接著執(zhí)行 ;===============================================================================
SECTION data_1 align=
16 vstart=
0 msg
0 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];用戶程序入口點(diǎn)code_entry dw
start ;偏移地址[0x04]dd section.code_1.
start ;段地址[0x06] realloc_tbl_len dw (header_end-code_1_segment)/4;段重定位表項(xiàng)個(gè)數(shù)[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:
這段代碼用來定義用戶程序的頭部。頭部格式在上一篇博文已經(jīng)說過了。 因?yàn)闃?biāo)號(hào)program_end所在的段沒有指定vstart==XX,所以program_end的匯編地址就從程序開頭(=0)開始計(jì)算,它所代表的匯編地址就是整個(gè)程序的大小(以字節(jié)計(jì)算)。 每個(gè)段都有一個(gè)匯編地址,它是相對(duì)于整個(gè)程序開頭(0)的,為了方便取得某個(gè)段的匯編地址,NASM編譯器提供了如下表達(dá)式: section.段名稱.start
如圖所示:
因?yàn)槌绦虻娜肟邳c(diǎn)在code_1段中,所以是
dw
start ;偏移地址[0x04]dd section.code_1.
start ;段地址[0x06]
其他語句源碼中都有注釋,很好理解。
put_char: 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
mov ah,al
mov dx,
0x3d4 mov al,
0x0f out dx,al
mov dx,
0x3d5 in al,dx
mov bx,ax 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,axshl 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,axcld
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
以上這段代碼是為了在光標(biāo)位置處顯示一個(gè)字符,并推進(jìn)光標(biāo)到下一個(gè)字符,還考慮到了滾屏。這段代碼書中有詳細(xì)的講解,這里就不贅述了。 唯一需要說明的是,我希望可以顯示不同顏色的字,所以在里面加了一句 mov [es:bx+1],ch; ch是字符屬性
put_string: mov cl,[bx]
or cl,cl jz
.exit call put_char
inc bx
jmp put_string
.exit :
ret
這段代碼又是一個(gè)過程,里面調(diào)用了put_char,看來過程是可以嵌套的! or cl,cl 這句指令不會(huì)影響到cl里面的值,但計(jì)算結(jié)果會(huì)影響標(biāo)志寄存器的某些位。如果ZF置位,說明cl的內(nèi)容為0,也就是串結(jié)束標(biāo)志。
我們從程序入口處看,要強(qiáng)調(diào)的是,當(dāng)加載器把執(zhí)行權(quán)交給用戶程序的時(shí)候,DS和ES都指向用戶程序的頭部段,也就是指向用戶程序的最開始。start:
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 ;轉(zhuǎn)移到代碼段2執(zhí)行
這段代碼用段超越前綴 es:code_2_segment 訪問重定位表,把code_2段的基地址壓棧。(因?yàn)榇藭r(shí)ds已經(jīng)不再指向用戶頭部了,但是es還是指向用戶頭部);然后再把code_2段內(nèi)的一個(gè)標(biāo)號(hào)begin(代表偏移地址)也壓棧。 cpu執(zhí)行retf指令時(shí),相當(dāng)于執(zhí)行 pop ip pop cs 這樣,執(zhí)行retf的時(shí)候,程序就相當(dāng)于轉(zhuǎn)移了,轉(zhuǎn)移到代碼段2執(zhí)行。
;======================================================
SECTION code_2 align=
16 vstart=
0 ;定義代碼段
2 (
16 字節(jié)對(duì)齊)begin:
push word [es:code_1_segment]mov ax,
continue push ax ;可以直接
push continue ,
80386 +retf ;轉(zhuǎn)移到代碼段
1 接著執(zhí)行
代碼段2其實(shí)什么也沒有干,干的事情就是轉(zhuǎn)移到代碼段1的continue處,原理和上面一樣。 于是開始執(zhí)行:
continue: mov ax,[es:data_2_segment]
mov ds,ax
mov bx,msg1
call put_string
jmp $
這段代碼就是調(diào)用過程,顯示信息。
好了,下面我們可以把代碼修改一下,顯示自己想要的東西。 比如在顯示字符串前,給ch賦值,0X02表示綠色,0X04表示紅色。
我們也可以自定義要顯示的字符。 我修改后的用戶代碼如下:
;代碼清單
8 -
2 ;文件名:c08.asm;文件說明:用戶程序 ;創(chuàng)建日期:
2011 -
5 -
5 18 :
17 ;===============================================================================
SECTION header vstart=
0 ;定義用戶程序頭部段 program_length dd program_end ;程序總長度[
0x00 ];用戶程序入口點(diǎn)code_entry dw start ;偏移地址[
0x04 ]dd section.code_1.start ;段地址[
0x06 ] realloc_tbl_len dw (header_end-code_1_segment)/
4 ;段重定位表項(xiàng)個(gè)數(shù)[
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 字節(jié)對(duì)齊)
put_string: ;顯示串(
0 結(jié)尾)。;輸入:DS:BX=串地址;ch:屬性mov cl,[bx]
or cl,cl ;cl=
0 ?jz .
exit ;是的,返回主程序 call put_charinc bx ;下一個(gè)字符 jmp put_string.
exit :ret;-------------------------------------------------------------------------------
put_char: ;顯示一個(gè)字符;輸入:cl=字符ascii
push ax
push bx
push cx
push dx
push ds
push es;以下取當(dāng)前光標(biāo)位置mov dx,
0x3d4 mov al,
0x0e out dx,almov dx,
0x3d5 in al,dx ;高
8 位 mov ah,almov dx,
0x3d4 mov al,
0x0f out dx,almov dx,
0x3d5 in al,dx ;低
8 位 mov bx,ax ;BX=代表光標(biāo)位置的
16 位數(shù)cmp cl,
0x0d ;回車符?jnz .put_0a ;不是。看看是不是換行等字符 mov ax,bx ;此句略顯多余,但去掉后還得改書,麻煩 mov bl,
80 div blmul blmov bx,axjmp .set_cursor.put_0a:cmp cl,
0x0a ;換行符?jnz .put_other ;不是,那就正常顯示字符 add bx,
80 jmp .roll_screen.put_other: ;正常顯示字符mov ax,
0xb800 mov es,axshl bx,
1 mov [es:bx],clmov [es:bx+
1 ],ch;以下將光標(biāo)位置推進(jìn)一個(gè)字符shr bx,
1 add bx,
1 .roll_screen:cmp bx,
2000 ;光標(biāo)超出屏幕?滾屏jl .set_cursormov ax,
0xb800 mov ds,axmov es,axcldmov si,
0xa0 mov di,
0x00 mov cx,
1920 rep movswmov bx,
3840 ;清除屏幕最底一行mov cx,
80 .cls:mov word[es:bx],
0x0720 ; spaceadd bx,
2 loop .clsmov bx,
1920 .set_cursor:mov dx,
0x3d4 mov al,
0x0e out dx,almov dx,
0x3d5 mov al,bhout dx,almov dx,
0x3d4 mov al,
0x0f out dx,almov dx,
0x3d5 mov al,blout dx,al
pop es
pop ds
pop dx
pop cx
pop bx
pop axret;---------------------------------- 用戶程序入口 --------------------------------------------start:;初始執(zhí)行時(shí),DS和ES指向用戶程序頭部段mov ax,[stack_segment] ;設(shè)置到用戶程序自己的堆棧 mov ss,axmov sp,stack_endmov ax,[data_1_segment] ;設(shè)置到用戶程序自己的數(shù)據(jù)段mov ds,axmov bx,msg
0 mov ch,
0x02 ;greencall put_string ;顯示第一段信息
push word [es:code_2_segment]mov ax,begin
push ax ;可以直接
push begin,
80386 +retf ;轉(zhuǎn)移到代碼段
2 執(zhí)行
continue :mov ax,[es:data_2_segment] ;段寄存器DS切換到數(shù)據(jù)段
2 mov ds,axmov bx,msg1mov ch,
0x04 ;redcall put_string ;顯示第二段信息
;這里我們顯示出多彩的Hellomov cx,
128 ;循環(huán)次數(shù)mov ah,
0
@1 :
push cxmov bx,msg2mov ch,ah call put_string ;顯示Hello
pop cxinc ah ;屬性值增加
1 loop
@1 jmp $ ;===============================================================================
SECTION code_2 align=
16 vstart=
0 ;定義代碼段
2 (
16 字節(jié)對(duì)齊)begin:
push word [es:code_1_segment]mov ax,
continue push ax ;可以直接
push continue ,
80386 +retf ;轉(zhuǎn)移到代碼段
1 接著執(zhí)行 ;===============================================================================
SECTION data_1 align=
16 vstart=
0 msg
0 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,看一下結(jié)果吧,這就是多彩的Hello
【the end 】
總結(jié)
以上是生活随笔 為你收集整理的硬盘和显卡的访问与控制(三)(含多彩的Hello)——《x86汇编语言:从实模式到保护模式》读书笔记03 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。