[010][x86汇编语言]学习用户程序的编写(c08.asm)

源程序来源

https://www.jianshu.com/p/72c151606908

加载程序

  • c08_mbr.asm

用户源程序:增加注释

;
;文件名:c08-2.asm
;文件说明:用户程序
;创建日期:13:08 2018/5/23

;----------------------------------------------------------------------
SECTION header vstart=0                 ;定义用户程序头部段
    program_length  dd  program_end     ;程序总长度[0x00]
    
    ;用户程序入口点
    code_entry      dw  start                   ;偏移地址[0x04] 此处的start来源于自己的命名
                    dd  section.code_1.start    ;段地址[0x06] 此处.start是汇编指令的语法
                        
    realloc_tbl_len dw  (header_end - code_1_segment)/4
                                                ;段重定位表项个数[0x0a]
                                                ;1个表项占4个字节
    ;段重定位表项
    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结尾)

            mov cl,[bx]             ;取一个字符
            or cl,cl                ;cl=0?
            jz .exit                ;cl=0时返回主程序
            call put_char           
            inc bx                  ;下一个字符
            jmp put_string
            
        .exit:
            ret
            
;----------------------------------------------------------------------
put_char:                           ;显示一个字符
        
            push ax
            push bx
            push cx
            push dx
            push ds
            push es
            
            ;以下取当前光标:光标位置是一个16位的数值
            mov dx,0x3d4            ;索引寄存器端口号 0x3d4
            mov al,0x0e             ;索引值14
            out dx,al
            mov dx,0x3d5            ;数据端口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                ;左移1位相当于乘以2
            mov [es:bx],cl          ;于光标处显示字符
            
            ;以下将光标位置推进一个字符
            shr bx,1                ;右移相当于除以2,换回来bx
            add bx,1                ;推进光标位置
            
    .roll_screen:                   ;VGA文本模式,每行80个字符,25行
            cmp bx,2000             ;光标超出屏幕?滚屏
            jl .set_cursor
            
            mov ax,0xb800
            mov ds,ax
            mov es,ax
            cld
            mov si,0xa0             ;从屏幕第2行第0列开始向上复制一行
            mov di,0x00
            mov cx,1920             ;80*25-80=1920
            rep movsw               ;以字为单位进行复制
            mov bx,3840             ;4000 - 160 = 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             ;高8位
            out dx,al
            mov dx,0x3d5
            mov al,bh
            out dx,al
            mov dx,0x3d4
            mov al,0x0f             ;低8位
            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                ;stack段里保留保留256字节的空间 mov sp,256
            
            mov ax,[data_1_segment]         ;设置到用户程序自己的数据段
            mov ds,ax
            
            mov bx,msg0                     ;显示第一段信息
            call put_string                 ;put_string用[bx]取每一个字符
            
            push word [es:code_2_segment]   ;段寄存器DS切换到数据段2
            mov ax,begin
            push ax
            
            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                     
            
            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:            ;trail段没有vstart标记,program_end是针对用户程序开头的偏移量

代码说明

  • 循环与子程序的调用关系

循环 
    put_string
参数
   DS 段寄存器器指向数据段
   BX 指向字符串的标号所在(位于数据段内,vstart=0 规定是相对数据段开头的偏移量)
-----------------------------------------------------------
-----------------------------------------------------------

子程序
    put_char
功能
    显示一个字符(注意是一个)
参数
    cl 要显示的字符
过程
    见下方put_char流程图

-----------------------------------------------------------
-----------------------------------------------------------
光标位置的计算与光标的显示分开的,由不同的情况计算出不同的位置数值,再统一由 子程序.setcursor 进行显示。
图8-19 过程put_char的流程图.png

《x86汇编语言:从实模式到保护模式》 第143页

  • 光标的读与写
指定索引寄存器 端口号是 0x3d4
光标位置是16位数,索引值 14(0x0e)和 15(0x0f)提供光标位置的高8位、低8位
指定好寄存器之后,通过数据端口 0x3d5 进行读写


取光标位置,取到的位置16位数值放到 BX里面
===================================================
 ;以下取当前光标:光标位置是一个16位的数值
            mov dx,0x3d4            ;索引寄存器端口号 0x3d4
            mov al,0x0e             ;索引值14
            out dx,al

            mov dx,0x3d5            ;数据端口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位数
---------------------------------------------------------------------------------------------



写光标位置,根据不同情况计算好的光标位置通过端口写
===============================================================
                               ;设置光标
    .set_cursor:                    ;不同的情况已用不同的方法将光标的新位置计算好
            mov dx,0x3d4
            mov al,0x0e             ;高8位
            out dx,al

            mov dx,0x3d5
            mov al,bh
            out dx,al

            mov dx,0x3d4
            mov al,0x0f             ;低8位
            out dx,al

            mov dx,0x3d5
            mov al,bl
            out dx,al
-------------------------------------------------------------------------------------------------------------------
  • 显存、光标、字符
VGA 文本模式 一页有25行,每行有80个字符

指定 es = 0xb800 di= 0x0000
假设要在第0行第0列,显示一个字符‘a’

1、需要在偶数地址写字符 
mov es:[di],'a'
2、在奇数地址写颜色属性,黑底白字0x07
mov es:[di+1],0x07

因此可以看到一页能显示 80*25=2000个字符,
但同时也是存了4000个字节的数据
(一半是字符的ASCII码,一半是每个字符的对应属性)

光标的位置是一个16位的数值,
因此,对光标而言,
0表示第0行0列,
1表示第0行1列,
....
20表示第0行20列,
每一行有80列,
79表示第0行79列,
80表示第1行第0行(换行...)
....

==========================================
在子程序
.put_other:
            mov ax,0xb800
            mov es,ax
            shl bx,1                ;左移1位相当于乘以2
            mov [es:bx],cl          ;于光标处显示字符
            
            ;以下将光标位置推进一个字符
            shr bx,1                ;右移相当于除以2,换回来bx
            add bx,1                ;推进光标位置

是通过 光标的位置数值 来确定 字符的显示位置的,
光标在哪里闪烁,字符就写到那里,
【光标位置数值 x 2 = 字符要被送入的那个偶地址】
字符的颜色属性默认是0x07不需要修改。
  • SECTION关键词
    在用户程序中使用SECTION关键词人为的分了很多段,然后把这些段集中放到头部段header,并且借由加载器的回写在执行时变成了都是可以映射到真实物理地址的段地址,需要用到哪个段就去头部段里取,这样少得可怜的段寄存器就够用了,如果全局固定es指向头部段,那么就疯狂复用ds寄存器
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容