鱼C论坛

 找回密码
 立即注册
查看: 2611|回复: 0

[学习笔记] X86汇编语言-从实模式到保护模式—笔记(8)

[复制链接]
发表于 2017-11-2 20:59:53 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 兰陵月 于 2017-12-5 21:55 编辑

第8章  硬盘和显卡的访问与控制

例程理解学习


接-X86汇编语言-从实模式到保护模式—学习笔记(七)

;以下读取程序的起始部分,即第一个扇区,为什么先读程序的起始部分,因为用户程序的头部有该程
;序的关键信息,特别是程序的长度数据,这样加载程序就知道要读取多少数据才能正确完整地加载用
;户程序了。

;有关硬盘读取的相关资料:硬盘是块设备,每次读取都是一个扇区一个扇区读取的。
;硬盘读取通过相关端口进行,端口有8个,分别是0x1f0~0x1f7。
;第一步:设置要读取的扇区数量。写入端口0x1f2,这是一个8位端口。
;第二部:设置起始LBA扇区号。将28位扇区号分别写入端口0x1f3~0x1f6,端口0x1f6高4位为1110。
;第三部:请求硬盘读。向端口0x1f7写入0x20。
;第四步:等待读写操作完成。不停检测端口0x1f7的第7位、第3位,值为0、1表示准备好了。
;第五步:连续取出数据。从端口0x1f0(16位)取出数据,分256次,每次1个字。

xor di,di  ; 第12行
mov si,app_lba_start  ; 第13行
;在硬盘LBA模式中,用28个比特位来表示逻辑扇区号。
;逻辑第100扇区二进制值为:0000 0000 0000 0000 0000 0110 0100,程序第13行的寄存器SI为16位,
;不能完整地存下其整个数值,因此余下的12位(28位减去SI中的低16位)放在寄存器DI的低12
;位中。(此处DI中最高4位的值为0,但并无用处)
;下图为LBA形式的逻辑扇区编号在寄存器DI、SI的存储示意图

文中图片006.jpg

xor bx,bx  ; 第14行
;初始化bx的值为0,表示下面要将用户程序加载到目标缓冲区DS:0x0000处开始的地方。
;bx使用的地方在过程“read_hard_disk_0”里。
;下图红框里就是用户程序将加载到的目标位置。

文中图片007.jpg

call read_hard_disk_0  ; 第15行
;调用过程“read_hard_disk_0”,读取第1个扇区。
;执行这句的时:
;第1步,处理器将当前SP值减去2,再将当前CS值压入栈中SS:SP指向的地方-0000:FFFE;
;第2步,处理器再将当前SP值减去2,再将当前IP值压入栈中SS:SP指向的地方-0000:FFFC;
;第3步,跳转到标号“read_hard_disk_0”处开始执行。
;压入的CS和IP指向下面的第16行的语句。
;【跳转到第51行处】
      
;【由第91行跳转来此处】
;以下判断整个程序有多大
mov dx,[2]  ; 第16行
mov ax,[0]  ; 第17行
;“[0]”、“[2]”的意思是DS:[0]、DS:[2],此时DS的值为0x1000。见程序第10行、第11行注释。
;我们知道经过过程“read_hard_disk_0”之后,我们已经将用户程序开头一个扇区长度的内容加载到了
;DS:0000处。而用户程序最开始的地方-header段-定义了一个双字 “program_length”,如下图。
文中图片011.jpg
;用户程序加载后,该标号“program_length”所处的位置为DS:0000开始的4个字节。放置的内容为
;“program_end”,而标号“program_end”编译后的数值实际上等于该用户程序的长度(以字节计算)。
;根据低端字节序存放方式,我们可以得知,DS:[0]处存放着用户程序长度的低字,DS:[2]处存放着用
;户程序长度的高字。因此,程序第16行将用户程序长度数值的高字放入寄存器DX中,程序第17行
;将用户程序长度数值的低字放入寄存器AX中。

;得到了用户程序长度的数值后,我们就可以判断经过一个扇区的加载后,程序是已经加载完?如果加
;载完成了,那我们就接下来重定位用户程序。如果没有加载完,就计算还要加载多少个扇区。

mov bx,512  ;第18行
div bx  ;第19行
;现在程序的总长度(以字节计算)储存在DX:AX中。因为硬盘读取每次是一个一个扇区读取的,而
;一个扇区长度是512字节,因此在知道用户程序长度(以字节计算)之后,要知道一个程序要通过多
;少个扇区读取才能加载完成,我们可以将用户程序长度整除512,除法之后的商就是整个用户程序的
;扇区数。
;程序第18行将除数512给了寄存器BX。程序第19行进行除法操作。
;程序第19行执行完毕之后,商放在AX中,余数放在DX中。

