鱼C论坛

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

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

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

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

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

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

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

例程理解学习


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

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

direct:  ; 第36行
;不管是由第25行跳转而来,还是从第35行顺序执行而来,此时用户程序都已经全部加载完成。

mov dx,[0x08]  ; 第37行
mov ax,[0x06]  ; 第38行
;此时DS值为0x1000。
;用户程序头部情况如下图:图中红线部分为用户程序入口所在段的32位段地址。
文中图片016.jpg
;上图中DS:[06]存储用户程序入口所在段的低字,DS:[08] 存储用户程序入口所在段的高字
;程序第37行、第38行,分别将32位段地址的高字给寄存器DX,低字给寄存器AX。

call calc_segment_base  ;第39行
;经过调用过程“calc_segment_base”后,我们已经得到用户程序入口点所在段的段地址。
;这个段地址的值保存在AX中。

mov [0x06],ax  ;第40行
;将该段地址回填修正后的入口点代码段基址,此时它的值位于DS:[06]开始的一个字中。

;下面开始处理段重定位表

mov cx,[0x0a]  ;第41行
;需要重定位的项目数量,DS:[0x0a]位置处的这个数,是在用户程序编译时就有了的。
;作为处理重定位表的循环次数。

mov bx,0x0c  ;第42行
;设置重定位表首地址,即起始偏移地址,这个值存放在用户程序头部0x0c处。
         
realloc:  ; 第43行
mov dx,[bx+0x02]  ;第44行
mov ax,[bx]  ; 第45行
;同入口点位置所在段段地址处理方法一样,寄存器此时的值为“0x0C”,由其开始的双字存储着第一
;个段的32位物理地址,将其按DX:AX分别存放好。

call calc_segment_base  ;第46行
;调用过程“calc_segment_base”得到第一个段的16位段地址。放在寄存器AX中。
mov [bx],ax  ;第47行
;将寄存器AX中的16位段地址回填到偏移地址BX的地方,即[0x0C]处。
add bx,4  ;第48行
;因每个表项占4个字节,因此寄存器BX值加4,移动到下一个重定位项。

loop realloc  ;第49行
;跳转到标号“realloc”处,设置下一个表项。不断循环知道所有的表项都被重新设定完成。
      
jmp far [0x04]  ;第50行                 
;转移到用户程序。
;“jmp far [0x04]”执行时,将DS:[0x04] 所在处的字-入口点的偏移地址-给IP,将DS:[0x06]所在处
;的字-入口点所在段地址值-给CS,这样程序将跳转用户程序入口点处开始执行。

read_hard_disk_0:  ;第51行                     
;【从第15行处跳转过来】
;从硬盘读取一个逻辑扇区,DI:SI=起始逻辑扇区号,DI:SI=起始逻辑扇区号。

push ax  ; 第52行
push bx  ; 第53行
push cx  ; 第54行
push dx  ; 第55行
;第52行~第55行,保存过程中需要用到的寄存器,过程执行完毕后予以恢复,这样就不会破坏到原
;来寄存器的值。执行第52行前SS:SP=0000:FFFC,执行“push ax”后,SS:SP=0000:FFFA;执行“push
; bx”后,SS:SP=0000:FFF8;执行“push cx”后,SS:SP=0000:FFF6;执行“push dx”后,SS:SP=0000:FFF4。
;下面开始读取硬盘的各个步骤:
mov dx,0x1f2  ; 第56行
mov al,1  ; 第57行
out dx,al  ; 第58行
;硬盘读取第一步:设置要读取的扇区数量。写入端口0x1f2,这是一个8位端口。
;硬盘的端口0x1f2是一个8位端口,因此最大写入的值为FF,代表最多读取256个扇区。
;程序第57行,al中为要读取的扇区数量,此处为1个。
;程序第58行,向端口0x1f2写入al的值1。

inc dx  ; 第59行
mov ax,si  ; 第60行
out dx,al  ; 第61行
;硬盘读取第二步(1):设置起始LBA扇区号,写0x1f3端口。
;28位扇区号二进制值为:0000 0000 0000 0000 0000 0110 0100B分别写入0x1f3~0x1f6端口(都为8位)。
;扇区号第0位~第7位写入端口0x1f3的第0位~第7位;
;扇区号第8位~第15位写入端口0x1f4的第0位~第7位;
;扇区号第16位~第23位写入端口0x1f5的第0位~第7位;
;扇区号第24位~第27位写入端口0x1f6的第0位~第3位;
;程序第59行,执行前dx为0x1f2,执行“inc dx”后,dx将变为0x1f3,即对0x1f3端口进行操作。
;程序第60行, SI=0000 0000 0110 0100B,将SI值给AX后,AX=0000 0000 0110 0100B
;程序第61行,AL=0100 0100B,执行后,将扇区号第0位~第7位“0110 0100”写入端口0x1f3。

