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


    上一篇博文我们用了很大的篇幅说了加载器,这一篇我们该说说用户程序了。
    先看作者的源码吧。

             ;代码清单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         ;定义代码段116字节对齐) 
    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          ;定义代码段216字节对齐)
    
      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          ;定义代码段216字节对齐)
    
      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表示红色。
    用户代码修改1

    我们也可以自定义要显示的字符。
    我修改后的用户代码如下:

             ;代码清单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         ;定义代码段116字节对齐) 
    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          ;定义代码段216字节对齐)
    
      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
    修改的用户代码2

    【the end 】

  • 相关阅读:
    从下拉菜单设计细节看Amazon对用户体验的把握
    单行省略号纯css解决方案
    2013年度最新整理45个div+css兼容性问题与解决方案
    20个非常绚丽的 CSS3 特性应用演示
    分享30个最佳 jQuery Lightbox 效果插件【只收藏经典】
    如何让网站被快速收录?
    About FAR HTML
    前端工具
    Web设计师必备的10款最佳排版工具
    清除浮动解决方案
  • 原文地址:https://www.cnblogs.com/longintchar/p/5224414.html
Copyright © 2020-2023  润新知