《x86汇编语言:从实模式到保护模式》配套代码清单

c05_mbr.asm

         ;代码清单5-1 
         ;文件名:c05_mbr.asm
         ;文件说明:硬盘主引导扇区代码
         ;创建日期:2011-3-31 21:15 
         
         mov ax,0xb800                 ;指向文本模式的显示缓冲区
         mov es,ax
 
         ;以下显示字符串"Label offset:"
         mov byte [es:0x00],'L'
         mov byte [es:0x01],0x07
         mov byte [es:0x02],'a'
         mov byte [es:0x03],0x07
         mov byte [es:0x04],'b'
         mov byte [es:0x05],0x07
         mov byte [es:0x06],'e'
         mov byte [es:0x07],0x07
         mov byte [es:0x08],'l'
         mov byte [es:0x09],0x07
         mov byte [es:0x0a],' '
         mov byte [es:0x0b],0x07
         mov byte [es:0x0c],"o"
         mov byte [es:0x0d],0x07
         mov byte [es:0x0e],'f'
         mov byte [es:0x0f],0x07
         mov byte [es:0x10],'f'
         mov byte [es:0x11],0x07
         mov byte [es:0x12],'s'
         mov byte [es:0x13],0x07
         mov byte [es:0x14],'e'
         mov byte [es:0x15],0x07
         mov byte [es:0x16],'t'
         mov byte [es:0x17],0x07
         mov byte [es:0x18],':'
         mov byte [es:0x19],0x07
 
         mov ax,number                 ;取得标号number的偏移地址
         mov bx,10
 
         ;设置数据段的基地址
         mov cx,cs
         mov ds,cx
 
         ;求个位上的数字
         mov dx,0
         div bx
         mov [0x7c00+number+0x00],dl   ;保存个位上的数字
 
         ;求十位上的数字
         xor dx,dx
         div bx
         mov [0x7c00+number+0x01],dl   ;保存十位上的数字
 
         ;求百位上的数字
         xor dx,dx
         div bx
         mov [0x7c00+number+0x02],dl   ;保存百位上的数字
 
         ;求千位上的数字
         xor dx,dx
         div bx
         mov [0x7c00+number+0x03],dl   ;保存千位上的数字
 
         ;求万位上的数字 
         xor dx,dx
         div bx
         mov [0x7c00+number+0x04],dl   ;保存万位上的数字
 
         ;以下用十进制显示标号的偏移地址
         mov al,[0x7c00+number+0x04]
         add al,0x30
         mov [es:0x1a],al
         mov byte [es:0x1b],0x04
         
         mov al,[0x7c00+number+0x03]
         add al,0x30
         mov [es:0x1c],al
         mov byte [es:0x1d],0x04
         
         mov al,[0x7c00+number+0x02]
         add al,0x30
         mov [es:0x1e],al
         mov byte [es:0x1f],0x04
 
         mov al,[0x7c00+number+0x01]
         add al,0x30
         mov [es:0x20],al
         mov byte [es:0x21],0x04
 
         mov al,[0x7c00+number+0x00]
         add al,0x30
         mov [es:0x22],al
         mov byte [es:0x23],0x04
         
         mov byte [es:0x24],'D'
         mov byte [es:0x25],0x07
          
   infi: jmp near infi                 ;无限循环
      
  number db 0,0,0,0,0
  
  times 203 db 0
            db 0x55,0xaa