inc dx  ; 第62行
mov al,ah  ; 第63行
out dx,al  ; 第64行
;硬盘读取第二步(2):写0x1f4端口。
;程序第62行,执行前dx为0x1f3,执行“inc dx”后,dx将变为0x1f4,即对0x1f4端口进行操作。
;程序第62行,AH=0000 0000B,执行后,AL=0000 0000B。
;程序第63行,AL=0000 0000B,执行后,将扇区号第8位~第15位即“0000 0000”写入端口0x1f4。

inc dx  ; 第65行
mov ax,di  ; 第66行
out dx,al  ; 第67行
;硬盘读取第二步(3):写0x1f5端口。
;程序第65行,执行前dx为0x1f4,执行“inc dx”后,dx将变为0x1f5,即对0x1f5端口进行操作。
;程序第66行,DI=0000 0000 0000 0000B,执行后,AX=0000 0000 0000 0000B。仅低12位有效。
;程序第67行,AL=0000 0000B,执行后,将扇区号第16位~第23位即“0000 0000”写入端口0x1f5。
;程序第67行执行完毕之后,LBA扇区号地址仅有AH中的低4位还未被写入。

inc dx  ; 第68行
mov al,0xe0  ; 第69行
or al,ah  ; 第70行
out dx,al  ; 第71行
;硬盘读取第二步(4):写0x1f6端口。
;程序第68行,执行前dx为0x1f5,执行“inc dx”后,dx将变为0x1f6,即对0x1f6端口进行操作。
;程序第69行,“0xe0”二进制表示形式:1110 0000B,其低4位“0000”作为扇区号第24~27位。
;程序第69行执行完毕之后,寄存器AX中的内容示意图:
文中图片008.jpg
; 如下图:0x1f6端口的余下高4位中,第7位、第5位固定为“1”;第6位的值为“0”时表示CHS
;模式,为“1”表示LBA模式(本程序采用);第4位的值为“0”时表示主硬盘,为“1”时表示从
;硬盘。最高4位的值分别为:“1、1、1、0”,表示LBA模式从主硬盘中读取。
文中图片009.jpg
;程序第70行,此时AH二进制值为0000 0000B,低4位为扇区号第24~27位,高4位无意义。AL的
;二进制值为1110 0000B,执行“or AL,AH”后,AL中组合了扇区号的低4位值,高4位符合“LBA
;模式从主硬盘中读取”的值,即“1110”。最终AL值为“1110 0000”。

;经过上面硬盘读取第二步(1)、(2)、(3)、(4)步骤后,拟读取的起始扇区号已经输入相关端口。


inc dx  ; 第72行
mov al,0x20  ; 第73行
out dx,al  ; 第74行
;硬盘读取第三步:向0x1f7端口写入0x20,请求硬盘读。
;程序第72行,执行前dx为0x1f6,执行“inc dx”后,dx将变为0x1f7,即对0x1f7端口进行操作。
;程序第74行,向0x1f7端口写入0x20,请求硬盘读操作。

.waits:  ; 第75行
      in al,dx  ; 第76行
      and al,0x88  ; 第77行
      cmp al,0x08  ; 第78行
      jnz .waits  ; 第79行
; 硬盘读取第四步:程序第75~79行,等待硬盘做好相关准备工作。
;下图表示端口0x1F7各个位的含义:
文中图片010.jpg
;也就是说,当第7位为“0”且第3位为“1”时,代表硬盘已经准备好了,可以进行后续读操作了。
;程序第76行,从端口0x1F7读一个字节,放到AL中。
;程序第77行,0x88二进制值为“1000 1000B”,执行完“and AL,0x88”后,AL中仅保留了第7位和
;第3位的值。
;程序第78行,0x08二进制值为“0000 1000B”,本行将AL值和0x08进行比较,如果AL中第7位值
;为0,第3位值为1,则“cmp”操作之后,结果应该为0,否则结果不为0。而“AL中第7位值为0,
;第3位值为1”代表硬盘已经准备好了,可以进行读取操作了。
;程序第79行,“jnz .waits”对“cmp”执行后的结果进行判断,如果结果不为0,则符合跳转条件,程
;序跳转到“.waits”处继续循环执行,如果结果为0,则不符合跳转条件,程序往下执行,即开始读取。

