天天看点

硬盘和显卡的访问与控制(三)(含多彩的Hello)——《x86汇编语言:从实模式到保护模式》读书笔记03

上一篇博文我们用了很大的篇幅说了加载器,这一篇我们该说说用户程序了。

先看作者的源码吧。

;代码清单-
         ;文件名:c08.asm
         ;文件说明:用户程序 
         ;创建日期:-- :

;===============================================================================
SECTION header vstart=                     ;定义用户程序头部段 
    program_length  dd program_end          ;程序总长度[]

    ;用户程序入口点
    code_entry      dw start                ;偏移地址[]
                    dd section.code_1.start ;段地址[] 

    realloc_tbl_len dw (header_end-code_1_segment)/
                                            ;段重定位表项个数[]

    ;段重定位表           
    code_1_segment  dd section.code_1.start ;[]
    code_2_segment  dd section.code_2.start ;[]
    data_1_segment  dd section.data_1.start ;[]
    data_2_segment  dd section.data_2.start ;[]
    stack_segment   dd section.stack.start  ;[]

    header_end:                

;===============================================================================
SECTION code_1 align= vstart=         ;定义代码段(字节对齐) 
put_string:                              ;显示串(结尾)。
                                         ;输入:DS:BX=串地址
         mov cl,[bx]
         or cl,cl                        ;cl= ?
         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,
         mov al,
         out dx,al
         mov dx,
         in al,dx                        ;高位 
         mov ah,al

         mov dx,
         mov al,
         out dx,al
         mov dx,
         in al,dx                        ;低位 
         mov bx,ax                       ;BX=代表光标位置的位数

         cmp cl,                     ;回车符?
         jnz .put_0a                     ;不是。看看是不是换行等字符 
         mov ax,bx                       ;此句略显多余,但去掉后还得改书,麻烦 
         mov bl,                       
         div bl
         mul bl
         mov bx,ax
         jmp .set_cursor

 .put_0a:
         cmp cl,                     ;换行符?
         jnz .put_other                  ;不是,那就正常显示字符 
         add bx,
         jmp .roll_screen

 .put_other:                             ;正常显示字符
         mov ax,
         mov es,ax
         shl bx,
         mov [es:bx],cl

         ;以下将光标位置推进一个字符
         shr bx,
         add bx,

 .roll_screen:
         cmp bx,                     ;光标超出屏幕?滚屏
         jl .set_cursor

         mov ax,
         mov ds,ax
         mov es,ax
         cld
         mov si,
         mov di,
         mov cx,
         rep movsw
         mov bx,                     ;清除屏幕最底一行
         mov cx,
 .cls:
         mov word[es:bx],          ; space
         add bx,
         loop .cls

         mov bx,

 .set_cursor:
         mov dx,
         mov al,
         out dx,al
         mov dx,
         mov al,bh
         out dx,al
         mov dx,
         mov al,
         out dx,al
         mov dx,
         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,msg
         call put_string                  ;显示第一段信息 

         push word [es:code_2_segment]
         mov ax,begin
         push ax                          ;可以直接push begin,+

         retf                             ;转移到代码段执行 

  continue:
         mov ax,[es:data_2_segment]       ;段寄存器DS切换到数据段 
         mov ds,ax

         mov bx,msg1
         call put_string                  ;显示第二段信息 

         jmp $ 

;===============================================================================
SECTION code_2 align= vstart=          ;定义代码段(字节对齐)

  begin:
         push word [es:code_1_segment]
         mov ax,continue
         push ax                          ;可以直接push continue,+

         retf                             ;转移到代码段接着执行 

;===============================================================================
SECTION data_1 align= vstart=

    msg 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 ,,,
         db '  Example code for calculate 1+2+...+1000:',,,,
         db '     xor dx,dx',,
         db '     xor ax,ax',,
         db '     xor cx,cx',,
         db '  @@:',,
         db '     inc cx',,
         db '     add ax,cx',,
         db '     adc dx,0',,
         db '     inc cx',,
         db '     cmp cx,1000',,
         db '     jle @@',,
         db '     ... ...(Some other codes)',,,,
         db 

;===============================================================================
SECTION data_2 align= vstart=

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

;===============================================================================
SECTION stack align= vstart=

         resb 

stack_end:  

;===============================================================================
SECTION trail align=

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

如图所示:

硬盘和显卡的访问与控制(三)(含多彩的Hello)——《x86汇编语言:从实模式到保护模式》读书笔记03

因为程序的入口点在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,
         mov al,
         out dx,al
         mov dx,
         in al,dx                        ;高8位 
         mov ah,al

         mov dx,
         mov al,
         out dx,al
         mov dx,
         in al,dx                        ;低8位 
         mov bx,ax                       ;BX=代表光标位置的16位数

         cmp cl,                     ;回车符?
         jnz .put_0a                     ;不是。看看是不是换行等字符 
         mov ax,bx                       ;此句略显多余,但去掉后还得改书,麻烦 
         mov bl,                       
         div bl
         mul bl
         mov bx,ax
         jmp .set_cursor

 .put_0a:
         cmp cl,                     ;换行符?
         jnz .put_other                  ;不是,那就正常显示字符 
         add bx,
         jmp .roll_screen

 .put_other:                             ;正常显示字符
         mov ax,
         mov es,ax
         shl bx,
         mov [es:bx],cl
         mov [es:bx+],ch ;这句是我自己加的,我想让字符属性通过ch传递 

         ;以下将光标位置推进一个字符
         shr bx,
         add bx,

 .roll_screen:
         cmp bx,                     ;光标超出屏幕?滚屏
         jl .set_cursor

         mov ax,
         mov ds,ax
         mov es,ax
         cld
         mov si,
         mov di,
         mov cx,
         rep movsw
         mov bx,                     ;清除屏幕最底一行
         mov cx,
 .cls:
         mov word[es:bx],          ; space
         add bx,
         loop .cls

         mov bx,

 .set_cursor:
         mov dx,
         mov al,
         out dx,al
         mov dx,
         mov al,bh
         out dx,al
         mov dx,
         mov al,
         out dx,al
         mov dx,
         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,+

         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= vstart=          ;定义代码段(字节对齐)

  begin:
         push word [es:code_1_segment]
         mov ax,continue
         push ax                          ;可以直接push continue,+

         retf                             ;转移到代码段接着执行 
           