c06_mbr.asm

 ;代码清单6-1
         ;文件名:c06_mbr.asm
         ;文件说明:硬盘主引导扇区代码
         ;创建日期:2011-4-12 22:12 
      
         jmp near start
         
  mytext db 'L',0x07,'a',0x07,'b',0x07,'e',0x07,'l',0x07,' ',0x07,'o',0x07,\
            'f',0x07,'f',0x07,'s',0x07,'e',0x07,'t',0x07,':',0x07
  number db 0,0,0,0,0
  
  start:
         mov ax,0x07c0                  ;设置数据段基地址 
         mov ds,ax						;ds寄存器一般保存数据段基地址
         
         mov ax,0xb800                  ;设置附加段基地址 
         mov es,ax						;这里附加段指向显存位置,存放在es寄存器中
         
         cld							;将方向标志位DF清零,以指示传送是负方向的,与此相对应的指令是std
         mov si,mytext					;movsw指令原始数据串需要存放在ds:si位置,目的地址为es:di,因为ds目前指示的是当前代码段基地址地址,因此只要把偏移mytext存入si寄存器即可                
         mov di,0						;当前es指示显存起始位置,因此只要把偏移0存入di即可
         mov cx,(number-mytext)/2       ;实际上等于 13,cx作为计数器,每进行一次rep指令cx-1
         rep movsw						;一次传送一个字(两个字节)
     
         ;得到标号所代表的偏移地址
         mov ax,number					;此代码目的旨在显示number的偏移地址
         
         ;计算各个数位
         mov bx,ax					   ;bx指向当前number偏移地址
         mov cx,5                      ;循环次数 
         mov si,10                     ;除数 
  digit: 
         xor dx,dx						;dx(被除数高16位)清零
         div si							;除法
         mov [bx],dl                   ;保存数位	;为什么这里不用加0x7c00了?
         inc bx 						;bx自加1,指向下一个内存单元,number一共定义了5个字节内存单元
         loop digit
         
         ;显示各个数位
         mov bx,number 
         mov si,4                      
   show:
         mov al,[bx+si]					;从后往前显示
         add al,0x30
         mov ah,0x04
         mov [es:di],ax
         add di,2
         dec si
         jns show						;上一条指令符号位为SF=0(结果为非负)时跳转
         
         mov word [es:di],0x0744

         jmp near $						;无限循环

  times 510-($-$$) db 0					;512字节减去之后两个db指令=510字节,$当前指令偏移,$$当前代码段起始位置,填充字节0(db 0)
                   db 0x55,0xaa

c07_mbr.asm

   ;代码清单7-1
         ;文件名:c07_mbr.asm
         ;文件说明:硬盘主引导扇区代码
         ;创建日期:2011-4-13 18:02

         jmp near start

 message db '1+2+3+...+100='

 start:
         mov ax,0x7c0           ;设置数据段的段基地址 
         mov ds,ax

         mov ax,0xb800          ;设置附加段基址到显示缓冲区
         mov es,ax

         ;以下显示字符串 
         mov si,message          
         mov di,0
         mov cx,start-message
     @g:
         mov al,[si]			;因为这是硬盘主引导扇区代码,因此被加载到0x7c00[si]=[ds:si],就是相对于代码段开头的相对偏移,这个相对偏移就是标签message的值
         mov [es:di],al
         inc di					;di用做显存段地址的相对偏移,字符内容信息放在低一个字节
         mov byte [es:di],0x07
         inc di					;字符显示信息放在高一个字节
         inc si					;si用作寻址字符串相对偏移
         loop @g

         ;以下计算1100的和 
         xor ax,ax				;清空ax寄存器,存放结果
         mov cx,1
     @f:
         add ax,cx
         inc cx					;cx做累加器
         cmp cx,100
         jle @f					;小于等于时跳转

         ;以下计算累加和的每个数位 
         xor cx,cx              ;设置堆栈段的段基地址
         mov ss,cx
         mov sp,cx				;堆栈段指针和段基址都在0x0000处,堆栈段从高地址向低地址生长

         mov bx,10
         xor cx,cx
     @d:
         inc cx			;压栈中用cx记录一共压入栈元素个数,以便之后出栈时能及时停止pop
         xor dx,dx		;被除数[dx:ax]
         div bx			;除数bx
         or dl,0x30		;余数在dx中,但是余数最多到9,因此在dl中就够了,加0x30得到ASCII码
         push dx		;dx中只有dl有意义,但是压栈的单位必须是字(两个字节)
         cmp ax,0
         jne @d			;循环跳出时,结果5050每一位被放在栈中

         ;以下显示各个数位 
     @a:
         pop dx			;出栈,栈顶元素是千位,百位,十位,个位
         mov [es:di],dl
         inc di
         mov byte [es:di],0x07
         inc di
         loop @a

         jmp near $ 

