<x86>第八章的内容大概是编写一个加载器,从磁盘加载前面写的程序,然后跳转到该程序中执行并输出hello world。咱不急,不跨这么大步子,先写个程序从磁盘上读数据吧~
我把要读取的数据存放在磁盘第二个扇区,格式如下:前2个字节记录需要读取的数据长度,以字节计算,后面紧跟实际数据。
关于IDE接口寄存器的介绍,这里转一个连接,因为网上都转自博主的文章:http://blog.chinaunix.net/uid-20235103-id-1970841.html
DiskDataReg equ 0x01f0
DiskErrReg equ 0x01f1
DiskSectCntReg equ 0x01f2
DiskLoLBAAddr equ 0x01f3
DiskMeLBAAddr equ 0x01f4
DiskHiLBAAddr equ 0x01f5
DiskModReg equ 0x01f6
DiskCmdStatReg equ 0x01f7
DiskReadCmd equ 0x20
DiskWriteCmd equ 0x30
Arg1Off equ 0x04 ;第一个参数相对bp偏移
Arg2Off equ 0x06 ;第二个参数相对bp偏移
Arg3Off equ 0x08 ;第三个参数相对bp偏移
Arg4Off equ 0x0A ;第四个参数相对bp偏移
StackBase equ 0x0000
StackEnd equ 0x2000
DataBase equ 0x0300
DataEnd equ 0x1FFF
section code align=16 vstart=0x7c00
jmp entry
SetSectAddr:
;设置逻辑扇区的地址
push bp
mov bp,sp
push bx
;发扇区总数
mov dx,DiskSectCntReg
mov al,byte [bp+Arg4Off]
out dx,al
;发扇区地址
mov dx,DiskLoLBAAddr
mov al,byte [bp+Arg3Off]
out dx,al
mov dx,DiskMeLBAAddr
mov al,byte [bp+Arg3Off+1]
out dx,al
mov dx,DiskHiLBAAddr
mov al,byte [bp+Arg2Off]
out dx,al
mov dx,DiskModReg
mov al,byte [bp+Arg2Off+1]
out dx,al
pop bx
mov sp,bp
pop bp
ret
WaitDiskReady:
;等待磁盘就绪
mov dx,DiskCmdStatReg
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits
;判断是否有错误
mov dx,DiskErrReg
.getErr:
in al,dx
cmp al,0x00
;al不为0出错
jnz .resume
;没有出错 返回0
xor ax,ax
ret
.resume:
;出错 返回
mov ax,0x01
ret
ReadFromDisk:
call SetSectAddr
;发读命令
mov dx,DiskCmdStatReg
mov al,DiskReadCmd
out dx,al
call WaitDiskReady
;读取保存在扇区上的数据的有效长度
mov dx,DiskDataReg
in ax,dx
shr ax,0x01
;比较要读取数据的长度和有效数据的长度,取最短的
;磁盘上保存的数据,长度按字节计数,但是DiskDataReg
;端口是16位端口,每次读取1字,因此循环次数需要除2
mov cx,ax
mov dx,DiskDataReg
.readw:
in ax,dx
mov [bx],ax
add bx,2
loop .readw
;代码没写好 没返回地址 只能自己构造返回地址了
lea ax,[RetFromReadDisk]
push ax
ret
entry:
xor ax,ax
mov ax,DataBase
mov ds,ax
mov ax,StackBase
mov ss,ax
mov sp,StackEnd
;读取扇区总数
push word 0x0001
;LBA地址的0-15位
push word 0x0001
;LBA地址的16-31位
push word 0xe000
;需要从扇区上读取的最大字节数
push word 0x0200
jmp ReadFromDisk
RetFromReadDisk:
jmp $
times 510-($-$$) db 0
db 0x55,0xaa
len dw 0x000c ;后面有效数据的长度
string db 'Hello world',0
程序第134行,定义0x55 0xaa使得产生的bin文件正好撑满一个扇区,后面在保留一些数据,那就挤到第二个扇区去了。
在我没想出其他保存数据到磁盘任意位置前就先这么凑合吧
本来想完成写磁盘的程序,没成功,所以代码有点不伦不类,比如130行,本可以用call调用子过程的,现在用了jmp代替。虽然完成了过程调用,但是没有返回地址,怎么回来呢?
call指令会把下一条指令的地址压入堆栈,即sp指向的内存存入返回地址,然后sp=sp-2,然后跳转到目标地址执行。
当程序返回时,ret指令从堆栈中取出sp指向的地址,然后使sp=sp+2,返回。
现在,jmp到目标地址执行,堆栈中没有返回地址,如果直接调用ret,此时堆栈顶部的数值就是返回地址,至于是否合法,执行完了才知道。如果往堆栈中压入一个地址,ret执行时就会跳到这个指定的地址,怎么指定这个地址?用lea~取标号的内存地址,push进堆栈,程序110、111行的目的就是如此,最终返回到131行RetFromReadDisk标号处。
一些简单的缓冲区溢出程序也是这样构造目标地址进行跳转的~