|
4. 端口
端口是直接和外部设备通讯的地方。外设接入系统后,系统就会把外设的数据接口映射到特定的端口地址空间,这样,从该端口读入数据就是从外设读入数据,而向外设写入数据就是向端口写入数据。当然这一切都必须遵循外设的工作方式。端口的地址空间与内存地址空间无关,系统总共提供对64K个8位端口的访问,编号0-65535. 相邻的8位端口可以组成成一个16位端口,相邻的16位端口可以组成一个32位端口。端口输入输出由指令IN,OUT,INS和OUTS实现,具体可参考汇编语言书籍。
汇编指令的操作数可以是内存中的数据, 如何让程序从内存中正确取得所需要的数据就是对内存的寻址。
INTEL 的CPU 可以工作在两种寻址模式:实模式和保护模式。 前者已经过时,就不讲了, WINDOWS 现在是32位保护模式的系统, PE 文件就基本是运行在一个32位线性地址空间, 所以这里就只介绍32位线性空间的寻址方式。
其实线性地址的概念是很直观的, 就想象一系列字节排成一长队,第一个字节编号为0, 第二个编号位1, 。。。。 一直到4294967295(十六进制FFFFFFFF,这是32位二进制数所能表达的最大值了)。 这已经有4GB的容量! 足够容纳一个程序所有的代码和数据。 当然, 这并不表示你的机器有那么多内存。 物理内存的管理和分配是很复杂的内容, 初学者不必在意, 总之, 从程序本身的角度看, 就好象是在那么大的内存中。
在INTEL系统中, 内存地址总是由"段选择符:有效地址"的方式给出。段选择符(SELECTOR)存放在某一个段寄存器中, 有效地址则可由不同的方式给出。 段选择符通过检索段描述符确定段的起始地址, 长度(又称段限制), 粒度, 存取权限, 访问性质等。 先不用深究这些, 只要知道段选择符可以确定段的性质就行了。 一旦由选择符确定了段, 有效地址相对于段的基地址开始算。 比如由选择符1A7选择的数据段, 其基地址是400000, 把1A7 装入DS中, 就确定使用该数据段。 DS:0 就指向线性地址400000。 DS:1F5278 就指向线性地址5E5278。 我们在一般情况下, 看不到也不需要看到段的起始地址, 只需要关心在该段中的有效地址就行了。 在32位系统中, 有效地址也是由32位数字表示, 就是说, 只要有一个段就足以涵盖4GB线性地址空间, 为什么还要有不同的段选择符呢? 正如前面所说的, 这是为了对数据进行不同性质的访问。 非法的访问将产生异常中断, 而这正是保护模式的核心内容, 是构造优先级和多任务系统的基础。 这里有涉及到很多深层的东西, 初学者先可不必理会。
有效地址的计算方式是: 基址 间址*比例因子 偏移量。 这些量都是指段内的相对于段起始地址的量度, 和段的起始地址没有关系。 比如, 基址=100000, 间址=400, 比例因子=4, 偏移量=20000, 则有效地址为:
100000 400*4 20000=100000 1000 20000=121000。 对应的线性地址是400000 121000=521000。 (注意, 都是十六进制数)。
基址可以放在任何32位通用寄存器中, 间址也可以放在除ESP外的任何一个通用寄存器中。 比例因子可以是1, 2, 4 或8。 偏移量是立即数。 如: [EBP EDX*8 200]就是一个有效的有效地址表达式。 当然, 多数情况下用不着这么复杂, 间址,比例因子和偏移量不一定要出现。
内存的基本单位是字节(BYTE)。 每个字节是8个二进制位, 所以每个字节能表示的最大的数是11111111, 即十进制的255。 一般来说, 用十六进制比较方便, 因为每4个二进制位刚好等于1个十六进制位, 11111111b = 0xFF。 内存中的字节是连续存放的, 两个字节构成一个字(WORD), 两个字构成一个双字(DWORD)。 在INTEL架构中, 采用small endian格式, 即在内存中,高位字节在低位字节后面。 举例说明:十六进制数803E7D0C, 每两位是一个字节, 在内存中的形式是: 0C 7D 3E 80。 在32位寄存器中则是正常形式,如在EAX就是803E7D0C。 当我们的形式地址指向这个数的时候,实际上是指向第一个字节,即0C。 我们可以指定访问长度是字节, 字或者双字。 假设DS:[EDX]指向第一个字节0C:
mov AL, byte ptr DS:[EDX] ;把字节0C存入AL
mov AX, word ptr DS:[EDX] ;把字7D0C存入AX
mov EAX, dword ptr DS:[EDX] ;把双字803E7D0C存入EAX
在段的属性中,有一个就是缺省访问宽度。如果缺省访问宽度为双字(在32位系统中经常如此),那么要进行字节或字的访问,就必须用byte/word ptr显式地指明。
缺省段选择:如果指令中只有作为段内偏移的有效地址,而没有指明在哪一个段里的时候,有如下规则:
如果用ebp和esp作为基址或间址,则认为是在SS确定的段中;
其他情况,都认为是在DS确定的段中。
如果想打破这个规则,就必须使用段超越前缀。举例如下:
mov eax, dword ptr [edx] ;缺省使用DS,把DS:[EDX]指向的双字送入eax
mov ebx, dword ptr ES:[EDX] ;使用ES:段超越前缀,把ES:[EDX]指向的双字送入ebx
堆栈:
堆栈是一种数据结构,严格地应该叫做“栈”。“堆”是另一种类似但不同的结构。SS 和 ESP 是INTEL对栈这种数据结构的硬件支持。push/pop指令是专门针对栈结构的特定操作。SS指定一个段为栈段,ESP则指出当前的栈顶。push xxx 指令作如下操作:
把ESP的值减去4;
把xxx存入SS:[ESP]指向的内存单元。
这样,esp的值减小了4,并且SS:[ESP]指向新压入的xxx。 所以栈是“倒着长”的,从高地址向低地址方向扩展。pop yyy 指令做相反的操作,把SS:[ESP]指向的双字送到yyy指定的寄存器或内存单元,然后把esp的值加上4。这时,认为该值已被弹出,不再在栈上了,因为它虽然还暂时存在在原来的栈顶位置,但下一个push操作就会把它覆盖。因此,在栈段中地址低于esp的内存单元中的数据均被认为是未定义的。
最后,有一个要注意的事实是,汇编语言是面向机器的,指令和机器码基本上是一一对应的,所以它们的实现取决于硬件。有些看似合理的指令实际上是不存在的,比如:
mov DS:[edx], ds:[ecx] ;内存单元之间不能直接传送
mov DS, 1A7 ;段寄存器不能直接由立即数赋值
mov EIP, 3D4E7 ;不能对指令指针直接操作。
“汇编语言”作为一门语言,对应于高级语言的编译器,我们需要一个“汇编器”来把汇编语言原文件汇编成机器可执行的代码。高级的汇编器如MASM, TASM等等为我们写汇编程序提供了很多类似于高级语言的特征,比如结构化、抽象等。在这样的环境中编写的汇编程序,有很大一部分是面向汇编器的伪指令,已经类同于高级语言。现在的汇编环境已经如此高级,即使全部用汇编语言来编写windows的应用程序也是可行的,但这不是汇编语言的长处。汇编语言的长处在于编写高效且需要对机器硬件精确控制的程序。而且我想这里的人学习汇编的目的多半是为了在破解时看懂反汇编代码,很少有人真的要拿汇编语言编程序吧?(汗......)
好了,言归正传。大多数汇编语言书都是面向汇编语言编程的,我的帖是面向机器和反汇编的,希望能起到相辅相成的作用。有了前面两篇的基础,汇编语言书上对大多数指令的介绍应该能够看懂、理解了。这里再讲一讲一些常见而操作比较复杂的指令。我这里讲的都是机器的硬指令,不针对任何汇编器。
无条件转移指令jmp:
这种跳转指令有三种方式:短(short),近(near)和远(far)。短是指要跳至的目标地址与当前地址前后相差不超过128字节。近是指跳转的目标地址与当前地址在用一个段内,即CS的值不变,只改变EIP的值。远指跳到另一个代码段去执行,CS/EIP都要改变。短和近在编码上有所不同,在汇编指令中一般很少显式指定,只要写 jmp 目标地址,几乎任何汇编器都会根据目标地址的距离采用适当的编码。远转移在32位系统中很少见到,原因前面已经讲过,由于有足够的线性空间,一个程序很少需要两个代码段,就连用到的系统模块也被映射到同一个地址空间。
jmp的操作数自然是目标地址,这个指令支持直接寻址和间接寻址。间接寻址又可分为寄存器间接寻址和内存间接寻址。举例如下(32位系统):
jmp 8E347D60 ;直接寻址段内跳转
jmp EBX ;寄存器间接寻址:只能段内跳转
jmp dword ptr [EBX] ;内存间接寻址,段内跳转
jmp dword ptr [00903DEC] ;同上
jmp fward ptr [00903DF0] ;内存间接寻址,段间跳转
解释:
在32位系统中,完整目标地址由16位段选择子和32位偏移量组成。因为寄存器的宽度是32位,因此寄存器间接寻址只能给出32位偏移量,所以只能是段内近转移。在内存间接寻址时,指令后面是方括号内的有效地址,在这个地址上存放跳转的目标地址。比如,在[00903DEC]处有如下数据:7C 82 59 00 A7 01 85 65 9F 01
内存字节是连续存放的,如何确定取多少作为目标地址呢?dword ptr 指明该有效地址指明的是双字,所以取
0059827C作段内跳转。反之,fward ptr 指明后面的有效地址是指向48位完全地址,所以取19F:658501A7 做远跳转。
注意:在保护模式下,如果段间转移涉及优先级的变化,则有一系列复杂的保护检查,现在可不加理会。将来等各位功力提升以后可以自己去学习。
条件转移指令jxx:只能作段内转移,且只支持直接寻址。
=========================================
|
|