本文新用的汇编指令
ror
;假设一个值a="二三抱",那么我让a循环右移1位,那么a="抱二三"
;ror与shr指令使用方式相同
;使用场景:将2个字组合成1个字,例如8086下将0xAB00右移8位,并与0x00CD合并
mov ax,0xAB00
shr ax,8 ;走完此行ax=0xAB
mov bx,0x00CD
ror bx,8 ;走完此行bx=0xCD00
or bx,ax ;走完此行bx=0xCDAB,到此场景演示完毕
jmp far
jmp far [0x04] ;以ds为段寄存器,偏移量04,05的位置是ip
;偏移量06,07的位置是cs
加载器用来将硬盘上的用户程序放到内存,并且回填逻辑段地址,只有这样,用户才能根据逻辑段地址,顺利的运行程序,假设编译好的用户程序在硬盘的LBA号为100的扇区上,下面的代码将读取硬盘100扇区的内容,加载到内存,然后回填段地址,最后将CPU控制权转到用户程序上执行
;从硬盘的第几扇区开始读数据
hard_index equ 100
;将读取的数据放到memory_index所指定的物理内存处
;==============开始从硬盘读512个字节的数据=====
mov al,1 ;操作1个扇区
mov dx,0x1f2
out dx,al
mov al,hard_index ;从第几个扇区开始操作
mov dx,0x1f3
out dx,al ;此处巨坑,这个端口特殊,
;必须al,其他8位端口ax也可以
xor al,al ;1f4端口直接写死赋值0,反正读
mov dx,0x1f4 ;100逻辑扇区,1f4也用不上
out dx,al
mov dx,0x1f5 ;此处同1f4端口,也赋值0
out dx,al
mov dx,0x01F6 ;1f6低4位是0,表示操作的扇区号
mov al,0xE0 ;1f6高4位表示操作主硬盘,以LBA方式
out dx,al
mov dx,0x1F7 ;往1f7写20表示读数据
mov al,0x20
out dx,al
mov dx,0x1F7
;无限轮询等待可以读取数据
waits:
in al,dx
and al,0x88 ;取位7和位3的值
cmp al,0x08 ;如果位7=0,位3=1,说明
jnz waits ;硬盘控制器准备就绪
mov ax,0x7c0
mov ds,ax ;设置ds,为了获取内存加载地址
mov ax,[memory_index]
mov bx,[memory_index+2]
shr ax,4
ror bx,4
and bx,0xF000
or ax,bx ;一顿操作下来,or之后ax=0x1000
mov ds,ax ;此时ds=0x1000
xor si,si ;一般首选bx,而不是si,毕竟都用ds和bx
mov cx,512 ;假设用户程序不超过512个字节
mov dx,0x1F0 ;注意1f0是16位端口而不是8位
reads: ;开始读取
in ax,dx
mov [si],ax ;16位端口用ax
add si,2
loop reads
;==============512字节读读取完毕=============
;假设用户程序不超过512,因为超过512还得
;写一堆代码,可读性太差了
;==============开始段的重定位,此时dx已经等于0x1000
mov si,0x06 ;开始回填main_segment,注意
mov ax,[si] ;该段必须是16字节对齐【断点7c57】
mov bx,[si+2]
shr ax,4
ror bx,4
and bx,0xF000
or ax,bx
push ds ;将ds的值通过栈操作给bx
pop bx
add ax,bx
mov [si],ax
mov ax,[0x0A] ;开始回填table_start至table_end
mov cx,ax ;注意回填的段必须16字节对齐
mov si,0x0C
replace_sec: ;循环回填
mov ax,[si]
mov bx,[si+2]
shr ax,4
ror bx,4
and bx,0xF000
or ax,bx
push ds ;将ds的值通过栈操作给bx
pop bx
add ax,bx
mov [si],ax
add si,4
loop replace_sec
;============重定位完毕,将cs/ip指向用户程序入口处
jmp far [0x04] ;16位直接远转移【断点7c96】
foreach:
hlt
jmp foreach
memory_index: ;范围是10000-9FFFF
dd 0x10000 ;最低位必须是0,因为必须要16字节对齐
times 510-($-$$) db 0
dw 0xAA55