8086寻址方式总结:
1.寄存器寻址 :mov ax,cx
2.立即寻址 :add bx,0xf000 (目的操作数立即数寻址,源操作数寄存器寻址)
3.直接寻址 :mov ax,[0x5c0f]
4.基址寻址:利用基址寄存器bx/bp中的值作为偏移地址,其中bx默认段寄存器为ds(数据段),bp默认段寄存器为ss(堆栈段)。堆栈段指针寄存器为sp,不要混淆。
如:mov dx,[bp-2]
5.变址寻址:利用变址寄存器si/di中的值作为偏移地址,默认段寄存器为ds
如:mov [si+0x100],al
6.基址变址寻址:利用基址寄存器bx/bp和变址寄存器si/di相加作为偏移地址
如:add word [bx+di],0x3000

加载程序 源程序c08_mbr.asm

  ;代码清单8-1
         ;文件名:c08_mbr.asm
         ;文件说明:硬盘主引导扇区代码(加载程序) 
         ;创建日期:2011-5-5 18:17
         
         app_lba_start equ 100           ;声明常数(用户程序起始逻辑扇区号)
                                         ;常数的声明不会占用汇编地址
                                    
SECTION mbr align=16 vstart=0x7c00                                     

         ;设置堆栈段和栈指针 
         mov ax,0      
         mov ss,ax
         mov sp,ax
      
         mov ax,[cs:phy_base]            ;计算用于加载用户程序的逻辑段地址 
         mov dx,[cs:phy_base+0x02]
         mov bx,16        
         div bx            
         mov ds,ax                       ;令DS和ES指向该段以进行操作
         mov es,ax                        
    
         ;以下读取程序的起始部分 
         xor di,di
         mov si,app_lba_start            ;程序在硬盘上的起始逻辑扇区号 
         xor bx,bx                       ;加载到DS:0x0000处 
         call read_hard_disk_0
      
         ;以下判断整个程序有多大
         mov dx,[2]                      ;曾经把dx写成了ds,花了二十分钟排错 
         mov ax,[0]
         mov bx,512                      ;512字节每扇区
         div bx
         cmp dx,0
         jnz @1                          ;未除尽,因此结果比实际扇区数少1 
         dec ax                          ;已经读了一个扇区,扇区总数减1 
   @1:
         cmp ax,0                        ;考虑实际长度小于等于512个字节的情况 
         jz direct
         
         ;读取剩余的扇区
         push ds                         ;以下要用到并改变DS寄存器 

         mov cx,ax                       ;循环次数(剩余扇区数)
   @2:
         mov ax,ds
         add ax,0x20                     ;得到下一个以512字节为边界的段地址
         mov ds,ax  
                              
         xor bx,bx                       ;每次读时,偏移地址始终为0x0000 
         inc si                          ;下一个逻辑扇区 
         call read_hard_disk_0
         loop @2                         ;循环读,直到读完整个功能程序 

         pop ds                          ;恢复数据段基址到用户程序头部段 
      
         ;计算入口点代码段基址 
   direct:
         mov dx,[0x08]
         mov ax,[0x06]
         call calc_segment_base
         mov [0x06],ax                   ;回填修正后的入口点代码段基址 
      
         ;开始处理段重定位表
         mov cx,[0x0a]                   ;需要重定位的项目数量
         mov bx,0x0c                     ;重定位表首地址
          
 realloc:
         mov dx,[bx+0x02]                ;32位地址的高16位 
         mov ax,[bx]
         call calc_segment_base
         mov [bx],ax                     ;回填段的基址
         add bx,4                        ;下一个重定位项(每项占4个字节) 
         loop realloc 
      
         jmp far [0x04]                  ;转移到用户程序  
 