代码段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表示红色。

硬盘和显卡的访问与控制(三)(含多彩的Hello)——《x86汇编语言:从实模式到保护模式》读书笔记03

我们也可以自定义要显示的字符。

我修改后的用户代码如下:

;代码清单-
         ;文件名:c08.asm
         ;文件说明:用户程序 
         ;创建日期:-- :

;===============================================================================
SECTION header vstart=                     ;定义用户程序头部段 
    program_length  dd program_end          ;程序总长度[]

    ;用户程序入口点
    code_entry      dw start                ;偏移地址[]
                    dd section.code_1.start ;段地址[] 

    realloc_tbl_len dw (header_end-code_1_segment)/
                                            ;段重定位表项个数[]

    ;段重定位表           
    code_1_segment  dd section.code_1.start ;[]
    code_2_segment  dd section.code_2.start ;[]
    data_1_segment  dd section.data_1.start ;[]
    data_2_segment  dd section.data_2.start ;[]
    stack_segment   dd section.stack.start  ;[]

    header_end:                

;===============================================================================
SECTION code_1 align= vstart=         ;定义代码段(字节对齐) 
put_string:                              ;显示串(结尾)。
                                         ;输入:DS:BX=串地址
                                         ;ch:属性
         mov cl,[bx]                     
         or cl,cl                        ;cl= ?
         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,
         mov al,
         out dx,al
         mov dx,
         in al,dx                        ;高位 
         mov ah,al

         mov dx,
         mov al,
         out dx,al
         mov dx,
         in al,dx                        ;低位 
         mov bx,ax                       ;BX=代表光标位置的位数

         cmp cl,                     ;回车符?
         jnz .put_0a                     ;不是。看看是不是换行等字符 
         mov ax,bx                       ;此句略显多余,但去掉后还得改书,麻烦 
         mov bl,                       
         div bl
         mul bl
         mov bx,ax
         jmp .set_cursor

 .put_0a:
         cmp cl,                     ;换行符?
         jnz .put_other                  ;不是,那就正常显示字符 
         add bx,
         jmp .roll_screen

 .put_other:                             ;正常显示字符
         mov ax,
         mov es,ax
         shl bx,
         mov [es:bx],cl
         mov [es:bx+],ch

         ;以下将光标位置推进一个字符
         shr bx,
         add bx,

 .roll_screen:
         cmp bx,                     ;光标超出屏幕?滚屏
         jl .set_cursor

         mov ax,
         mov ds,ax
         mov es,ax
         cld
         mov si,
         mov di,
         mov cx,
         rep movsw
         mov bx,                     ;清除屏幕最底一行
         mov cx,
 .cls:
         mov word[es:bx],          ; space
         add bx,
         loop .cls

         mov bx,

 .set_cursor:
         mov dx,
         mov al,
         out dx,al
         mov dx,
         mov al,bh
         out dx,al
         mov dx,
         mov al,
         out dx,al
         mov dx,
         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,msg
         mov ch, ;green
         call put_string                  ;显示第一段信息 

         push word [es:code_2_segment]
         mov ax,begin
         push ax                          ;可以直接push begin,+

         retf                             ;转移到代码段执行 

  continue:
         mov ax,[es:data_2_segment]       ;段寄存器DS切换到数据段 
         mov ds,ax

         mov bx,msg1
         mov ch,  ;red
         call put_string                  ;显示第二段信息 
;这里我们显示出多彩的Hello
         mov cx,     ;循环次数
         mov ah,
@1:
         push cx
         mov bx,msg2
         mov ch,ah   
         call put_string                  ;显示Hello

         pop cx
         inc ah    ;属性值增加
         loop @1

         jmp $ 

;===============================================================================
SECTION code_2 align= vstart=          ;定义代码段(字节对齐)

  begin:
         push word [es:code_1_segment]
         mov ax,continue
         push ax                          ;可以直接push continue,+

         retf                             ;转移到代码段接着执行 

;===============================================================================
SECTION data_1 align= vstart=

    msg 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 ,,,
         db '  Example code for calculate 1+2+...+1000:',,,,
         db '     xor dx,dx',,
         db '     xor ax,ax',,
         db '     xor cx,cx',,
         db '  @@:',,
         db '     inc cx',,
         db '     add ax,cx',,
         db '     adc dx,0',,
         db '     inc cx',,
         db '     cmp cx,1000',,
         db '     jle @@',,
         db '     ... ...(Some other codes)',,,,
         db 

;===============================================================================
SECTION data_2 align= vstart=

    msg1 db '  The above contents is written by LeeChung. '
         db '2011-05-06'
         db ,,,
         db 
    msg2 db 'Hello'
         db 
;===============================================================================
SECTION stack align= vstart=

         times  db 

stack_end:  

;===============================================================================
SECTION trail align=

program_end:
           

OK,看一下结果吧,这就是多彩的Hello

硬盘和显卡的访问与控制(三)(含多彩的Hello)——《x86汇编语言:从实模式到保护模式》读书笔记03

【the end 】

继续阅读