本文由zengl.com站长对
http://pan.baidu.com/share/link?shareid=3717576860&uk=940392313 汇编教程英文版相应章节进行翻译得来。
另外在附加一个因特尔英文手册的共享链接地址:
http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (在某些例子中会用到)
学习汇编语言首先要理解什么是汇编语言,不像其他的编程语言,不同的汇编程序有不同的语法格式,许多刚接触汇编的程序员就陷入了这种困境,不知道该学哪种好。
所以,学习汇编的第一步就是选择一种适合你的开发环境的汇编语言类型,一旦你确定下来了,学习汇编将不再是一件困惑的事情。
本章将先介绍汇编语言的起源,以及为什么要使用汇编语言。要理解汇编语言,首先要理解处理器指令集的概念,接着要理解高级语言是如何转为原始的处理器指令集的,在理解了这些之后,你将明白如何使用汇编,以及如何与高级语言配合使用。
从最底层的操作来看,所有的电脑处理器(包括微型计算机,小型计算机,大型计算机)处理数据都是通过厂家生产的处理器内部定义的二进制指令来完成的。这些指令定义了处理器可以执行的功能,以及如何处理程序员提供的数据,这些预先定义好的指令被称作指令集。不同类型的处理器包含不同类型的指令集,处理器芯片的类型也通常是通过它们所支持的指令集类型和数量来划分的。
尽管不同处理器可能包含不同的指令集,但是它们处理这些指令集的方式却很相似,下面将描述处理器如何处理这些指令集,以及指令集在示例芯片中的大概样子。
指令的处理:
当处理器芯片运行时,它从内存中读取指令,每条指令可以包含一个或多个字节的信息,这些信息告诉处理器去执行不同的任务,由于每条指令都是从内存中读取出来的,所以指令执行时所需要的任何数据也都是存储在内存中的,而存储指令的内存和存储数据的内存是没什么不同的,所以就需要一个方法让处理器能分辨出哪些内存中存储的是指令,哪些内存中存储的是指令操作数。
通过两种特殊的指针可以追踪内存中的指令和数据。如图1-1所示:
图1-1
图1-1中的Instruction Pointer即指令指针用于帮助处理器判断那些指令已经执行过了,以及哪条指令是下一次将要执行的。当然,还存在一些特殊的指令可以修改Instruction Pointer(指令指针)的位置,例如程序中的跳转指令。
类似的Data Pointer(数据指针)用于帮助处理器判断数据区域在内存中的起始位置。这个数据区域被称作"栈(stack)"。当有新的数据存放到栈中时,数据指针就会向下移,即从内存的高字节地址向低字节地址移动,也就是常说的"压栈"。当数据从栈中读取出来时,数据指针就会反方向往上移,即从内存的低字节地址向高字节地址移动,也就是常说的“弹出栈”。
每条指令可以包含一个或多个字节的信息以供处理器来处理,例如,下面这条指令的字节码(以十六进制格式显示):
C7 45 FC 01 00 00 00
这条指令会告诉因特尔IA-32系列的处理器去加载十进制1这个值到处理器寄存器定义的内存偏移的内存中。这条指令中包含的字节信息将在下面的Opcode操作码中做解释。另外,这条指令明确的定义了处理器将要执行的功能,为了让处理器能按顺序正确的执行程序,所有的指令都必须以适当的方式和顺序存放在内存中。
每条指令中都必须至少包含一个字节,在这个字节中存放着operation code(操作码,缩写为opcode),这个opcode操作码定义了处理器可以执行的功能,每个处理器家族都有自己预定义好的操作码,这些操作码定义了所有可用的功能。下面就介绍英特尔IA-32家族的微处理器中操作码的使用,这些操作码将用在后面的所有例子中。
处理器的指令格式:
在现代IBM微型计算机中所用到的所有微处理器的当前类型都包括在英特尔IA-32家族的微处理器系列中(在后面的章节中,被称作“IA-32平台”),流行的Pentium(奔腾)系列的处理器也包括在里面。IA-32家族的微处理器使用一种特定的指令格式,理解这种格式将有助于你编写汇编程序。这种指令格式主要由四个部分组成:
-
Optional Instruction prefix (可选的指令前缀)
-
Operational code (opcode) 前面提到过的操作码
-
Optional modifier (可选的修饰字节)
-
Optional data element (可选的数据元素)
下面的图1-2显示了这种IA-32指令格式的布局:
图1-2
每个部分都定义了指令的相关功能,下面就一一介绍这几个部分。
小提示:并不是只有奔腾的处理器家族才采用这种IA-32的指令格式,AMD公司也生成了一批完全兼容IA-32指令格式的处理器芯片。
Opcode(操作码):
由图1-2可知,IA-32指令格式中必须至少包含一个Opcode操作码部分,这个操作码定义了处理器可以执行的基本功能或任务。
操作码由1到3个字节组成,它唯一的定义了处理器可以执行的功能。例如,两个字节的操作码:"OF A2"对应IA-32的CPUID指令,当处理器执行这条指令时,它会在微处理器的不同寄存器中存放一些特定的信息,程序员就可以通过其他指令从这些寄存器中提取出信息,通过这些信息就可以检测出当前程序所运行的微处理器的类型和型号。
小提示:寄存器是处理器芯片中用于暂存数据的组件,在后面的章节"IA-32平台"中会进行详细的介绍。
Instruction Prefix(指令前缀):
指令前缀由1到4个字节组成,用以修改操作码的行为。这些前缀根据功能的不同,被划分为四个不同的组,每个组中同一时间只能有一个前缀用于修改操作码(因此指令前缀部分最多为4个字节),这四个组能以任何顺序进行前后排列。这四个组分别是(下面涉及到的一些汇编代码和CS段之类的东东将在以后进行说明,这里先留个印象,非原著里的内容,而是从英特尔手册里找到的):
-
组1:Lock(锁)和repeat(重复)前缀,在某些汇编代码中可以看到LOCK前缀,LOCK会被编码为F0H(十六进制F0),表明任何共享内存区域都将只能被当前指令使用,lock会锁FSB,前端串行总线,front serial bus,这个FSB是处理器和RAM之间的总线,锁住了它,就能阻止其他处理器或者core从RAM获取数据。当然这种操作是比较昂贵的,只能操作小的内存可以这样做,在多处理器和超线程系统中,尤其是在原子性操作时,这个前缀就很有用了。repeat(重复)前缀,主要用于在进行字符串操作时或IO指令中,可以进行一些重复性的工作,例如汇编中常见的REPNE/REPNZ(会被编码为F2H),REP,REPE,REPZ(会被编码为F3H)等汇编前缀,有了这些前缀就可以通过单一的指令用数据填充一片连续的内存等。这里提到的编码如F2H,F3H在某些指令中属于Opcode的强制前缀,在这些指令中F2H,F3H就不再具备repeat的含义了。
-
组2:Segment Override prefixs(段覆盖前缀):
-
2EH ---- CS段覆盖 (在任何分支指令如JZ之类的跳转指令中使用时将被保留)
-
36H ---- SS段覆盖前缀 (在任何分支指令中使用时将被保留)
-
3EH ---- DS段覆盖前缀 (在任何分支指令中使用时将被保留)
-
26H ---- ES段覆盖前缀 (在任何分支指令中使用时将被保留)
-
64H ---- FS段覆盖前缀 (在任何分支指令中使用时将被保留)
-
65H ---- GS段覆盖前缀 (在任何分支指令中使用时将被保留)
Branch hints(分支暗示前缀)
-
2EH ---- 不采用分支 (仅用于Jcc之类的条件转移指令,例如JNZ之类的指令)
-
3EH ---- 采用分支 (仅用于Jcc之类的条件转移指令,例如JZ之类的指令)
-
组3:Operand-size override prefix(操作数大小覆盖前缀),该前缀对应编码为66H(66H在某些指令中属于强制前缀)。该前缀允许程序在16位和32位操作数大小之间进行切换,这两种大小都有可能是当前执行环境的默认大小,通过使用此前缀,程序就能选择非默认的另一种大小。
-
组4:Address-size override prefix(地址大小覆盖前缀),对应编码:67H,该前缀允许程序在16位和32位寻址中切换。每种大小都可能是默认大小,通过使用此前缀,程序就能选择非默认的另一种大小来寻址。
Modifiers(修饰字节):
一些Opcode(操作码)需要某些额外的修饰字节来确定指令中的操作数存放在哪个寄存器中或者该操作数所在的内存位置,这些修饰字节主要包含在三个分开的值中:
-
addressing-form specifier (ModR/M) byte 寻址格式字节
-
Scale-Index-Base (SIB) byte 内存寻址时的三个计算因子,只在32位指令中存在,用以增加寻址的灵活性
-
one , two , or four address displacement bytes 一个,或两个,或四个字节的displacement位移字节
The ModR/M byte (寻址格式字节):
ModR/M字节由三个字段组成,如下面的图1-3所示:
图1-3
上图中Mod字段主要和r/m字段配合来定义出指令中要使用的寄存器和寻址模式。2位的Mod和3位的r/m一共有32种可能的组合,其中有8种组合用于定义要使用的寄存器,剩下24种组合用于定义寻址模式。
中间的reg/opcode字段可以有两种用途:前面提到过指令的Opcode操作码由1到3个字节组成,有的指令的Opcode操作码还会在reg/opcode字段中再定义3位,用于定义操作码的子功能,reg/opcode字段的另一种用途就是定义一个指令要用的寄存器,通常是用作指令的目标操作数。
r/m字段前面提到过,是和Mod字段配合来定义指令要使用的寄存器(和reg/opcode一样,它也可以单独定义一个寄存器),或者和Mod配合来定义一种需要的寻址模式。
上面只是个概述,真正用的时候是需要通过在因特尔手册上查表来确定这些字段的用途的,下面译者会举例说明,原著中对这段信息只有个概述,没有很详细的例子。
The SIB byte (32位指令中内存寻址的三个计算因子,用以增加寻址的灵活性):
SIB字节也由三个字段组成,如下图1-4所示:
图1-4
原著对SIB讲的过于简单,译者就不采用原著的内容,在SIB字节中index和base字段都对应三位的寄存器代码(同样的index,base对应的寄存器代码也可以通过因特尔手册查表得到,后面会进行说明),scale字段是一个2位的数字。要计算SIB的值,处理器会使用下面的公式:(index * 2^scale) + base (其中2^scale表示2的scale次方,显然,处理器会使用位移的方法来计算2的次方),处理器得到SIB的值后,就会代替掉ModR/M表中的计算因子(ModR/M表在后面会提到),从而得到最终的寻址值。(所以这些都是需要通过查表才能进行计算的)。
The address displacement byte (寻址位移字节):
这个位移字节就是一个内存偏移值,和上面提到的东东组合在一起就可以得到最终的指令操作内存地址(组合的模式也在因特尔的手册表里,下面会提到表的位置)。
Data element (数据元素部分):
有的汇编指令中会用到一些立即数,所谓立即数就是一些常量数字,例如 MOV CL 12H 这条汇编里的12H(12的十六进制)就是立即数,这条汇编的意思就是将12H这个数字传递到CL寄存器中。这些立即数转为处理器指令时就会放在Data element(数据元素)部分,这样CPU就可以直接从指令中得到操作数,不需要再内存寻址去获取操作数,Data element(数据元素)部分根据数据的大小可以由1,2或4个字节组成。
下面的部分就由译者从wikibooks上找到的资料,先举例说明8086下16位指令的格式(32位的和16位的结构大致差不多,只不过多了SIB字节,ModR/M之类的组合也有些差别,wikibooks上的原文地址是
http://en.wikibooks.org/wiki/X86_Assembly/Machine_Language_Conversion ),如果觉得太难,就留个印象好了:
This is the general instruction form for the 8086 sequentially in main memory:
下面是8086指令在内存中的通用格式:
Opcode 2 (occasional second byte) |
|
Displacement or data (occasional: 1, 2 or 4 bytes) |
-
Prefixes
-
Optional prefixes which change the operation of the instruction
可选的前缀,可以改变指令的操作
-
D
-
(1 bit) Direction. 1 = Register is Destination, 0 = Register is source.
1位的方向位,1表示寄存器是目标操作数,0表示寄存器是源操作数
-
W
-
(1 bit) Operation size. 1 = Word, 0 = byte.
1位的操作数大小,1表示操作数是word 16位大小,0表示操作数是byte 1字节8位大小
-
Opcode
-
the opcode is a 6 bit quantity that determines what instruction family the code is
操作码是一个6位的数据,用以决定指令的功能。
-
MOD (Mod)
-
(2 bits) Register mode.
2位的寄存器模式
-
Reg
-
(3 bits) Register. Each register has an identifier.
3位的寄存器值,每个寄存器都有一个标识。
-
R/M (r/m)
-
(3 bits) Register/Memory operand
3位的寄存器或内存操作数
Not all instructions have W or D bits; in some cases, the width of the operation is either irrelevant or implicit, and for other operations the data direction is irrelevant.
不是所有指令都有W或D位,在某些情况下,操作数的宽度要么是无关紧要的要么是隐式声明的,并且对于指令的其他操作数,数据的方向是无关紧要的。
Notice that Intel instruction format is little-endian, which means that the lowest-significance bytes are closest to absolute address 0. Thus, words are stored low-byte first; the value 1234H is stored in memory as 34H 12H. By convention, most-significant bits are always shown to the left within the byte, so 34H would be 00110100B.
注意因特尔的指令格式是小字节序或叫低字节序,意思就是指令或数据中的最低字节是最靠近内存的绝对地址0的。因此,内存中首先出现的是低字节部分,例如,值1234H在内存中是以34H 12H的方式进行储存的,按照惯例,字节内最高位通常显示在字节的左侧,所以34H对应的二进制就是00110100
After the initial 2 bytes, each instruction can have many additional addressing/immediate data bytes.
在头两个字节后,每条指令可以有许多额外的寻址或立即数的数据字节。
Mod / Reg / R/M tables
Mod |
Displacement |
00 |
If r/m is 110, Displacement (16 bits) is address; otherwise, no displacement |
01 |
Eight-bit displacement, sign-extended to 16 bits |
10 |
16-bit displacement (example: MOV [BX + SI]+ displacement,al) |
11 |
r/m is treated as a second "reg" field |
Reg |
W = 0 |
W = 1 |
double word |
|
000 |
AL |
AX |
EAX |
001 |
CL |
CX |
ECX |
010 |
DL |
DX |
EDX |
011 |
BL |
BX |
EBX |
100 |
AH |
SP |
ESP |
101 |
CH |
BP |
EBP |
110 |
DH |
SI |
ESI |
111 |
BH |
DI |
EDI |
r/m |
Operand address |
000 |
(BX) + (SI) + displacement (0, 1 or 2 bytes long) |
001 |
(BX) + (DI) + displacement (0, 1 or 2 bytes long) |
010 |
(BP) + (SI) + displacement (0, 1 or 2 bytes long) |
011 |
(BP) + (DI) + displacement (0, 1 or 2 bytes long) |
100 |
(SI) + displacement (0, 1 or 2 bytes long) |
101 |
(DI) + displacement (0, 1 or 2 bytes long) |
110 |
(BP) + displacement unless mod = 00 (see mod table) |
111 |
(BX) + displacement (0, 1 or 2 bytes long) |
(因特尔手册上有关16位的ModR/M表则是以关系表的形式出现,在开头的链接地址里可以下载到因特尔英文手册,可以查看该手册的427页,该页有张“Table 2-1. 16-Bit Addressing Forms with the ModR/M Byte”的表)
Note the special meaning of MOD 00, r/m 110. Normally, this would be expected to be the operand [BP]. However, instead the 16-bit displacement is treated as the absolute address. To encode the value [BP], you would use mod = 01, r/m = 110, 8-bit displacement = 0.
注意表中一个特殊的组合,MOD为00,r/m为110时,如果按照常规,操作数应该是[BP],然而,因特尔却武断的将这种情况定义为直接使用16位的displacement部分作为内存的绝对地址(在32位下,也有这种情况,只不过32位的组合情况可能不同),如果需要使用[BP],那么你将需要使用mod = 01,r/m=110,8位displacement=0的组合,所以很多汇编程序在遇到例如 MOV CL [BP] 这样的汇编语句时,会自动将其转为 MOV CL [BP + 0]的形式。
Example: Absolute addressing (例子:绝对寻址)
Let's translate the following instruction into bytecode:
让我们将下面的汇编语句转为指令字节码:
Note that this is XORing CL with the contents of address 12H – the square brackets are a common indirection indicator. The opcode for XOR is "001100dw". D is 1 because the CL register is the destination. W is 0 because we have a byte of data. Our first byte therefore is "00110010".
这条汇编的意思是将12H位置处的内存值读取出来,再和CL进行异或运算 ---- 中括号是一种通用的寻址符号,XOR异或的指令操作码是“001100dw”。D位是1,这是因为CL寄存器是目标操作数。W位是0,这是因为我们的操作数是一个字节的大小,所以我们的第一个字节就是“00110010”
Now, we know that the code for CL is 001. Reg thus has the value 001. The address is specified as a simple displacement, so the MOD value is 00 and the R/M is 110. Byte 2 is thus (00 001 110b).
从前面的表可知,CL寄存器的代码是001 。另外,因为绝对地址是一个单一的dispacement偏移即12H,所以MOD值为00,R/M值为110 。所以我们的第二个字节就是 (00 001 110b ,b表示这串数是二进制数)
Byte 3 and 4 contain the effective address, low-order byte first, 0012H as 12H 00H, or (00010010b) (00000000b)
字节3和字节4对应的就是16位的displacement偏移,此处就是有效地址,低字节放在前面,所以0012H对应为12H 00H ,或者 (00010010b) (00000000) 因为是16位,所以高位字节全为0
All together,
所有的组合在一起就是:
XOR CL, [12H] = 00110010 00001110 00010010 00000000 = 32H 0EH 12H 00H |
wikibooks上还举了个立即数的例子,因篇幅就不放在这里了,可以根据前面的链接地址,到wikibooks上查看。
在汇编语言英文原著中,有关
Data element这一节的最后部分还举了个32位指令的例子(这个例子在前面也提到过):
上面的指令字节码开头的C7是opcode操作码,在因特尔英文手册PDF电子档的第1693页,有张表
“Table A-2. One-byte Opcode Map: (00H — F7H)”,这张表里是有关一个字节的操作码和汇编指令的映射图,从这张表中可知,C7对应的是MOV指令,如下图1-5所示:
图1-5
对于C7后面的45可以查询手册第428页的表“Table 2-2. 32-Bit Addressing Forms with the ModR/M Byte” ,如下图1-6所示:
图1-6
所以45对应就是[EBP]的值加上后面的FC(FC就是表中的disp8偏移值),最后面的01 00 00 00为立即数1,所以上面的字节码对应的汇编就是
C7 45 FC 01 00 00 00 => MOV dword ptr [EBP + FCH],01H |
在因特尔手册的第429页是关于SIB的组合表,有需要的可以查看。
所以如果你精通了这些指令字节码,甚至可以用0101这种二进制来编程,有点hacker的味道了,呵呵。
OK,到这里,休息,休息一下 o(∩_∩)o~~