汇编程序中以.开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)或伪操作(Pseudo-operation),由于它不是真正的指令所以加个“伪”字。
IA-32 硬件特性
通用寄存器用于存放正在处理的数据。
EAX 用于操作数和结果数的累加器
EBX 指向数据内存断中的数据的指针
ECX 字符串和循环操作的计数器
EDX IO指针
EDI 用于字符串操作的目标的数据指针
ESI 用于字符串操作的源的数据指针
ESP 堆栈指针
EBP 堆栈数据指针
其中寄存器EAX,EBX,ECX,EDX又可以通过16位和8位寄存器名称引用。如EAX,AX引用EAX低16位,AL引用EAX低8位,AH引用AL之后的高8位。
IA-32平台允许使用3中内存模型: 平坦内存模式 分段内存模式 实地址模式。
平坦内存把全部的系统内存表示为连续的地址空间, 通过线性地址的特定地址访问内存位置。
分段内存把系统内存划分为独立的段组,通过位于寄存器中的指针进行引用。每个段用于包含特定类型的数据。一个段用于包含指令码,另一个段包含数据元素,第三个段包含数据堆栈。段中的内存位置是通过逻辑地址引用的, 逻辑地址是由段地址加上偏移量构成, 处理器把逻辑地址转换为相应的线性地址以便访问。
段寄存器:
CS 代码段
DS 数据段
SS 堆栈段
ES 附加段指针
FS 附加段指针
GS 附加段指针
每个段寄存器都是16位的,包含指向内存特定段起始位置的指针,程序不能显示加载或改变CS寄存器。DS、ES、FS、GS都用于指向数据段,通过4个独立的段, 程序可以分隔数据元素,确保他们不会重叠,程序必须加载带有段的正确指针值的数据段寄存器,并且使用偏移值引用各个内存的位置。SS段寄存器用于指向堆栈段,堆栈包含传递给函数和过程的数据值。
如果实地址模式, 所有段寄存器都指向线性0地址, 并且都不会被程序改动, 所有的指令码、数据元素、堆栈元素都是通过他们的线性地址直接访问的。
指令指针寄存器是EIP寄存器,它跟踪要执行程序的下一条指令代码,应用程序不能修改指令指针本身,不能指定内存地址把它拖放EIP寄存器中,相反必须通过一般的跳转指令来改变预存取缓存的下一条指令。
在平坦内存模型中,指令指针包含下一条指令码的线性地址,在分段模型中指令指针包含逻辑地址指针,通过CS寄存器的内存引用。
这几个寄存器是与分页机制密切相关的,因此,在进程管理及虚拟内存管理中会涉及到这几个寄存器,读者要记住CR0、CR2、CR3及CR4这三个寄存器的内容
不能直接访问控制寄存器,但是能把控制寄存器中的值传递给通用寄存器,如果必须改动控制寄存器的标志,可以改动通用寄存器的值,然后把内容传递给控制寄存器。
CR0是系统内的控制寄存器之一。控制寄存器是一些特殊的寄存器,它们可以控制CPU的一些重要特性。
CR0的第0位是保护允许位PE(Protedted Enable),用于启动保护模式,如果PE位置1,则保护模式启动,如果PE=0,则在实模式下运行。
CR0的第1位是监控协处理位MP(Moniter coprocessor),它与第3位一起决定:当TS=1时操作码WAIT是否产生一个“协处理器不能使用”的出错信号。
CR0的第2位是模拟协处理器位 EM (Emulate coprocessor),如果EM=1,则不能使用协处理器,如果EM=0,则允许使用协处理器。
CR0的第3位是任务转换位(Task Switch),当一个任务转换完成之后,自动将它置1。随着TS=1,就不能使用协处理器。
CR0的第4位是微处理器的扩展类型位ET(Processor Extension Type),其内保存着处理器扩展类型的信息,如果ET=0,则标识系统使用的是287协处理器,如果 ET=1,则表示系统使用的是387浮点协处理器。
CR0的第16位是写保护未即WP位(486系列之后),只要将这一位置0就可以禁用写保护,置1则可将其恢复。
CR0的第31位是分页允许位PG(Paging Enable),当设置该位时即开启了分页机制;当复位时则禁止分页机制,此时所有线性地址等同于物理地址。在开启这个标志之前必须已经或者同时开启PE标志。即若要启用分页机制,那么PE和PG标志都要置位。
CR1是未定义的控制寄存器,供将来的处理器使用。
CR2是页故障线性地址寄存器,保存最后一次出现页故障的全32位线性地址。
CR3是页目录基址寄存器,保存页目录表的物理地址,页目录表总是放在以4K字节为单位的存储器边界上,因此,它的地址的低12位总为0,不起作用,即使写上内容,也不会被理会。
CR4在Pentium系列(包括486的后期版本)处理器中才实现,它处理的事务包括诸如何时启用虚拟8086模式等。。
IA-32使用单一的寄存器来包含一组状态控制和系统标志,eflags寄存器包含32位标志信息。
在64-bit模式下,eflags寄存器被扩展为64位的rflgas寄存器,高32位被保留,而低32位则与eflags寄存器相同。
32位的eflags寄存器包含一组状态标志、系统标志以及一个控制标志。在x86处理器初始化之后,eflags寄存器的状态值为00000002H。第1、3、5、15以及22到31位均被保留,这个寄存器中的有些标志通过使用特殊的通用指令可以直接被修改,但并没有指令能够检查或者修改整个寄存器。通过使用lahf/sahf/pushf/popf/popfd等指令,可以将eflags寄存器的标志位成组移到程序栈或eax寄存器,或者从这些设施中将操作后的结果保存到eflags寄存器中。在eflags寄存器的内容被传送到栈或是eax寄存器后,可以通过位操作指令(bt, bts, btr, btc)检查或修改这些标志位。
当调用中断或异常处理程序时,处理器将在程序栈上自动保存eflags的状态值。若在中断或异常处理时发生任务切换,那么eflags寄存器的状态将被保存在TSS中 【the state of the EFLAGS register is saved in the TSS for the task being suspended.】 ,注意是将要被挂起的本次任务的状态。
标志位说明
CF(bit 0)Carry flag,进位标志,如果无符号数的数学操作产生最高有效位的进位或者借位,此时值为1。
PF(bit 2)Parity flag,奇偶校验标志,用于表明数学操作的结果寄存器中的是否包含错误数据。
AF(bit 4)Adjust flag,辅助进位标志,用于二进制编码的10进制(BCD)的数学操作中,如果用于运算的寄存器的第三位发生进位或借位,该值为1。
ZF(bit 6)Zero flag,0标志,如果操作为0,则该值为1,反之清零。
SF(bit 7)Sign flag,符号标志,设置为结果的最高有效位,这一位是符号位表明结果是正值还是负值(0指示结果为正,反之则为负)。
OF(bit 11)Overflow flag,溢出标志。如果整型结果是较大的正数或较小的负数,并且无法匹配目的操作数时将该位置1,反之清零。这个标志为带符号整型运算指示溢出状态。
DF(bit 10)方向标志, 用于控制处理器处理字符串的方式,控制串指令(MOVS, CMPS, SCAS, LODS以及STOS)。设置DF标志使得串指令自动递减(从高地址向低地址方向处理字符串),清除该标志则使得串指令自动递增。STD以及CLD指令分别用于设置以及清除DF标志。
TF(bit 8)Trap flag,陷阱标志, 设置为1时启用单步模式, 在单步模式下处理器每次只执行一条命令。
IF(bit 9)Interrupt enable flag,中断使能标志, 该标志用于控制处理器对可屏蔽中断请求(maskable interrupt requests)的响应。置1表示响应可屏蔽中断,反之则禁止可屏蔽中断。
IOPL(bits 12 and 13)I/O privilege level field,IO特权级别标志, 表明当前正在运行任务的IO特权级别(I/O privilege level), 正在运行任务的当前特权级(CPL)必须小于或等于I/O特权级才能允许访问I/O地址空间。这个域只能在CPL为0时才能通过POPF以及IRET指令修改。
NT(bit 14)Nested task flag,这个标志控制中断链和被调用任务。若当前任务与前一个执行任务相关则置1,反之则清零。
RF(bit 16)Resume flag],恢复标志,控制处理器对调试异常的响应。
VM(bit 17)Virtual-8086 mode flag,虚拟8086模式, 置1处理器在虚拟8086模式中,而不是保护模式或者实模式。
AC(bit 18)Alignment check flag,对准检查标志, 该标志以及在CR0寄存器中的AM位,置1时将允许内存引用的对齐检查,以上两个标志中至少有一个被清零则禁用对齐检查。
VIF(bit 19)Virtual interrupt flag,虚拟中断标志, 当处理器在虚拟模式中操作时, 该标志起IF标志的作用。该标志是IF标志的虚拟镜像(Virtual image),与VIP标志结合起来使用。使用这个标志以及VIP标志,并设置CR4控制寄存器中的VME标志就可以允许虚拟模式扩展(virtual mode extensions)。
VIP(bit 20)Virtual interrupt pending flag,虚拟中断挂起标志, 在虚拟模式操作时,该位置1以指示一个中断正在被挂起,当没有中断挂起时该位清零。
ID(bit 21)Identification flag,表示CPU是否支持cpuid指令, 如果处理器能够设置或者清零这个标志, 表示处理器支持该指令。
eip寄存器主要用于存放当前代码段即将被执行的下一条指令的偏移,但其本质上并不能直接被指令直接访问。这个寄存器指令由控制转移指令、中断及异常所控制。读操作通过执行call指令并取得栈中所存放的地址来实现,而写操作则通过修改程序栈中的返回指令指针并执行RET/IRET指令来完成。
立即数前面要加一个$符号, 寄存器前面要加%符号。
mov指令可能是汇编里用的最多的指令了,完成c语言里的赋值。
格式:movx 源操作数,目的操作数。其中x为要传送数据的长度,取值有:
b 用于8位的字节值。
w 用于16位的字。
l 用于32位的长字节。
q 用于64位的长字节。
符号扩展指令
movsbw #作符号扩展的1字节复制到2字节
movsbl #作符号扩展的1字节复制到4字节
movsbq #作符号扩展的1字节复制到8字节
movswl #作符号扩展的2字节复制到4字节
movswq #作符号扩展的2字节复制到8字节
movslq #作符号扩展的4字节复制到8字节
movzbw #作0扩展的1字节复制到2字节
movzbl #作0扩展的1字节复制到4字节
movzbq #作0扩展的1字节复制到8字节
movzwl #作0扩展的2字节复制到4字节
movzwq #作0扩展的2字节复制到8字节
movzlq #作0扩展的4字节复制到8字节
8个通用的寄存器是用于保存数据的最常用的寄存器,这些寄存器的内容可以传递给其他的任何可用的寄存器。和通用寄存器不同,专用寄存器(控制、调试、段)的内容只能传送给通用寄存器,或者接收从通用寄存器传过来的内容。
指令格式:cmovex 源操作数, 目的操作数。x的取值为:
无符号数:
a/nbe 大于/不小于或者等于
ae/nb 大于或者等于/不小于
nc 无进位
b/nae 小于/不大于等于
c 进位
be/na 小于或等于/不大于
e/z 等于/零
ne/nz 不等于/不为零
p/pe 奇偶校验/偶校验
np/po 非奇偶校验/奇校验
有符号数:
ge/nl 大于或者等于/不小于
l/nge 小于/不大于或者等于
le/ng 小于或者等于/不大于
o 溢出
no 未溢出
s 带符号(负)
ns 无符号(非负)
xchg指令在两个寄存器之间或者寄存器和内存间交换值。
指令格式:xchg 操作数, 操作数。
要求两个操作数必须长度相同且不能同时都是内存位置
bswap反转一个32位寄存器的字节顺序。
xadd指令交换两个值,并把两个值只和存储在目标操作数中。
指令格式:xadd 源操作数,目标操作数。
其中源操作数必须是寄存器, 目标操作数可以是内存位置也可以是寄存器。其中寄存器可以是32,16,8位的。
指令格式:cmpxchg 源操作数,目标操作数。
其中源操作数必须是寄存器, 目标操作数可以是内存或者寄存器, 用来比较两者的值, 如果相等,就把源操作数的值加载到目标操作数中, 如果不等就把目标操作数加载到源操作数中,其中寄存器可以是32,16,8位的, 其中源操作数是EAX,AX或者AL寄存器中的值。
指令格式:cmpxchg8b 目标操作数。
cmpxchg8b指令同cmpxchg,但是它处理8字节值,同时它只有一个操作数。其中目标操作数引用一个内存位置, 其中的8字节值会与edx和eax寄存器中包含的值(EDX高位寄存器, EAX低位寄存器)进行比较, 如果目标值和edx:eax对中的值相等, 就把edx:eax对中的64位值传递给内存位置, 如果不匹配就把内存地址中的值加载到edx:eax对中。
esp寄存器保存了当前堆栈的起始位置,当一个数据压入栈时,它就会自动递减,反之其自动递增。
pushx指令为压入堆栈操作。
指令格式:pushx source。x取值为:l为32位,w为16位。
popx指令为弹出堆栈操作。
指令格式:popx source
其中source必须是16或32位寄存器或者内存位置,当pop最后一个元素时ESP值应该和以前的相等。
pusha/popa 压入或者弹出所有16位通用寄存器。
pushad/popad 压入或者弹出所有32位通用寄存器。
pushf/popf 压入或者弹出EFLAGS寄存器的低16位。
pushfd/popfd 压入或者弹出EFLAGS寄存器的全部32位。
clc 清空进位标志(设置为0)
cmc 对进位标志求反(把它改变为相反的值)
stc 设置进位标志(设置为1)
sti 中断标志置1,使IF=1。
cli 中断标志置0,使IF=0。
loop循环直到ecx寄存器为0。
指令格式为: loopxx address 注意循环指令只支持8位偏移地址。
loope/loopz 循环直到ecx寄存器为0或者没有设置ZF标志。
loopne/loopnz 循环直到ecx为0或者设置了ZF标志。
cmpb S2,S1 基于S1–S2,比较字节,差关系。
cmpw S2,S1 基于S1–S2,比较字,差关系。
cmpl S2,S1 基于S1–S2,比较双字,差关系。
testb S2,S1 基于S1&S2,测试字节,与关系。
testw S2,S1 基于S1&S2,测试字节,与关系。
testl S2,S1 基于S1&S2,测试字节,与关系。
add指令,把两个整数相加。
指令格式:add source, destination。
其中source可以是立即数内存或者寄存器,destination可以是内存或者寄存器,但是两者不能同时都是内存位置。adc和add相似进行加法运算,但是它把前一个add指令的产生进位标志的值包含在其中,在处理位数大于32(如64)位的整数时,该指令非常有用。
sub指令,把两个整数相减。
指令格式:sub source, destination。
sbb指令,和加法操作一样,可以使用进位情况帮助执行大的无符号数值的减法运算,sbb在多字节减法操作中利用进位和溢出标志实现跨数据边界的的借位特性。
neg它生成值的补码。
指令格式:dec destination。递减。
指令格式:inc destination。递增。
其中dec和inc指令都不会影响进位标志,所以递增或递减计数器的值都不会影响程序中涉及进位标志的其他任何运算。
指令格式:mul source。
它使用隐含的目标操作数,目标位置总是使用eax的某种形式,这取决与源操作数的长度,因此根据源操作数的长度,目标操作数必须放在al,ax,eax中。此外由于乘法可能产生很大的值,目标位置必须是源操作数的两倍位置,源为8时,应该是16,源为16时,应该为32,但是当源为16位时,intel为了向下兼容,目标操作数不是存放在eax中,而是分别存放在dx:ax中,结果高位存储在dx中,低位存储在ax中。对于32位的源,目标操作数存储在edx:eax中,其中edx存储的是高32位,eax存储的是低32位。
2.8.4.2.1 指令格式:imul source。
imul指令的目标操作数和mul的一样。
2.8.4.2.2 指令格式:imul source, destination。
执行有符号乘法运算,但是此时可以把目标放在指定的位置,使用这种格式的缺陷在与乘法的操作结果被限制为单一目标寄存器的长度。
2.8.4.2.3 指令格式:imul multiplier, source, destination。
其中multiplier是一个立即数,这种方式允许一个值与给定的源操作数进行快速的乘法运算,然后把结果存储在通用寄存器中。
指令格式:div divisor。执行无符号数除法运算。
除数的最大值取决与被除数的长度,对于16位被除数,除数只能为8位,32或64位同上
被除数 被除数长度 商 余数
AX 16位 AL AH
DX:AX 32位 AX DX
EDX:EAX 64位 EAX EDX
指令格式:idiv divisor。执行有符号数的除法运算,方式和div一样。
指令格式:sal 向左移位
指令格式:sal destination。 把destination向左移动1位。
指令格式:sal %cl, destination。 把destination的值向左移动CL寄存器中指定的位数。
指令格式:sal shifter, destination。 把destination的值向左移动shifter值指定的位数。
向左移位可以对带符号数和无符号数执行向左移位的操作, 移位造成的空位用零填充, 移位造成的超过数据长度的任何位都被存放在进位标志中, 然后在下一次移位操作中被丢弃。
shr向右移位
sar向右移位
shr指令清空移位造成的空位,所以它只能对无符号数进行移位操作。
sar指令根据整数的符号位,要么清空,要么设置移位造成的空位,对于负数,空位被设置为1。
和移位指令类似,只不过溢出的位被存放回值的另一端,而不是丢弃。
rol 向左循环移位。
ror 向右循环移位。
rcl 向左循环移位, 并且包含进位标志。
rcr 向右循环移位, 并且包含进位标志。
movs 有三种格式
(1)movsb 传送单一字节
(2)movsw 传送一个字
(3)movsl 传送双字
movs指令使用隐含的源和目的操作数, 隐含的源操作数是esi,隐含的目的操作数是edi。
怎样把字符串地址传给ESI与EDI呢?有两种方式加载内存地址到esi和edi,
(1)第一种是使用标签间接寻址 movl $output,%esi。
(2)第二种是使用lea指令,lea指令加载对象的地址到指定的目的操作数如lea output,%esi。例如,在32位程序下,地址为4个字节,所以指令就是leal。如加载一个字符串地址到ESI寄存器,假设字符串标签为demoStr。使用就是:leal demoStr,%esi.
每次执行movs指令后,数据传送后esi和edi寄存器会自动改变,为另一次传送做准备,esi和edi可能随着标志DF的不同自动递增或者自动递减,如果DF标志为0则movs指令后esi和edi会递增,反之会递减,为了设置DF标志,可以使用一下指令:cld将DF标志清零。std设置DF标志。
rep指令的特殊之处在与它不执行什么操作,这条指令用于按照特定次数重复执行字符串指令,有ecx寄存器控制,但不需要额外的loop指令。
如rep movsl
rep指令的其他格式:
repe 等于时重复
repne 不等于时重复
repnz 不为零时重复
repz 为零时重复
lods指令的含义是将内存单元(地址:DS:SI)中的内容装入(load)到寄存器(AX,EAX)。lods指令加载字符串,esi为源,当一次执行完lods时会递增或递减esi寄存器,然后把字符串值存放到eax中。
(1)lodsb指令:加载一个字节到al中,Esi中的地址值按df的方向进行加1或减1。
(2)lodsw指令:加载一个字到ax中,esi中的地址值按df的方向进行加2或减2。
(3)lodsl指令:加载二个字到eax中,加载之后esi中的地址值加4或减4。
stos指令的含义是:将寄存器(AX,EAX)里的内容(一个字或一个字节)存储(store)到内存单元(地址ES:DI)。同时CPU自动会根据DF的值自动递增或者递减edi中的值,如果设置了DF(direction flag),那么edi会在该指令执行后减小,如果没有设置DF,那么edi的值会增加。
(1)stosb:从al存一个字节到edi指定的位置,edi的位置在存后或向前移位1个。
(2)stosw:从ax存二个字节到edi指定的位置,edi的位置在存向后或向前移位2个。
(3)stosl:从eax存四个字节到edi指定的位置,edi的位置在向后或向前移位4个。
cmps和其他的操作字符串的指令一样,隐含的源和目标操作数都为ESI和EDI,每次执行时都会根据DF的值把ESI和EDI递增或者递减,cmps指令从目标字符串中减去源字符串,执行后会设置EFLAGS寄存器的状态。
scas把EDI作为目标,它把EDI中的字符串和EAX中的字符串进行比较,然后根据DF的值递增或者递减EDI。
条件跳转按照EFLAGS中的值来判断是否该跳转。
jmp Label,直接跳转。
jmp *Operand,间接跳转。
条件跳转格式为:jxx address,其中xx是1-3个字符的条件代码, 取值如下:
ja 大于时跳转,跳转条件:~CF&~ZF。
jae 大于等于,跳转条件:~CF。
jb 小于。跳转条件:CF。
jbe 小于等于。跳转条件:CF|ZF。
jc 进位。
jcxz 如果CX寄存器为0。
jecxz 如果ECS寄存器为0。
je 相等,等于/零。跳转条件:ZF。
jna 不大于
jnae 不大于或者等于
jnb 不小于
jnbe 不小于或等于
jnc 无进位
jne 不等于
jg 大于(有符号)。跳转条件:~(SF^OF)&~ZF。
jge 大于等于(有符号)。 跳转条件:~(SF^OF)。
jl 小于(有符号)。 跳转条件:SF^OF。
jle 小于等于(有符号)。 跳转条件:(SF^OF)|ZF。
jng 不大于(有符号)
jnge 不大于等于(有符号)
jnl 不小于
jnle 不小于等于
jno 不溢出
jnp 不奇偶校验
jns 无符号
jnz 非零,跳转条件:~SF。
jo 溢出
jp 奇偶校验
jpe 如果偶校验
jpo 如果奇校验
js 如果带符号
jz 如果为零
条件跳转不支持分段内存模型下的远跳转,如果在该模式下进行程序设计必须使用程序逻辑确定条件是否存在,然后实现无条件跳转,跳转前必须设置EFLAGS寄存器。
调用指令分为两个部分:
(1)调用call address跳转到指定位置。
(2)返回指令ret,它没有参数紧跟在call指令后面的位置。
执行call指令时,它把eip的值放到堆栈中,然后修改eip以指向被调用的函数地址,当被调用函数完成后,它从堆栈获取过去的eip的值,并把控制权返还给原始程序。
由硬件设备生成中断。程序生成软件中断,当一个程序产生中断调用时, 发出调用的程序暂停, 被调用的程序接替它运行, 指令指针被转移到被调用的函数地址,当调用完成时使用中断返回指令可以返回调原始程序。
ljmp 是64K内任意跳转,ljmp的机器码,占用三个字节。
ajmp 2K内跳转
sjmp 前128到后128跳转 总共256个字节内。而sjmp占用两个字节。
jmp@a+dptr 64k内相对转移
IA-32平台中存储超过一字节的数都被存储为小尾数的形式但是把数字传递给寄存器时, 寄存器里面保存是按照大尾数的形式存储。
把无符号数转换成位数更大的值时,必须确保所有的高位部分都被设置为零。
把有符号数转换成位数更大的数时,intel提供了movsx指令它允许扩展带符号数并保留符号,它与movzx相似,但是它假设要传送的字节是带符号数形式。
fld 指令用于把浮点数字传送入和传送出FPU寄存器。
格式:fld source。其中source可以为32 64或者80位整数值
IA-32使用FLD指令用于把存储在内存中的单精度和双精度浮点值FPU寄存器堆栈中,为了区分这两种长度GNU汇编器使用FLDS加载单精度浮点数, FLDL加载双精度浮点数。
类似FST用于获取FPU寄存器堆栈中顶部的值, 并且把这个值放到内存位置中, 对于单精度使用FSTS, 对于双精度使用FSTL
GNU汇编语言定义函数的语法:
.type 标签(也就是函数名), @function
ret 返回到调用处
linux系统调用的中断向量为0x80,调用内核功能。系统调用标识存放在eax中
系统调用输入值:
ebx 第一个参数
ecx 第二个参数
edx 第三个参数
esi 第四个参数
edi 第五个参数
需要输入超过6个输入参数的系统调用,ebx指针用于保存指向输入参数内存位置的指针,输入参数按照连续的的顺序存储,系统调用的返回值存放在eax中。
从文本中拷贝,忘记参考链接。