;-------------------------------------------------------------------------------
read_hard_disk_0:                        ;从硬盘读取一个逻辑扇区
                                         ;输入:DI:SI=起始逻辑扇区号
                                         ;      DS:BX=目标缓冲区地址
         push ax
         push bx
         push cx
         push dx
      
         mov dx,0x1f2
         mov al,1
         out dx,al                       ;读取的扇区数

         inc dx                          ;0x1f3
         mov ax,si
         out dx,al                       ;LBA地址7~0

         inc dx                          ;0x1f4
         mov al,ah
         out dx,al                       ;LBA地址15~8

         inc dx                          ;0x1f5
         mov ax,di
         out dx,al                       ;LBA地址23~16

         inc dx                          ;0x1f6
         mov al,0xe0                     ;LBA28模式,主盘
         or al,ah                        ;LBA地址27~24
         out dx,al

         inc dx                          ;0x1f7
         mov al,0x20                     ;读命令
         out dx,al

  .waits:
         in al,dx
         and al,0x88
         cmp al,0x08
         jnz .waits                      ;不忙,且硬盘已准备好数据传输 

         mov cx,256                      ;总共要读取的字数
         mov dx,0x1f0
  .readw:
         in ax,dx
         mov [bx],ax
         add bx,2
         loop .readw

         pop dx
         pop cx
         pop bx
         pop ax
      
         ret

;-------------------------------------------------------------------------------
calc_segment_base:                       ;计算16位段地址
                                         ;输入:DX:AX=32位物理地址
                                         ;返回:AX=16位段基地址 
         push dx                          
         
         add ax,[cs:phy_base]
         adc dx,[cs:phy_base+0x02]
         shr ax,4
         ror dx,4
         and dx,0xf000
         or ax,dx
         
         pop dx
         
         ret

;-------------------------------------------------------------------------------
         phy_base dd 0x10000             ;用户程序被加载的物理起始地址
         
 times 510-($-$$) db 0
                  db 0x55,0xaa

用户程序 编译源程序 c08.asm

   ;代码清单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
         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:

c09_1.asm

         ;代码清单9-1
         ;文件名:c09_1.asm
         ;文件说明:用户程序 
          ;创建日期:2011-4-16 22:03
         
;===============================================================================
SECTION header vstart=0                     ;定义用户程序头部段 
    program_length  dd program_end          ;程序总长度[0x00]
    
    ;用户程序入口点
    code_entry      dw start                ;偏移地址[0x04]
                    dd section.code.start   ;段地址[0x06] 
    
    realloc_tbl_len dw (header_end-realloc_begin)/4
                                            ;段重定位表项个数[0x0a]
    
    realloc_begin:
    ;段重定位表           
     code_segment    dd section.code.start   ;[0x0c]
    data_segment    dd section.data.start   ;[0x14]
    stack_segment   dd section.stack.start  ;[0x1c]
    
header_end:                
    
