鱼C论坛

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

[学习笔记] X86汇编语言-从实模式到保护模式—笔记(31)-第13章 程序的动态加载和执行(9)

[复制链接]
发表于 2017-12-5 11:55:26 | 显示全部楼层 |阅读模式

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

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

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

13.4  用户程序的加载和重定位

一、加载用户程序前显示友好信息

为了表示程序的友好性、交互性,在用户程序加载之前,我们先显示一段信息,意思就是要加载用户程序了。第567、568行,处理器将调用公用例程段的过程put_string显示标号message_5处的字符信息。

二、加载用户程序

(一)调用过程load_relocate_program

信息显示完了后,下面就要正式开始加载用户程序。第569行,将用户程序在硬盘上的存储位置即扇区号50给寄存器ESI。第570行,调用一个过程load_relocate_program,从指令中我们看出,这时一个段内调用,该过程在内核当前的代码段内,在程序的第387行。我们来到第387行,该过程有解释:功能是加载并重定位用户程序;参数是ESI,用户程序在硬盘上的起始逻辑扇区号;返回值是ax,它指向用户程序头部的选择子。

(二)进入过程load_relocate_program,并做相关前期工作

第390行~第397行,压栈保存在过程内部要用到的通用寄存器和段寄存器;第399、400行,切换DS到内核数据段,此前DS也是指向内核数据段的,为什么这里还要这样做,因为我们的这个过程有可能被很多代码调用,而不仅仅是第570行那里调用,因为那些代码执行的时候DS是否指向内核数据段我们不得而知,所以这里为了确保我们的过程能够正常运行,我们必须在这里切换DS指向内核数据段。

(三)加载用户程序的第一个扇区

加载用户程序的过程是read_hard_disk_0,它位于公用例程段内第144行,其功能与主引导程序的同名过程是一样的,即读取硬盘上指定的一个扇区。它需要的参数是:1、EAX,需要读取的硬盘的逻辑扇区号;2、DS:EBX,加载到内存的目标地址。它的返回值是:EBX=EBX+512,在传入参数的基础上加512,指向下一个要加载到内存的目标地址。该过程具体的实现过程已经重温多次,不再重复。第402行,将ESI的值(ESI在调用load_relocate_program前已经作为参数给值50)给EAX,EAX将作为过程read_hard_disk_0的参数。第403行,将标号core_buf的值给EBX,DS:EBX将作为过程read_hard_disk_0的另一个参数,它是用户程序准备加载到内存的目标地址,该地址位于内核数据段中,是在第376行声明的一个2048字节长的缓冲区。因为在内核中开辟出一段固定的空间,对于分析、加工和中转数据都比较方便。第404行,调用内核公用例程段的read_hard_disk_0过程读取用户程序的第一个扇区到缓冲区。

(四)计算用户程序实际占用的扇区数量

用户程序必须符合规定的格式,才能被内核程序识别和加载。通常情况下,流行的操作系统会规定自己的可执行文件格式,一般都比较复杂,这种复杂性和操作系统自身的复杂性是息息相关的。

1、首先分析用户程序头部的相关数据

所有操作系统的可执行文件都包括文件头,这里也不例外。事实上,这也是我们熟悉的、一贯的做法。用户程序头部在用户程序的第7行~第38行。

(1)偏移0x00,双字长,用户程序总长度,字节为单位。

(2)偏移0x04,双字长,用户程序头部长度,字节为单位。

(3)偏移0x08,双字长,为栈保留,用于接收堆栈段选择子。和早先的做法不同,内核不要求用户程序提供栈空间,而改由内核动态分配,以减轻用户程序编写的负担。当内核分配了栈空间后,会把栈段的选择子填写到这里,用户程序开始执行时,可以从这里取得该选择子以初始化自己的栈。

(4)偏移0x0C,双字长,供用户程序使用,编写者建议的栈大小,以4KB为单位。如果是1,就是希望分配4KB的栈空间;如果是2,就是希望分配8KB的栈空间,依次类推。