mov cx,256  ; 第80行
mov dx,0x1f0  ; 第81行
.readw:  ; 第82行
          in ax,dx  ; 第83行
          mov [bx],ax  ; 第84行
          add bx,2  ; 第85行
          loop .readw  ; 第86行
;硬盘读取第五步:连续取出数据。
;一个扇区512字节,每次读取两个字节,因此需要循环256次取出数据。
;程序第80行将循环次数给CX。
;程序第81行,将“0x1f0”给DX,准备对“端口0x1f0”进行操作。
;程序第83行、第84行,从“端口0x1f0”取出一个字,放入地址DS:BX处。
;程序第85行,BX值加2,指向下一个要存入的地址。准备进行下一次取数据操作。
;程序第86行,CX未到1时,循环继续进行,当循环进行256次后,刚好取完一个扇区的程序长度。

;执行完上述循环后,过程“read_hard_disk_0”刚好加载完用户程序的第一个扇区。

          pop dx  ; 第87行
          pop cx  ; 第88行
          pop bx  ; 第89行
          pop ax  ; 第90行
;程序第87~90行,按压栈顺序反向依次恢复调用过程“read_hard_disk_0”前的各寄存器的值。
;恢复现场。
          ret  ; 第91行
;返回调用过程后的第一条语句。
;“ret”句将栈中SS:SP指向的值赋给IP,然后跳转到CS:IP位置处执行。CS:IP指向程序第16行。
;【跳转到第16行处执行】。


;【从第39行或第46行跳转过来】
;计算16位段地址
;输入:DX:AX=32位物理地址
;返回:AX=16位段基地址

calc_segment_base:  ; 第92行

push dx  ; 第93行
;保存寄存器DX内容,此时DX为用户程序入口所在段32位段地址的高字。

    add ax,[cs:phy_base]  ; 第94行
    adc dx,[cs:phy_base+0x02]  ; 第95行
;此时DX:AX的值是编译过程中形成的,因此其数值是相对于用户程序从物理地址0x00000处的值。
;但经过加载器加载完用户程序后,用户程序放在了物理地址0x10000开始的地方。
;因此,程序第94行、第95行,分别将用户程序编译后的物理地址用带进位的加法,加上加载器加载
;到的物理地址位置,执行完第95行之后,就得到了用户程序入口点代码段的起始物理地址。该起始
;物理地址的最低4位肯定也是0。这是因为它肯定可以作为段地址,前面讲到了作为段地址的起始物
;理地址的特点就是最低4位为0。同时它虽然是32位的,但是理论上它只有其中的低20位有效。其
;中低16位放在寄存器AX中,高4位在寄存器DX的低4位。

    shr ax,4  ;第96行
;执行完第96行后,AX的低4位已经移出,高4位空置为“0”。下面只需要将寄存器DX的低4位移
;到寄存器AX的高4位,就可以得到我们所需要的段地址。

    ror dx,4  ;第97行
;执行完第97行后,原本寄存器DX的低4位,此时已经移到DX的高4位。
    and dx,0xf000  ;第98行
;“0xF000”的二进制为:1111 0000 0000 0000B,寄存器DX中的内容与其进行“与”运算后,仅保
;留了未执行前寄存器DX中的高4位,其余位全部置0

    or ax,dx  ;第98行
;将寄存器AX值与寄存器DX值进行“或”运算,就得到了我们想要的16位段地址的值。

    pop dx  ;第99行
;恢复寄存器DX的值

    ret  ;第100行
;程序返回调用前的下一句继续执行。
;【此处跳转到第40行或第47行】

phy_base dd 0x10000  ;第101行
;用户程序被加载的物理起始地址,程序员设计的加载位置,也可以是另外的数据,只需要相应改动。
         
times 510-($-$$) db 0  ;第102行
;用“0”填充主引导程序不足510字节的其余部分。

              db 0x55,0xaa  ;第103行
;最后两个字节,用0x55、0xAA填充,表明这是一个正常的主引导程序。

本帖被以下淘专辑推荐:

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-11-25 00:50

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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