;===============================================================================
SECTION code align=16 vstart=0           ;定义代码段(16字节对齐) 
new_int_0x70:
      push ax
      push bx
      push cx
      push dx
      push es
      
  .w0:                                    
      mov al,0x0a                        ;阻断NMI。当然,通常是不必要的
      or al,0x80                          
      out 0x70,al
      in al,0x71                         ;读寄存器A
      test al,0x80                       ;测试第7位UIP 
      jnz .w0                            ;以上代码对于更新周期结束中断来说 
                                         ;是不必要的 
      xor al,al
      or al,0x80
      out 0x70,al
      in al,0x71                         ;读RTC当前时间()
      push ax

      mov al,2
      or al,0x80
      out 0x70,al
      in al,0x71                         ;读RTC当前时间()
      push ax

      mov al,4
      or al,0x80
      out 0x70,al
      in al,0x71                         ;读RTC当前时间()
      push ax

      mov al,0x0c                        ;寄存器C的索引。且开放NMI 
      out 0x70,al
      in al,0x71                         ;读一下RTC的寄存器C,否则只发生一次中断
                                         ;此处不考虑闹钟和周期性中断的情况 
      mov ax,0xb800
      mov es,ax

      pop ax
      call bcd_to_ascii
      mov bx,12*160 + 36*2               ;从屏幕上的1236列开始显示

      mov [es:bx],ah
      mov [es:bx+2],al                   ;显示两位小时数字

      mov al,':'
      mov [es:bx+4],al                   ;显示分隔符':'
      not byte [es:bx+5]                 ;反转显示属性 

      pop ax
      call bcd_to_ascii
      mov [es:bx+6],ah
      mov [es:bx+8],al                   ;显示两位分钟数字

      mov al,':'
      mov [es:bx+10],al                  ;显示分隔符':'
      not byte [es:bx+11]                ;反转显示属性

      pop ax
      call bcd_to_ascii
      mov [es:bx+12],ah
      mov [es:bx+14],al                  ;显示两位小时数字
      
      mov al,0x20                        ;中断结束命令EOI 
      out 0xa0,al                        ;向从片发送 
      out 0x20,al                        ;向主片发送 

      pop es
      pop dx
      pop cx
      pop bx
      pop ax

      iret

;-------------------------------------------------------------------------------
bcd_to_ascii:                            ;BCD码转ASCII
                                         ;输入:AL=bcd码
                                         ;输出:AX=ascii
      mov ah,al                          ;分拆成两个数字 
      and al,0x0f                        ;仅保留低4位 
      add al,0x30                        ;转换成ASCII 

      shr ah,4                           ;逻辑右移4and ah,0x0f                        
      add ah,0x30

      ret

;-------------------------------------------------------------------------------
start:
      mov ax,[stack_segment]
      mov ss,ax
      mov sp,ss_pointer
      mov ax,[data_segment]
      mov ds,ax
      
      mov bx,init_msg                    ;显示初始信息 
      call put_string

      mov bx,inst_msg                    ;显示安装信息 
      call put_string
      
      mov al,0x70
      mov bl,4
      mul bl                             ;计算0x70号中断在IVT中的偏移
      mov bx,ax                          

      cli                                ;防止改动期间发生新的0x70号中断

      push es
      mov ax,0x0000
      mov es,ax
      mov word [es:bx],new_int_0x70      ;偏移地址。
                                          
      mov word [es:bx+2],cs              ;段地址
      pop es

      mov al,0x0b                        ;RTC寄存器B
      or al,0x80                         ;阻断NMI 
      out 0x70,al
      mov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更 
      out 0x71,al                        ;新结束后中断,BCD码,24小时制 

      mov al,0x0c
      out 0x70,al
      in al,0x71                         ;读RTC寄存器C,复位未决的中断状态

      in al,0xa1                         ;8259从片的IMR寄存器 
      and al,0xfe                        ;清除bit 0(此位连接RTC)
      out 0xa1,al                        ;写回此寄存器 

      sti                                ;重新开放中断 

      mov bx,done_msg                    ;显示安装完成信息 
      call put_string

      mov bx,tips_msg                    ;显示提示信息
      call put_string
      
      mov cx,0xb800
      mov ds,cx
      mov byte [12*160 + 33*2],'@'       ;屏幕第12行,35.idle:
      hlt                                ;使CPU进入低功耗状态,直到用中断唤醒
      not byte [12*160 + 33*2+1]         ;反转显示属性 
      jmp .idle

;-------------------------------------------------------------------------------
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
         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