(5)偏移0x10,双字长,用户程序入口点的32位偏移地址。

(6)偏移0x14,双字长,用户程序代码段的起始汇编地址。当内核完成对用户程序的加载和重定位后,将把该段的选择子回填到这里(仅占用低字部分)。这样一来,它和0x10处的双字一起,共同组成一个6字节的入口点,内核从这里转移控制到用户程序。

(7)偏移0x18,双字长,用户程序代码段的长度,字节为单位。

(8)偏移0x1C,双字长,用户程序数据段起始汇编地址,当内核完成用户程序的加载和重定位后,将把该段的选择子回填到这里(仅占用低字部分)。

(9)偏移0x20,双字长,用户程序数据段长度,字节为单位。

2、根据读取到的用户程序长度数据确定实际需要的扇区数量

用户程序的长度数据在其头部偏移0x00处的一个双字内,前面我们把用户程序的第一个扇区长度的数据加载到了目标缓冲区,该缓冲区由标号core_buf指向。也就是说,现在用户程序的第一个扇区起始地址就是标号core_buf所在的位置。第407行,把core_buf开始的偏移量为0x00的位置处的双字传送给寄存器EAX,这个位置处的数据就是用户程序的长度,因此,实际上就是把用户程序的长度数据给了寄存器EAX。

用户程序的大小(总字节数)不一定恰好是512的整数倍。也就是说,最后一个扇区未必是满的。因此,如果直接除以512,可能会使结果(除法的商)比实际的扇区数少1。通常情况下,需要判断除法的余数,根据余数是否为零,来决定实际的扇区总数,这不可避免地要使用判断和条件转移指令。

早先的处理器中,转移指令是影响处理器速度的重大因素之一,因为它会使流水线中那些已经预取和译码的指令失效。在较晚的处理器中,虽然普遍采用了分支预测技术,但并不总能保证预测是准的。因此,为了减少转移指令对处理器的影响,最好的办法就是尽量不使用转移指令。为了帮助程序员部分地戒掉使用转移指令的欲望,处理器引入了条件传送指令cmovcc

cmovcc指令是从P6处理器开始引入的,因为并非所有处理器都支持它。要检测处理器是否支持cmovcc指令,可以以1号功能执行cpuid指令(mov eax,1; cpuid)当处理器执行这两条指令后,会在EBX、ECX和EDX寄存器返回丰富的信息,以指示各种详尽的处理器特性。此时,检查EDX寄存器的第16位(位15),当它是“1”时,表明处理器支持cmovcc指令。comvcc指令是条件转移指令和传送指令相结合的产物,既有条件转移指令的多样性,又执行的是传送操作。但是,和mov指令不同的是,它的目的操作数只允许是16位或者32位通用寄存器,源操作数只能是相同宽度的通用寄存器和内存单元。条件传送指令支持所有的条件转移指令的条件。只需要相应的把条件转移指令的“j”换成“cmov”即可。该指令不影响EFLAGS寄存器中的任何标志位。相反地,它的执行过程要依赖于这些标志,就像条件转移指令一样。

回到程序,关于一个数能否被512整除,我们可以找到一个规律,所有能被512整除的数,其最低端的9个比特都是“0”。掌握了这个规律,我们很多事情就好做了。第408行,将EAX值传送给EBX,等于是做个备份,因为还要用到。第409、410行,(先假设了EBX不能被512整除)先用and指令将长度数据的最低9个比特清零,等于是去掉那些不足512的零头,然后,再将其加上512,等于是将那些零头凑整。这样EBX中就是实际占用的整数个扇区长度数值。但是,如果人家原本就是512的整数倍,这样做无疑是多加了一个扇区。因此,第411行,先测试EAX(同样保存着用户程序长度数据)寄存器的最低9个比特,如果测试的结果是它们不全为零,则将EBX中内容传送至EAX,采用凑整的结果;如果为全零,则cmovnz指令什么也不做,依然采用用户程序原本的长度。

本帖被以下淘专辑推荐:

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-12-24 01:54

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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