记录一段主引导扇区程序

记录一段主引导扇区程序,它会跳转到逻辑扇区号100的扇区执行用户程序:

;文件名:fifteen.asm
;文件说明:硬盘主引导扇区代码(加载程序) 
;创建日期:2022-11-29 20:54
         
         app_lba_start equ 100           ;声明常数(用户程序起始逻辑扇区号必须是100开始)
                                         ;常数的声明不会占用汇编地址
                                    
SECTION mbr align=16 vstart=0x7c00   	 ; 段内所有元素的汇编地址将从0x7c00开始                                 

         ;设置堆栈段和栈指针 
         mov ax,0      
         mov ss,ax						 ;栈段和栈指针都指向的0,也就是0000:0000
         mov sp,ax
      
         mov ax,[cs:phy_base]            ;计算用于加载用户程序的逻辑段地址,phy_base为四个字节,0x10000;地址为0x7c00+phy_base
         mov dx,[cs:phy_base+0x02]		 ;DX AX:  0001 0000;小端字节序存放4字节
         mov bx,16        				 ;除数为16位,所以被除数在DX和AX当中,也就是10000,右移4位的目的是得到段地址1000
         div bx            
         mov ds,ax                       ;计算完后DX为余数,AX为商,令DS和ES指向该段以进行操作,段地址为1000
         mov es,ax                        
    
         ;以下读取程序的起始部分 ,用于读取用户程序的第一个扇区,该扇区包括了用户程序的头部,而头部包括了用户程序的大小
         xor di,di
         mov si,app_lba_start            ;程序在硬盘上的起始逻辑扇区号 
         xor bx,bx                       ;加载到DS:0x0000处 
         call read_hard_disk_0
      
         ;以下判断整个程序有多大
         mov dx,[2]                      ;把高16位放到dx,此处是在使用刚加载进来的用户程序的头部的前四个字节,也就是用户程序的总长度 
         mov ax,[0]						 ;把低16位放到ax
         mov bx,512                      ;512字节每扇区
         div bx
         cmp dx,0						 ;计算完后DX为余数,AX为商;如果余数不为零,则扇区数为ax中的数+1,但此时已经读掉了一个头部扇区,因此还剩ax中的数的个数
         jnz @1                          ;如果dx不为零,则ax中的个数正好为剩余的扇区数,直接转移,不执行下一条指令
         dec ax                          ;如果dx为零,说明没余数,执行到这,已经读了一个扇区,扇区总数需减1 
   @1:
         cmp ax,0                        ;考虑用户程序实际长度小于等于512个字节的情况 
         jz direct
         
         ;读取剩余的扇区
         push ds                         ;以下要用到并改变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]                  ;转移到用户程序;取出16位的偏移地址和16位的逻辑段地址(已经从32位汇编地址变成了16位的逻辑地址),修改cs:ip
 
;-------------------------------------------------------------------------------
read_hard_disk_0:                        ;从硬盘读取一个逻辑扇区,用DI:SI来传递28位的逻辑扇区号
                                         ;输入:DI:SI=起始逻辑扇区号 0000:0100
                                         ;      DS:BX=目标缓冲区地址 1000:0000,硬盘读出的数据将来要存到内存的地址
         push ax
         push bx
         push cx
         push dx
      
         mov dx,0x1f2					 ;将端口号写入到dx寄存器,0x1f28位端口,设置读取的扇区数量
         mov al,1						 ;扇区数量
         out dx,al                       ;读取的扇区数,就读一个扇区

		;扇区的读写是连续的,只需要写出第一个扇区的编号;扇区号为28位
         inc dx                          ;0x1f3 8位端口,扇区号低0~7位设置端口(LBA28模式)
         mov ax,si
         out dx,al                       ;LBA地址7~0

         inc dx                          ;0x1f4 8位端口,扇区号低8~15位设置端口(LBA28模式)
         mov al,ah
         out dx,al                       ;LBA地址15~8

         inc dx                          ;0x1f5 8位端口,扇区号低16~23位设置端口(LBA28模式)
         mov ax,di
         out dx,al                       ;LBA地址23~16

         inc dx                          ;0x1f6 低四位设置扇区号低24~27位设置端口(LBA28模式),高四位,第75位固定为1;第6位 置0表示CHS模式,置1表示LBA模式;
											;4位 置0表示主硬盘,置1表示从硬盘
         mov al,0xe0                     ;LBA28模式,主盘
         or al,ah                        ;LBA地址27~24
         out dx,al

         inc dx                          ;0x1f7 8位端口,命令/状态端口;向这个端口传输0x20就表示 请求硬盘读,随后硬盘会每时每刻将它的状态信息传输到这个端口,
											;一旦硬盘准备好了就可以开始传输数据;第7位 置0表示硬盘不忙,置1表示硬盘忙;第3位 置0表示未准备好与主机交换数据,
											;1 表示准备好与硬盘交换数据;第0位表示前一条命令执行错误,具体原因查看0x1f1端口
         mov al,0x20                     ;读命令
         out dx,al						 ;请求硬盘读

  .waits:								 ;用于等待,判断硬盘是否忙和数据是否准备好
         in al,dx
         and al,0x88
         cmp al,0x08
         jnz .waits                      ;不忙,且硬盘已准备好数据传输 

         mov cx,256                      ;总共要读取的字数
         mov dx,0x1f0					 ;0x1f016位端口,数据传输端口
  .readw:								 ;硬盘准备好后,开始读取数据到内存1000:0000,读取一个扇区
         in ax,dx						 ;把读到的数据放到ax寄存器中
         mov [bx],ax					 ;加载到DS:BX处,也就是加载到0x1000:0x0000处,第一次循环时bx为0,也就是把读到的数据存入到[bx]
         add bx,2						 ;每次都是一个字,所以移动两个字节
         loop .readw					 ;重复256次,正好一个扇区

         pop dx
         pop cx
         pop bx
         pop ax
      
         ret

;-------------------------------------------------------------------------------
calc_segment_base:                       ;计算16位段地址
                                         ;输入:DX:AX=32位汇编地址,低20位有效,且最后四位为零,因为是段地址
                                         ;返回:AX=16位段基地址 ,逻辑段地址
         push dx        				 ;dx不用做返回,不应该破坏dx中的数据                  
         
         add ax,[cs:phy_base]			 ;段的汇编地址与程序的起始物理地址相加。可能会进位,改变cf标志位
         adc dx,[cs:phy_base+0x02]		 ;adc会判断标志寄存器的cf位
         shr ax,4						 ;逻辑右移
         ror dx,4						 ;循环右移
         and dx,0xf000					
         or ax,dx						 ;组合两个寄存器中的值,最终ax中就是逻辑段地址
         
         pop dx
         
         ret

;-------------------------------------------------------------------------------
         phy_base dd 0x10000             ;开辟一个32位空间存放,用户程序被加载的物理起始地址
         
 times 510-($-$$) db 0					 ;主引导扇区为512字节,使用0进行填充
                  db 0x55,0xaa			 ;主引导扇区要以0x55 0xaa结尾
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值