;===============================================================================
SECTION data align=16 vstart=0

    init_msg       db 'Starting...',0x0d,0x0a,0
                   
    inst_msg       db 'Installing a new interrupt 70H...',0
    
    done_msg       db 'Done.',0x0d,0x0a,0

    tips_msg       db 'Clock is now working.',0
                   
;===============================================================================
SECTION stack align=16 vstart=0
           
                 resb 256
ss_pointer:
 
;===============================================================================
SECTION program_trail
program_end:
————————————————
版权声明:本文为CSDN博主「ARM的程序员敲着诗歌的梦」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/longintchar/article/details/50446330

c11_mbr.asm

  ;代码清单11-1
         ;文件名:c11_mbr.asm
         ;文件说明:硬盘主引导扇区代码 
         ;创建日期:2011-5-16 19:54

         ;设置堆栈段和栈指针 
         mov ax,cs      
         mov ss,ax
         mov sp,0x7c00
      
         ;计算GDT所在的逻辑段地址 
         mov ax,[cs:gdt_base+0x7c00]        ;16位 
         mov dx,[cs:gdt_base+0x7c00+0x02]   ;16位 
         mov bx,16        
         div bx            
         mov ds,ax                          ;令DS指向该段以进行操作
         mov bx,dx                          ;段内起始偏移地址 
      
         ;创建0#描述符,它是空描述符,这是处理器的要求
         mov dword [bx+0x00],0x00
         mov dword [bx+0x04],0x00  

         ;创建#1描述符,保护模式下的代码段描述符
         mov dword [bx+0x08],0x7c0001ff     
         mov dword [bx+0x0c],0x00409800     

         ;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区) 
         mov dword [bx+0x10],0x8000ffff     
         mov dword [bx+0x14],0x0040920b     

         ;创建#3描述符,保护模式下的堆栈段描述符
         mov dword [bx+0x18],0x00007a00
         mov dword [bx+0x1c],0x00409600

         ;初始化描述符表寄存器GDTR
         mov word [cs: gdt_size+0x7c00],31  ;描述符表的界限(总字节数减一)   
                                             
         lgdt [cs: gdt_size+0x7c00]
      
         in al,0x92                         ;南桥芯片内的端口 
         or al,0000_0010B
         out 0x92,al                        ;打开A20

         cli                                ;保护模式下中断机制尚未建立,应 
                                            ;禁止中断 
         mov eax,cr0
         or eax,1
         mov cr0,eax                        ;设置PE位
      
         ;以下进入保护模式... ...
         jmp dword 0x0008:flush             ;16位的描述符选择子:32位偏移
                                            ;清流水线并串行化处理器 
         [bits 32] 

    flush:
         mov cx,00000000000_10_000B         ;加载数据段选择子(0x10)
         mov ds,cx

         ;以下在屏幕上显示"Protect mode OK." 
         mov byte [0x00],'P'  
         mov byte [0x02],'r'
         mov byte [0x04],'o'
         mov byte [0x06],'t'
         mov byte [0x08],'e'
         mov byte [0x0a],'c'
         mov byte [0x0c],'t'
         mov byte [0x0e],' '
         mov byte [0x10],'m'
         mov byte [0x12],'o'
         mov byte [0x14],'d'
         mov byte [0x16],'e'
         mov byte [0x18],' '
         mov byte [0x1a],'O'
         mov byte [0x1c],'K'

         ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 
         mov cx,00000000000_11_000B         ;加载堆栈段选择子
         mov ss,cx
         mov esp,0x7c00

         mov ebp,esp                        ;保存堆栈指针 
         push byte '.'                      ;压入立即数(字节)
         
         sub ebp,4
         cmp ebp,esp                        ;判断压入立即数时,ESP是否减4 
         jnz ghalt                          
         pop eax
         mov [0x1e],al                      ;显示句点 
      
  ghalt:     
         hlt                                ;已经禁止中断,将不会被唤醒 