;得到商和余数之后,我们要考虑以下几个可能的情况:
;【情况1】余数是否为0的状况。代表用户程序长度是否正好是512的倍数。如果余数DX=0,则说
;明程序长度刚好能够被整除。如果余数DX不等于0,则说明用户程序长度不能被512整除。
;【情况2】根据【情况1】,如果DX=0,则说明用户程序长度能够被512整除,这时整个用户程序长
;度扇区数目为商的值(即AX的值,这个值肯定>=1),而此时已经加载了一个扇区,因此还需要加
;载的扇区数目为AX的值减去1。这个时候我们再判断AX-1的值,如果AX-1的值为0,则说明
;还要加载0个扇区(意思就是用户程序刚好一个扇区大,经过第一次加载,我们已经全部加载完了)。
;如果AX-1的值>=1,则说明还要加载AX-1个扇区,那我们就循环AX-1次调用过程
;“read_hard_disk_0”继续加载用户程序。
;【情况3】根据【情况1】,如果DX≠0,则说明用户程序不能被512整除,即用户程序长度不是整
;数个扇区长度。在DX≠0的情况下,我们又要考虑两种情况。一是AX是否等于0,如果AX=0,则
;说明用户程序长度不足512字节,因此时我们已经加载了一个扇区,因此整个用户程序在第一次加载
;的时候,就已经全部加载完毕了,我们可以对用户程序进行后续加工处理。二是AX≠0,则说明整
;个用户程序的长度为商+1,即AX+1。为什么是AX+1,因为用户程序长度数值整除512有余数,且
;加载硬盘数据是一个一个扇区为单位,不是以字节为单位,因此余数在加载时也是用一个扇区长度来
;加载的,所以在DX≠0,且AX≠0的情况下,整个用户程序是需要AX+1个扇区来加载。因此时我
;们已经加载一个扇区长度的内容,因此用户程序剩下的内容需要加载(AX+1)-1个扇区,即AX个
;扇区。接下来我们就循环AX次调用过程“read_hard_disk_0”继续加载用户程序。

push ds  ;第26行                        
;等下要用到并改变DS寄存器,这里对DS进行现场保存。
;在执行此句之前,DS的值位0x1000,也就是用户程序加载的位置,这个位置等下还要用到,因此这
;里必须保存,否则后续处理就用不上了,当然后面要用到DS的地方,也可以采取把ES给AX后,
;再由AX给DS的方式,但是那种做法不如这里简单,程序执行也更慢。

mov cx,ax  ;第27行
;将AX值给CX,作为循环次数,也就是循环调用过程“read_hard_disk_0”的次数。

@2:  ;此处忘记填写行号,如添加则会改动下面太多处,所以不添加了。
mov ax,ds  ;第28行
add ax,0x20  ;第29行
mov ds,ax  ;第30行
xor bx,bx  ;第31行
;用户程序被加载的位置是由DS和ES所指向的逻辑段。一个逻辑段最大也只有64KB,当用户程序大
;于64KB的时候,如果仍靠原来的DS和ES加偏移地址,则偏移地址达到FFFF时,又会绕回到0000
;的地方,把原来最初的开始加载的内容覆盖掉,就会导致发生错误。
;要解决这个问题的最好办法就是,每次往内存中加载一个扇区前,都重新在前面的数据尾部构造一个
;新的逻辑段,并把要读取的数据加载到这个新的逻辑段内,如此一来,因为每个段的大小是512字节,
;即十六进制的0x200,右移4位后(相当于除以16)为0x20,这就是新段的段地址和旧段的段地址之
;间的差值。因此新段段地址=老段段地址+0x20,新段的偏移地址仍然是从0x0000开始。
;程序第28行将老段段地址给AX。
;程序第29行、第30行,将AX值加上0x20(即新老段地址差值)后,再给DS。
;程序第31行,将用作偏移地址的寄存器BX清零,意思是偏移地址每次从0开始。
;循环每次执行到这里之后,DS的值就是新段的段地址了,偏移地址就是BX(从0开始)。

inc si  ;第32行
;第一次读取的时候逻辑扇区号是100,现在要开始读下一个逻辑扇区,因此将SI自加1。得到下一个
;将要读取的逻辑扇区的扇区号。
;这里考虑SI的值可能大于65535的情况,比如我将用户程序储存在逻辑扇区65535号的位置,即主
;引导程序中常量定义为65535,将65535即十六进制的FFFF给SI,当用户程序有2个或2个以上扇
;区长度时,读完第一个扇区之后,SI自加1,则变成0x10000,但SI只有16位,因此只保留低16位,
;所以SI的值变成了0,而硬盘上第0号扇区储存的是主引导扇区,因此读取的数据就会发生错误。
;按照上述情况进行操作后,即把主引导扇区的“app_lba_start”值改为65535,将用户程序储存在硬盘
;第65535个扇区起始处,编译后运行,结果果然发生了错误。
;如下图,将“app_lba_start”值改为65535。红线部分。
文中图片012.jpg
;如下图,将用户程序存储在硬盘的第65535号扇区开始的地方。
文中图片013.jpg
;程序运行之后,不能如原来设计一般正常运行。红色部分开始产生乱码。
文中图片014.jpg
;下图为程序正常运行图
文中图片015.jpg

call read_hard_disk_0  ;第33行
;调用过程“read_hard_disk_0”读取SI指向的扇区。

loop @2  ;第34行
;循环读CX次数,直到读完整个功能程序。

pop ds  ;第35行                        
;程序全部加载完毕,恢复数据段基址到用户程序头部段。
;push ds之前,DS的值为0x1000,此处执行完毕后,DS的值恢复为0x1000。

;程序运行到此处时,用户程序已经全部正确加载完毕。
;接下来就是要对用户程序头部进行处理,以处理器从加载程序正确地跳到用户程序处。
;下面开始计算入口点代码段基址

【未完待续,接-X86汇编语言-从实模式到保护模式—学习笔记(九)】

本帖被以下淘专辑推荐:

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-4-17 00:46

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表