x86编程 boot loader 用户程序

友链

版权所有:

  • 《x86 汇编语言 从实模式到保护模式——李忠 王晓波 余洁》
    书中用到的工具
    密码是1

https://gitee.com/wochinijiamile/smartya/blob/master/booktool.7z

nasm编译器:
https://gitee.com/wochinijiamile/smartya/blob/master/nasm-2.07-win32.zip

模拟器:

https://gitee.com/wochinijiamile/smartya/blob/master/Bochs-win64-2.7.exe

密码是1
好东西

用户程序进行了一点改动,对显存先进行了初始化,清楚了bochs在模拟器启动时显示的字符串和显示模式
在这里插入图片描述

用户程序

         ;代码清单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         ;定义代码段1(16字节对齐) 
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                   ; 索引寄存器端口号  0x3d4
         mov al,0x0e                    ; al 0e  8bit光标寄存器的高8bit
         out dx,al                      ; 把al写入索引端口,表示要操作0x0e对应的光标寄存器(高8bit)
         mov dx,0x3d5                   ; 数据端口 0x3d5
         in al,dx                        ;高8位 把高8位取出来
         mov ah,al                      ; 把高8bit存到ax寄存器的高8bit

         mov dx,0x3d4                   ; 和上面同样的方式,取出光标寄存器的低8bit,存到ax的al部分
         mov al,0x0f
         out dx,al
         mov dx,0x3d5
         in al,dx                        ;低8位 
         mov bx,ax                       ;BX=代表光标位置的16位数

         cmp cl,0x0d                     ;回车符?  判断是否为回车符
         jnz .put_0a                     ;不是。看看是不是换行等字符   如果不是就跳转到.put_0a
         mov ax,bx                       ;此句略显多余,但去掉后还得改书,麻烦 
         mov bl,80                       
         div bl
         mul bl                 ;回到行首,当前光标的位置除以80再乘以80即可得到行首的光标位置
         mov bx,ax      ;乘法操作的结果保存在ax中,需要将结果移动到bx中
         jmp .set_cursor

 .put_0a:
         cmp cl,0x0a                     ;换行符?      是不是换行符
         jnz .put_other                  ;不是,那就正常显示字符 
         add bx,80                      ;直接换到下一行(一行80个字符)的当前列
         jmp .roll_screen               ;然后滚屏

 .put_other:                             ;正常显示字符
         mov ax,0xb800                          ;定位到显存segment
         mov es,ax
         shl bx,1               ; bx左移1,相当于乘以2,因为一个字符在显存中占两个字节,真正的字符+显示模式
         mov [es:bx],cl                 ; cl是字符的ascii     

         ;以下将光标位置推进一个字符            ;怎么又右移回来了? 哦哦哦哦,之前给bx左移1,是为了表示显存段的偏移量
         shr bx,1
         add bx,1

 .roll_screen:
         cmp bx,2000                     ;光标超出屏幕?滚屏   判断是否真的需要滚屏   一屏是25行,一行80列,最大值就是1999(从0开始)如果大于等于2000,就说明需要滚屏了
         jl .set_cursor                 ; jl   就是jump if less 没有超出屏幕范围,直接设置光标就行了,否则就需要滚屏
         mov ax,0xb800
         mov ds,ax
         mov es,ax                      ;怎么把ds和es都定位到了显存
         cld                ; 清除DF标志位  方向标志位
         mov si,0xa0    ;哦,我懂了,输入换行符,会把屏幕中原来的第一行顶没,所以需要从第二行开始,将数据往上移动,表现在现存的操作就是从偏移量160的位置开始,因为一行80个字符正好占用160字节,每次移动两个字节,也就是一个字符,我们需要移动24*80个字符,也就是1920次 
         mov di,0x00
         mov cx,1920        
         rep movsw          ;从DS段传送1920个2bytes到ES段
         mov bx,3840                     ;清除屏幕最底一行,这个就是原来的第25行,上面的movsw会把原来的25行复制到24行,但是并不会清除25行,在显存中每个字符占两字节,1920*2=3840,从显存的这个偏移位置开始,我们重复80次,将显存设置为空格字符,即可达到清空最后一行的目的
         mov cx,80
 .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls

         mov bx,1920  ;清空完成之后,将光标位置设置为1920,25行的第一个位置,然后顺利执行设置光标的代码,最后ret返回

 .set_cursor:
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         mov dx,0x3d5
         mov al,bh
         out dx,al              ; 写入高8bit
         mov dx,0x3d4
         mov al,0x0f
         out dx,al
         mov dx,0x3d5           
         mov al,bl                      
         out dx,al             ;;写入低8bit      上面这些代码是写光标位置的

         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



;从开始的位置输出
;将所有涉及到的寄存器压栈
;但是由于没有对es段进行初始化,bochs的输出无法被完全覆盖,而且他的字符样式还被保留了
;我们需要对es段寄存器的一整屏进行初始化
push dx
push ax
push bx
mov bx, 0x0
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
mov al,bh
out dx,al              ; 写入高8bit
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5           
mov al,bl                      
out dx,al             ;;写入低8bit      上面这些代码是写光标位置的
pop bx
pop ax
pop dx

push es
push bx
push cx
mov bx, 0xb800
mov es,bx
         mov bx,0                     ;清除屏幕最底一行,这个就是原来的第25行,上面的movsw会把原来的25行复制到24行,但是并不会清除25行,在显存中每个字符占两字节,1920*2=3840,从显存的这个偏移位置开始,我们重复80次,将显存设置为空格字符,即可达到清空最后一行的目的
         mov cx,2000
 .cssssssssssssls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cssssssssssssls
pop cx
pop bx
pop es

         call put_string                  ;显示第一段信息 ,put_string调用完成
       
       
     push word [es:code_2_segment]  ;要跳转到的段地址   es段寄存器还指向程序头部,es:code_2_segment就是段地址
         mov ax,begin
         push ax                          ;可以直接push begin,80386+ 要跳转到的偏移地址
         
         retf                             ;转移到代码段2执行 这里利用retf指令的原理就是从栈中弹出IP和CS的特性,事先现将自定义的段地址和偏移地址push到栈中,来达到高效跳转的目的,此时程序执行流程进入code_2代码段的begin标号
         
  continue:
         mov ax,[es:data_2_segment]       ;段寄存器DS切换到数据段2 
         mov ds,ax    ;切换数据段到data_2
         
         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                          ;可以直接push continue,80386+
         
         retf                             ;转移到代码段1接着执行 又跳回去了,哎就是玩儿,跳到了code_1代码段的continue标号处
         
;===============================================================================
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
           
      times 256 db 0

stack_end:  

;===============================================================================
SECTION trail align=16
program_end:

程序加载器:

         ;代码清单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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值