作者:zenglong (例如两个字节的操作码OF A2对应IA32的CPUID指令当处理器执行这条指令时它会在微处理器的不同寄存器中存放一些特定的信息) (操作码由1到3个字节组成) (所有的电脑处理器是通过厂家生产的处理器内部定义的二进制指令来完成的) 添加时间:2013/8/30 19:12:30 浏览次数:
979
学习汇编语言首先要理解什么是汇编语言,不像其他的编程语言,不同的汇编程序有不同的语法格式,许多刚接触汇编的程序员就陷入了这种困境,不知道该学哪种好。所以,学习汇编的第一步就是选择一种适合你的开发环境的汇编语言类型,一旦你确定下来了,学习汇编将不再是一件困惑的事情... 另外在附加一个因特尔英文手册的共享链接地址: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家族的微处理器使用一种特定的指令格式,理解这种格式将有助于你编写汇编程序。这种指令格式主要由四个部分组成:
图1-2 每个部分都定义了指令的相关功能,下面就一一介绍这几个部分。 小提示:并不是只有奔腾的处理器家族才采用这种IA-32的指令格式,AMD公司也生成了一批完全兼容IA-32指令格式的处理器芯片。 Opcode(操作码): 由图1-2可知,IA-32指令格式中必须至少包含一个Opcode操作码部分,这个操作码定义了处理器可以执行的基本功能或任务。 操作码由1到3个字节组成,它唯一的定义了处理器可以执行的功能。例如,两个字节的操作码:"OF A2"对应IA-32的CPUID指令,当处理器执行这条指令时,它会在微处理器的不同寄存器中存放一些特定的信息,程序员就可以通过其他指令从这些寄存器中提取出信息,通过这些信息就可以检测出当前程序所运行的微处理器的类型和型号。 小提示:寄存器是处理器芯片中用于暂存数据的组件,在后面的章节"IA-32平台"中会进行详细的介绍。 Instruction Prefix(指令前缀):
一些Opcode(操作码)需要某些额外的修饰字节来确定指令中的操作数存放在哪个寄存器中或者该操作数所在的内存位置,这些修饰字节主要包含在三个分开的值中:
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./wiki/X86_Assembly/Machine_Language_Conversion ),如果觉得太难,就留个印象好了: This is the general instruction form for the 8086 sequentially in main memory:
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. 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. After the initial 2 bytes, each instruction can have many additional addressing/immediate data bytes. Mod / Reg / R/M tables
(因特尔手册上有关16位的ModR/M表则是以关系表的形式出现,在开头的链接地址里可以下载到因特尔英文手册,可以查看该手册的427页,该页有张“Table 2-1. 16-Bit Addressing Forms with the ModR/M Byte”的表) 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". 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). Byte 3 and 4 contain the effective address, low-order byte first, 0012H as 12H 00H, or (00010010b) (00000000b) All together,
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,所以上面的字节码对应的汇编就是
在因特尔手册的第429页是关于SIB的组合表,有需要的可以查看。 所以如果你精通了这些指令字节码,甚至可以用0101这种二进制来编程,有点hacker的味道了,呵呵。 OK,到这里,休息,休息一下 o(∩_∩)o~~ |
|