硬盘和显卡的访问与控制(二)——《x86汇编语言:从实模式到保护模式》读书笔记02 (5)

上面这段代码是为了把剩余的用户程序读到内存里(以扇区为单位)
我们分别讲解。

;以下判断整个程序有多大 mov dx,[2] mov ax,[0] mov bx,512 ;512字节每扇区 div bx cmp dx,0 jnz @1 ;未除尽,因此结果比实际扇区数少1 dec ax ;已经读了一个扇区,扇区总数减1 @1: cmp ax,0 ;考虑实际长度小于等于512个字节的情况 jz direct

因为已经约定了用户程序的头部4个字节是用户程序的总长度,所以这里取总长度到[dx:ax]中,把[dx:ax]除以512,就能得到有几个扇区。dx存放余数,ax存放商。
如果dx==0,那么就把ax减一(因为前面已经读了一个扇区),继续执行@1;如果dx!=0,那么剩余的扇区数就是ax,然后跳到@1;
开始执行@1处的代码时,ax已经保存了还要读取的扇区数,但是这个值也有可能为0,如果为0,就不用再读取了, jz direct就可以;如果不为0,就执行下面的代码。
好了,如果你觉得上面说得不够清楚,那么看这个简单的流程图吧:

是否继续读扇区流程图

;读取剩余的扇区 push 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 ;恢复数据段基址到用户程序头部段

mov ax,ds
add ax,0x20
mov ds,ax ;这三行表示调整ds的位置,让ds指向最后读入的块的末尾,也就是将要读入的块的开始。其他语句都好理解,这里就不解释了。
接下来是处理段的重定位表。我们要修正每个表项的值。
为什么要修正呢?看图就明白了。

修正段地址

用户程序在编译的时候,每个段的段地址都是相对于程序开头(0)计算的。但是用户程序被加载器加到到物理地址[phy_base]的时候,相当于每个段的物理地址都向后偏移了[phy_base],所以我们要修正这个差值。
我们看看代码:

calc_segment_base: ;计算16位段地址 ;输入:DX:AX=32位物理地址 ;返回:AX=16位段基地址 push dx add ax,[cs:phy_base] adc dx,[cs:phy_base+0x02] shr ax,4 ror dx,4 and dx,0xf000 or ax,dx pop dx ret

add ax,[cs:phy_base]
adc dx,[cs:phy_base+0x02];
这两句其实是做了一个20位数的加法,修正后的物理地址是[dx:ax];
shr ax,4
ror dx,4
and dx,0xf000
or ax,dx;
这四句是求出段基地址(16位),也就是逻辑段地址,结果在AX中。然后回填到原处(仅覆盖低16位,高16位不用管)。
为什么要求出段基地址呢?因为在用户程序中,对段寄存器赋值,都是从这里引用的。

;计算入口点代码段基址 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] ;转移到用户程序

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwsszg.html