;-------------------------------------------------------------------------------
     
         gdt_size         dw 0
         gdt_base         dd 0x00007e00     ;GDT的物理地址 
                             
         times 510-($-$$) db 0
                          db 0x55,0xaa

c12_mbr.asm

   ;代码清单12-1
    ;文件名:ex12-1.asm
    ;文件说明:硬盘主引导扇区代码
    ;创建日期:16:23 2018/5/30

;---------------------------------------------------------------    
;定义常量
;---------------------------------------------------------------    
    MEMORY_START equ 0x100000           ;要检测的内存起始地址
    MEMORY_END   equ 0x500000           ;要检测的内存结束地址
    MEMORY_SIZE  equ (MEMORY_END-MEMORY_START)/4    ;以双字位单元
;---------------------------------------------------------------    
    
    ;设置堆栈段和栈指针
    mov eax,cs
    mov ss,eax
    mov sp,0x7c00
    
    ;计算GDT所在的逻辑段地址
    mov eax,[cs:pdgt+0x7c00+0x02]   ;GDT的32位线性基地址
    xor edx,edx
    mov ebx,16
    div ebx                         ;分解成16位逻辑地址
    
    mov ds,eax          ;令DS指向该段以进行操作:EAX低16位有效 DS=0x7e00
    mov ebx,edx         ;段内起始偏移地址:EDX EBX低16位有效 ebx=0x0000
    
    ;创建0#描述符,它是空描述符,这是处理器的要求
    mov dword [ebx+0x00],0x00000000
    mov dword [ebx+0x04],0x00000000
    
    ;创建1#描述符,这是一个数据段,对应0~4GB的线性地址空间
    mov dword [ebx+0x08],0x0000ffff     ;段基地址0x00000000
    mov dword [ebx+0x0c],0x00cf9200
    
    ;创建2#描述符,这是一个只执行的代码段
    mov dword [ebx+0x10],0x7c0001ff     ;段基地址0x00007C00
    mov dword [ebx+0x14],0x00409800
    
    ;创建3#描述符,这是上面代码段的别名
    mov dword [ebx+0x18],0x7c0001ff     ;段基地址0x00007C00
    mov dword [ebx+0x1c],0x00409200
    
    ;创建4#描述符,这是栈段
    mov dword [ebx+0x20],0x7c00fffe     ;段基地址0x00007C00
    mov dword [ebx+0x24],0x00cf9600
    
    ;初始化描述符寄存器GDTR
    mov word [cs:pdgt+0x7c00],39        ;5*8-1=39
    lgdt [cs:pdgt+0x7c00]
    
    in al,0x92                          ;南桥芯片的端口
    or al,0000_0010B
    out 0x92,al                         ;打开A20
    
    cli
    
    mov eax,cr0
    or eax,1
    mov cr0,eax                         ;设置PE位
    
    ;以下进入保护模式... ...
    jmp dword 0x0010:flush
    
    [bits 32]
flush:
    mov eax,0x0018      ;索引号3#
    mov ds,eax      
    
    mov eax,0x0008      ;索引号1#
    mov es,eax
    mov fs,eax
    mov gs,eax
    
    mov eax,0x0020      ;索引号4#
    mov ss,eax
    xor esp,esp         ;ESP=0
    
    mov dword [es:0x0b8000],0x072e0750  ;'P.'
    mov dword [es:0x0b8004],0x072e074d  ;'M.'
    mov dword [es:0x0b8008],0x07200720  ;'  '
    mov dword [es:0x0b800c],0x076b076f  ;'ok'

    
;---------------------------------------------------------------    
;显示需要检测的总的单元个数
;---------------------------------------------------------------        
    mov byte [es:0x0b8140],'H'
    mov byte [es:0x0b8142],'E'
    mov byte [es:0x0b8144],'X'
    mov byte [es:0x0b8146],':'
    
    mov ebp,0x0b8140+10
    mov ecx,0
    call check
    
    mov byte [es:0x0b8140+30],'/'
    
    mov ebp,0x0b8140+34
    mov ecx,MEMORY_SIZE
    call check
