上回为大家简单介绍了 Visual C++ Inline Assembly,相信已经有人想实际动手来试试了。然而,要想自由使用 Inline Assembly,你首先必须掌握 INTEL X86 体系的 32 位汇编语言。本文正是为那些已经略有 8086 汇编语言基础却没接触过 X86 体系的 32 位汇编语言的同志们准备的。我们将一起了解和深入 INTEL X86 体系的 32 位汇编语言。
因为我们的目标是“速成”,如果你能有点基础的话,那么在此之上展开讨论就能让彼此都感觉轻松很多。假若你以前完全没有学习过汇编语言,那么请务必先去找本 8086 汇编语言的教科书来补习补习之后再来阅读本文。
学习一种的汇编语言,必须了解这种 CPU 的寄存器、寻址方式以及各种指令。我们就先从寄存器开始着手吧。
g INTEL X86 常用寄存器
通用寄存器 段寄存器
AH/AL AX (EAX) 累加器 CS 代码段 BH/BL BX (EBX) 基址 DS 数据段 CH/CL CX (ECX) 计数器 SS 堆栈段 DH/DL DX (EDX) 数据 ES 附加段 (FS) 386 新增的段寄存器 (Exx) 为 386 新增的 32 位寄存器 (GS) 386 新增的段寄存器
指针寄存器 堆栈寄存器
SI (ESI) 源索引指针 SP (ESP) 栈指针 DI (EDI) 目的索引指针 BP (EBP) 基址指针 IP 指令指针
状态寄存器
|11|10|F|E|D|C|B|A|9|8|7|6|5|4|3|2|1|0| | | | | | | | | | | | | | | | | | +--- CF Carry Flag | | | | | | | | | | | | | | | | +--- 1 | | | | | | | | | | | | | | | +--- PF Parity Flag | | | | | | | | | | | | | | +--- 0 | | | | | | | | | | | | | +--- AF Auxiliary Flag | | | | | | | | | | | | +--- 0 | | | | | | | | | | | +--- ZF Zero Flag | | | | | | | | | | +--- SF Sign Flag | | | | | | | | | +--- TF Trap Flag (Single Step) | | | | | | | | +--- IF Interrupt Flag | | | | | | | +--- DF Direction Flag | | | | | | +--- OF Overflow flag | | | | +----- IOPL I/O Privilege Level (286+ only) | | | +----- NT Nested Task Flag (286+ only) | | +----- 0 | +----- RF Resume Flag (386+ only) +------ VM Virtual Mode Flag (386+ only)
怎么样,看起来大半部分都应该是我们以前很熟的了吧。现在,我们只需要侃侃那些在 386 上才 开始出现的新的寄存器就行了。
首先必须强调的是,在用 32 位汇编语言编程的时候,所有的地址偏移量都是 32 位的,在寻址时 千万不要还用原来的 16 位方式。
对于通用寄存器来说,多了种形如 (Exx) 的 32 位寄存器,它的低 16 位内容就是原来的 16 位寄 存器,而多出的高 16 位的内容,则只能通过使用 32 位寄存器来访问。
再以指针寄存器为例,在寻址时一定要用 ESI、EDI、EBP 等等,必须要把以前那种 mov ax,[si] 之 类的指令改为 mov ax,[ESI]。
从 386 开始,多出了 FS、GS 这两个新的段寄存器。由于我们学习的目的是为了今后写在线汇编,所以很多繁琐的问题都不会直接遇到。为了能更快地投入实际运用,这里就不打算去讲述保护模式的细节了,而直接给大家提出一个结论,你只管按照这个结论去做就行了。
这个简单的结论就是:你在 VC 中写在线汇编时,尽量不要去碰段寄存器!
得出这个结论的第一个原因是 VC 生成的应用程序的 DS、ES 和 SS 是相同的。换种说法,整个应用程序的数据段、附加段、堆栈段都在同一个地址,你根本就没有去改变它们的必要。第二个原因是,因为是保护模式,每个段都有 4GB 大小,所有数据都可以轻易地放进去,于是当然就用不着去改段寄存器了。最后一个原因是,保护模式下的段寄存器使用方法同实模式下完全不一样,在你没弄懂以前,最好别去乱改,否则……嘿嘿,死掉了别怨我。
至于状态寄存器,大家肯定是非常熟悉了,虽然多了几位,但这些内容我们一般都用不着,所以也可以略过。
下面将开始讲述 INTEL X86 的 32 位偏移地址构成方式。
这里给出关于 80386 寻址模式的一张列表:
基地址 + (变址 X 比例因子) + 偏移量
|无 | |无 | |EAX| |EAX| |EBX| |EBX| |1| |ECX| |ECX| |2| | 无 | |EDX| + |EDX| X |4| + | 8位| |ESP| |---| |8| |32位| |EBP| |EBP| |ESI| |ESI| |EDI| |EDI|
其中,“---”表示 ESP 不能被用作变址寄存器
注意到比例因子,这个可是从 386 才开始加入的好东西,它对处理表结构大有好处,而且还衍生出了不少技巧,详情请参考《图形程序开发人员指南 (Michael Abrash's Graphics Programming Black Book Special Edition)》一书第 6.3 节。
在 80386 寻址时,其默认的段寄存器取决于所选择的基地址寄存器。如果基地址寄存器是 ESP 或 者 EBP,则默认的段寄存器是 SS。对于别的基地址寄存器的选择,包括无基地址寄存器,DS 仍然是其默认的段寄存器。其实,前面已经说了,你在 VC 中写在线汇编时,可以不去碰段寄存器,但反正已经讲到这里了,所以随便就提两句。
其对应关系见下表:
基地址寄存器 默认的段寄存器
BP or SP SS SI or DI DS DI strings ES SI strings DS
了解了寄存器和寻址方式以后,我们就可以使用过去已经知道的指令开始编程了。但 INTEL 在 386 以后新增了不少指令,虽然这里我不可能全部列出来,但可以稍微看看其中比较常用的几条。
BSWAP - 字节交换 (486+) 颠倒 32 位寄存器的字节排列顺序。 CDQ - 双字转换成四字 (386+) 把 EAX 中的符号数按符号扩展为 EDX:EAX,即把 EAX 中的第 32 位赋予 EDX 的每一位。 CWDE - 将字扩展转换成双字 (386+) 把 AX 中的符号数按符号扩展为 EAX,即把 AX 中的第 16 位赋予 EAX 的高 16 位。 MOVSX - 符号扩展传送 (386+) 传送数据时先作符号扩展。 MOVZX - 零扩展传送 (386+) 传送数据时先作零扩展。 SHLD/SHRD - 双精度移位 (386+) 把源操作数中的若干位移入目的操作数,源操作数中的内容保持不变。 POPA/POPAD - 所有通用寄存器出栈 (80188+) PUSHA/PUSHAD - 所有通用寄存器入栈 (80188+)
怎么样?感觉这些指令挺爽的吧,其实还有很多的好东西(比如各种位处理指令等等)没能在这里提到。更完整和详细的资料请查阅『INTEL 汇编指令集』。
到此为止,该了解的基础知识我们差不多都已经全部提到了,很简单不是?剩下的就是该你自己去多多编程实践了。只有这样,你才能获取宝贵的经验,从而真正地掌握这门汇编语言。
|