;---------------------------------------------------------------        
;内存检测
;以双字为单元,使用花码0x55aa55aa0xaa55aa55进行内存检测
;---------------------------------------------------------------        
        
        xor ecx,ecx                 ;检测的单元个数
        mov ebx,MEMORY_START        ;检测的起始地址
exam:   
        mov dword [es:ebx],0x55aa55aa
        cmp dword [es:ebx],0x55aa55aa
        jnz err
        
        mov dword [es:ebx],0xaa55aa55
        mov dword [es:ebx],0xaa55aa55
        jnz err

        add ebx,4
        inc ecx
        mov byte  [es:0x0b80a0+28],'!'
        not byte  [es:0x0b80a0+29]
        
        
        mov ebp,0x0b8140+10
        call check
        
        cmp ebx,MEMORY_END
        jnz exam
        
        mov dword [es:0x0b80b0+20],0x076b076f       ;'ok'
        
;---------------------------------------------------------------        
err:
        hlt                         ;进入停机状态

        
;---------------------------------------------------------------
;子程序:check
;参数:    
;       ecx = 要显示的数值
;       ebp = 数值在显存的起始位置
;功能:    计算并显示检测的内存个数(以双字位单位)
;---------------------------------------------------------------
check:          push ebx
                push ecx
                push esi
                push eax
                push ebp
                
                mov eax,ecx
                xor ebx,ebx
                mov ecx,8
                mov esi,16
        digit:  
                xor edx,edx
                div esi
                mov [mem+ebx],dl
                inc ebx
                loop digit
                
                
                xor edi,edi
                xor ebx,ebx
                mov esi,7
        show:   
                mov al,[mem+esi]
                mov bl,al
                mov al,[number+ebx]
                mov [es:ebp+edi],al
                add edi,2
                dec esi
                jns show
        
                pop ebp
                pop eax
                pop esi
                pop ecx
                pop ebx
        
ret
;---------------------------------------------------------------
    mem     db  0,0,0,0,0,0,0,0     ;存放数位
    number  db '0123456789ABCDEF'
;---------------------------------------------------------------
    pdgt    dw  0
            dd  0x00007e00          ;GDT的物理地址
;---------------------------------------------------------------
    times 510-($-$$) db 0
                     db 0x55,0xaa
  • 24
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
x86汇编语言从实模式保护模式》是一本经典的计算机软件和硬件教材。该书通过详细讲解x86汇编语言的实模式保护模式两个阶段的特点和转换过程,帮助读者深入了解计算机的底层工作原理。 实模式是早期x86处理器的工作模式,它具有简单和直接访问内存的特点,但存在一些限制,例如只能寻址最多1MB的内存空间。《x86汇编语言从实模式保护模式》首先介绍了实模式的基本概念和操作指令,然后通过实际的编程示例,让读者熟悉实模式下的汇编语言编程。 接着,《x86汇编语言从实模式保护模式》详细介绍了保护模式的内存管理、特权级和中断处理等概念和机制。保护模式是现代操作系统常用的工作模式,它具有更强大的内存管理能力和安全性,可以有效地利用计算机的资源。通过学习保护模式的相关知识,读者可以了解操作系统的内核和应用程序的执行过程,并且能够编写更强大和高效的程序。 《x86汇编语言从实模式保护模式》还介绍了x86处理器的特殊指令和编程技巧,例如x87浮点指令和SSE指令集等。这些指令和技巧可以提高程序的性能和效率,使程序员能够充分发挥x86体系结构的优势。 总之,《x86汇编语言从实模式保护模式》是一本全面且实用的教材,通过对x86汇编语言的学习,读者可以更深入地了解计算机的底层原理和操作系统的工作方式。无论是想成为程序员还是深入研究计算机体系结构的人员,这本书都是一本必读之作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值