标签:
3.1历史观点
Intel处理器的换代:8086——80286——i386——i486——Pentium——PentiumPro——PentiumII——PentiumIII——Pentium4——Pentium4E——Core2——Corei7。
这些所有的代都是Intel系列的,Intel系列本身有很多名字,比如x86,比如IA32,Intel系列中64位扩展称为x86-64。最常用的是x86。也就是说x86就是Intel每一代处理器的统称。
8086和80286的存储器模型都已经过时了,从i386开始提供的平坦寻址模式也是linux使用的模式,这是将存储空间看做一个大的字节数组。
看来i386是一个转折点,从这里开始系统扩展为32位,同时,GCC为32位执行的默认调用仍然假设是为i386机器产生的代码,Intel系列,x86,是向后兼容的,所以i386的可以执行的代码,后面的都可以执行。
3.2 程序编码
提高gcc的优化级别,会使得产生的机器代码和初始源代码之间的关系非常难以理解。-O2,第二级优化是默认选择。这里是LMNOPQ的O,不是零。
机器代码的两种形式:目标代码,可执行代码。目标代码包含了所有的指令但还没有填入地址的全局值,后者是处理器执行的代码格式。
对于机器级编程来说,有两种抽象尤其重要:机器级编程的格式和行为,定义为指令集体系结构。机器级程序使用的存储器地址是虚拟地址,提供的存储器模型看上去是一个非常大的字节数组。
能够理解汇编代码以及它与原始c代码的联系,是理解计算机如何执行程序的关键一部。
IA32机器代码和原始的c代码差别非常大。一些通常对c语言程序员隐藏的处理器状态是可见的:
程序计数器,PC,用%eip表示,指示要执行的下一条指令在存储器中的地址,这里的存储器是主存。
整数寄存器文件包含8个命名的位置,分别32位,可以保存:地址,程序状态,局部变量,函数返回值。
条件码寄存器保存着最近执行的算术或逻辑指令的状态信息。用来实现if和while等。
一组浮点寄存器存放浮点数据。
机器代码只是将存储器看成一个很大的、按字节寻址的数组。
程序存储器(program memeory)包含:程序的可执行机器代码(代码和数据区),操作系统需要的一些信息(应该也在代码和数据区),用来管理过程调用和返回的运行时栈(栈),以及用户分配的存储器块(堆)。
操作系统负责管理虚拟地址空间,将虚拟地址翻译成实际处理器存储器(processor memory)中的物理地址。
gcc -O1 -s xxx.c 输出 汇编代码
gcc -O1 -c xxx.c 输出 目标代码
objdump -d xxx.o 反汇编机器代码(包括目标代码和可执行代码,两者的区别在于偏移地址)
gdb 可以直接对机器代码使用(包括目标代码和可执行代码)
gcc -s产生的汇编代码中,所有以点开头的行都是用于指导汇编器和链接器的。
gcc和objdump产生的汇编代码是ATT风格,微机原理里面学习的是Intel风格。有所不同,但本质没有改变。
3.3 数据格式
Intel用术语word表示16位数据类型,32为double words,64位为quad words,这是由于最初Intel系列是从16位开始的。
ATT风格的汇编代码指令都有一个字符后缀,表面操作数的大小。Intel风格的汇编代码是没有的。
3.4 访问信息
IA32的cpu中有8个32位的寄存器,%e是前缀,依次是:ax,cx,dx,bx,si,di,sp,bp。前6个可以看作是通用寄存器,大多数情况下。前3个和后3个的保存和恢复惯例不同。最后两个指向程序栈中重要位置的指针。
指令的源数据值可以以常数形式给出,或是从寄存器或存储器中读出。也就是,常数,寄存器,内存。
IA32的一条限制:数据传送指令的两个操作数不能都指向存储器位置。
数据传送指令的源操作数在左,目的操作数在右(ATT风格),(Intel风格则相反)。
栈在程序的虚拟地址空间的上部,再往上就是内核虚拟空间了,栈底紧挨着内核虚拟空间,栈顶向下增长。%esp保存这栈顶元素的地址。
将一个双字压入栈,先将%esp减小4,然后将双字放入这多出来的4个字节的空间中。出栈则是先读出4个字节,然后%esp加4。
因为栈和程序代码以及其他形式的程序数据都是放在同样的存储器中(虚拟地址空间),所以程序用标准的存储器寻址方法访问栈内任意位置。
3.5 算术和逻辑操作
加载有效地址地址指令,leal,只是将源操作数计算出来的地址交给目的操作数,貌似:源操作数是存储器访问格式的,目的操作数是寄存器。
一元操作:incb/w/l,decb/w/l,negb/w/l,notb/w/l,前面两个好理解,第三个是取负,这里用到了一个概念,取负是指2变成-2,-3变成3,可以认为,就将后面的操作数认为是补码编码的了,然后如果本来是2的补码,现在就要编程-2的补码,也就是进行0-2的运算。第四个是取反。
二元操作:addb/w/l,subb/w/l,imulb/w/l,xorb/w/l,orb/w/l,andb/w/l。12两个是位级的运算,不,其实,所有的都是位级运算,因为是汇编吗。但值得注意的是:第3个,乘是先数学上的乘,然后截断一下。这是2章中得到的结论,至少是对于imulb/w/l3者。后3个好理解,异或,或,并。
移位:salb/w/l,shlb/w/l,sarb/w/l,shrb/w/l。向左left,向右right移位。向左总是补0的,向右就是算术移位和逻辑移位了。移位量是单个字节的编码,因为只允许0到31位的移位(只考虑移位量的低五位)。移位量只可以是一个立即数或者单字节寄存器元素%cl。
特别的:imull,mull可以是一元操作(上面的二元操作)。divl和idivl是除法。cltd是符号扩展。这5个是特殊的算术操作。
3.6 控制
机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值(是测试,而不是真实计算),然后根据测试的结果来改变控制流或者数据流。
先讲机器级机制,然后说明c语言的结构是如何组成的,然后介绍使用有条件的数据传输来实现与数据相关的行为。
除了前面说的8个寄存器,IA32cpu还有一个条件码寄存器。它们描述了最近的(最近的)算术或逻辑操作的属性。
最常用的条件码:
CF:进位标志
ZF:零标志
SF:符号标志
OF:溢出标志
CF:(unsigned)t < (unsiged)a 无符号溢出
ZF: (t==0) 零
SF: (t < 0) 负数
OF:(a <0 == b< 0)&& (t < 0 != a < 0) 有符号溢出
leal不会改变任何条件码,逻辑操作进位标志CF和溢出标志OF都为设置为0,移位操作进位标志CF会设置为最后一个被移出的位,而溢出标志OF会设置为0,INC和DEC指令会设置溢出OF和零ZF标志位,但不会改变进位标志CF。
CMP指令基于A-B,TEST指令基于A&B。
条件码通常不会直接读取,常用的使用方法有3种:
可以根据条件码的组合,将一个字节设置为0或者1——SET系列指令(目的操作数是8个单字节寄存器之一,或者存储器中的一个字节)。
可以条件跳转到程序的某个其他的部分——JUMP系列指令。
可以条件的传递数据——前两者是控制转移,路径的变化,这一点导致了现代处理器流水线处理的中断(预测错误的时候),而条件数据传递则可以保证数据按照流水线处理,效率要远高于前面的条件控制转换——CMOV系列指令。
我们来看SET指令。
jmp是无条件跳转指令,其他的都是有条件跳转的。jmp可以间接跳转,也可以直接,其他的,也就是有条件的跳转只能是直接的。
当从汇编代码汇编成机器代码时,跳转指令有几种编码,典型的,都是于PC(程序计数器)相关的。这些编码会将目标指令的地址(要跳转的地址)与本条跳转指令后面的那条指令的地址之间的差作为编码。还有的编码使用“绝对“地址。
通过使用与PC相关的跳转目标编码,指令编码很简洁,而且目标代码可以不做改变就移到存储器中不同的位置。因为指令里面是相对位置。
if else 结构的汇编代码会交替使用有条件和无条件跳转。
do while,while,for,第一个最符合汇编的风格,第二个借用第一个,第三个借用第二个。控制的条件转移为循环翻译成机器代码提供了基本的机制。但for里面的contiune需要注意下,要加一个额外的goto。
条件传送指令CMOV和SET和JUMP指令对应的比较好。
不是所有的条件表达式都可以用条件传送来编译。如果两种表达式(if下的body和else下的body)的任意一个可能产生错误条件或者副作用,就会导致非法的行为。
总的来说:条件数据传送提供了一种用条件控制转移来实现条件操作的替代策略。
switch,重要的地点在跳转表,挑出标号和跳转表的对应关系即可。
3.7 过程
一个过程调用包括将数据和控制从代码的一部分传递到另一部分。过程调用就是函数。数据就是函数参数和返回值。控制应该就是当前运行的代码吧。另外,过程的局部变量的空间的分配和释放。
IA32中,控制的转移是有相应的指令的,但是,数据传递和局部变量的分配释放通过操纵程序栈来实现。
从这里看,过程调用分成3个部分:控制转移,数据传递,分配与释放局部变量。
为单个过程分配的那部分栈称为栈帧。
栈帧也可以看做有两种,一种是处于最低端的栈帧,也就是当前过程的栈帧;另一种就是处于栈中部的(非最低端)栈帧,也就是之前调用的过程,还没有返回。
每个栈帧的顶端都是%ebp,这里有两个意思,首先当前过程,拥有当前寄存器的值,%ebp寄存器的值是一个地址,地址是本栈帧最高字的地址。这个最高字记录着上一栈帧的最高字地址,以此类推。
控制:其实表示的就是当前cpu在处理哪一个过程的代码,在处理那一个过程的代码,我们就说当前的控制在那一个过程。
当前栈帧总是以%ebp中的值表示栈帧的最高字地址。最低字地址由%esp表示。中间栈帧最高字地址,保持在其下一个栈帧(也就是较低栈帧)的最高字节中,中间栈帧的最低字节都是返回地址,就是说,当较低栈帧代表的过程返回时,将跳转的代码的位置。
栈的重点还是两个寄存器:%ebp和%esp。前者记录当前栈帧的最高字地址,后者记录栈的最低字地址,但其实也就是当前栈帧的最低字地址。一个是帧指针,一个是栈指针。
栈帧中,可以保存:寄存器,本地变量,临时变量。
局部变量应该包括本地变量和临时变量,一般来说,局部变量保存在寄存器中,但也用栈来保存,用栈来保存的时候,一般有如下原因:
没有足够多的寄存器存放所有变量(好理解)
有些局部变量是数组和结构,因此必须通过数组和结构引用来访问。(就是通过地址来访问)
要对一个局部变量使用地址操作符&,必须能够为它生成一个地址(好理解)
当前栈帧的过程使用的参数都存放在上一栈帧中,上一栈帧的过程要使用的参数存放在上上一个栈帧中。
call指令的效果是:将返回地址入栈,并跳转到被调用过程的起始处。(这里的效果对栈来说,只有一点,就是入栈了返回地址,这句话之后,%esp就指向返回地址所在的位置了。这意味着旧栈帧已经封存了,但新的栈帧还没有出现,后半句跳转到被调用过程的起始处,说明了一点,过程的起始处有建立新栈帧的指令,新栈帧的建立是新过程自己完成的)
返回地址,就是call后面的指令的地址。
当调用过程返回时,执行会从此处继续。
ret指令的效果是:从栈中弹出地址,并跳转到这个位置。(这里有个重点,就是ret并不知道当前栈顶存储的是什么,他只是简单的弹出这个值,然后跳转去。所以要正确使用这个指令,必须,先要使得栈指针指向返回地址,这也就是说,新栈已经没有了,栈顶变成了旧栈的最后一个字。这个时候才可以使用ret指令,否则就不对了。)
leave指令的效果是:可以使栈做好返回的准备。(这里包含两点:1使得%ebp指向旧帧的最高字地址,2使得%esp指向旧帧返回地址。;使用这个指令之后,再使用ret,就没有问题。但这个指令不是必须的,其可以通过其他指令代替)
过程要返回整数或者指针的时候,寄存器%eax可以用来返回值。其实汇编级别的返回,就是在栈变化的时候(过程控制变化的时候)保持某一个寄存器不变,这样,返回的过程就可以使用这个寄存器,也就是说返回值了。相信,如果是double类型的,那么可能用两个寄存器返回也不一定,肯定是类似的过程。
调用者保存寄存器——%eax,%edx,%ecx。被调用者随便用。
被调用者保存寄存器——%ebx,%esi,%edi。被调用者要用这些的话,就需要先保存这些到栈,然后在ret前恢复这些寄存器。
此外,%ebp和%esp是要保持的,%esp还好,但%ebp是很需要维持的,算是被调用者保存寄存器了。因为这两个只用于当前过程。
gcc坚持一个x86编程指导方针:一个函数使用的所有栈空间,必须是16字节的整数倍。
这里突然想到了过程和函数是不同的,一个过程是顺序的,一个函数不一定,一个函数可以是顺序的,也可以不是,比如递归函数。对于递归函数,每一次的递归,对于机器语言来说,就是一个过程。所以过程和函数是不对应的。一个函数至少是一个过程。
一个函数所使用的栈空间,必须是16字节的整数倍。如果是单一过程,也就是一个栈帧的情况,那这个栈帧就应该是16字节的倍数,如果多个过程,那么单个栈帧搞不好就可以没有16字节了,这个不多想了。
一个过程的开始,通常是在call指令执行后,这个时候,旧帧已经封存了,但新帧还没有建立,所以个过程的开始要先建立新栈帧。通常是两个语句:pushl %ebp和movl %esp, %ebp。
也可能多一个pushl %ebx。
编译器根据一组很简单的惯例来产生管理栈结构的代码:
参数在栈上传递给函数,可以从栈中用相对于 %ebp的正偏移量来访问他们。
可以使用push或者从栈指针减去偏移量来在栈上分配空间。
返回前,函数必须将栈恢复到原始条件,可以恢复所有的被调用者保存寄存器和%ebp,并且重置%esp使其指向返回地址。
由于本次所学知识和上学期的汇编语言有联系,所以特别熟悉了一下一下汇编语言:
伪指令
•
1、定位伪指令
ORG m
•2、定义字节伪指令
DB X1,X2,X3,…,Xn
•3、字定义伪指令
DW Y1,Y2,Y3,…,Yn
4、汇编结束伪指令
END
寻址方式
MCS-51单片机有五种寻址方式:
1、寄存器寻址 2、 寄存器间接寻址
3、直接寻址 4、立即数寻址
5、基寄存器加变址寄存器间接寻址 6、相对寻址
7、位寻址
数据传送指令
一、以累加器A为目的操作数的指令(4条)
•MOV A,Rn ;(Rn)→A n=0~7
•MOV A,direct ;( direct )→A
•MOV A,@Ri ;((Ri))→A i=0~1
•MOV A,#data ; data →A
二、以Rn为目的操作数的指令(3条)
MOV Rn ,A ;(A)→ Rn
MOV Rn ,direct ;( direct )→ Rn
MOV Rn ,#data ; data → Rn
•三、以直接寻址的单元为目的操作数的指令(5条)
MOV direct,A ;(A)→direct
MOV direct,Rn ;(Rn)→direct
MOV direct,direct ;(源direct)→目的direct
MOV direct,@Ri ;((Ri))→direct
MOV direct,#data ; data→direct
四、以寄存器间接寻址的单元为目的操作数的指令(3条)
MOV @Ri,A ;(A)→(Ri)
MOV @Ri,direct ;(direct)→(Ri)
MOV @Ri,#data ; data→ (Ri)
五、十六位数据传送指令(1条)
MOV DPTR,#data16 ;dataH→DPH,dataL →DPL
六、堆栈操作指令
进栈指令
PUSH direct ;(SP)+1 → SP ,(direct) → SP
退栈指令
POP direct
七、字节交换指令(5条)
•XCH A,Rn ;(A)→ß(Rn)
•XCH A,direct ;(A)→ß(direct)
•XCH A,@Ri ;(A)→ß((Ri))•
八、半字节交换指令
•XCHD A,@Ri ;(A)0~3→ß((Ri)) 0~3
九、加器A与外部数据存贮器传送指令(4条)
•MOVX A,@DPTR ; ((DPTR))→A
•MOVX A,@ Ri ; ((Ri))→A i=0,1
•MOVX @ DPTR ,A ; (A)→( DPTR)
•MOVX @ Ri , A ; (A)→(Ri) i=0,1
十、查表指令
(i)MOVC A ,@ A+PC ;((A)+(PC))→A
• (ii)MOVC A , @A+ DPTR ;((A)+(DPTR))
算术运算指令
一、不带进位的加法指令(4条)
ADD A,Rn ;(A)+(Rn)→A
ADD A,direct ;(A)+(direct)→A
ADD A,@Ri ;(A)+((Ri))→A
ADD A,#data ;(A)+#data→A
二、带进位加法指令(4条)
ADDC A,Rn ;(A)+(Rn)+CY→A
ADDC A,direct ;(A)+(direct) +CY →A
ADDC A,@Ri ;(A)+((Ri)) +CY →A
ADDC A,#data ;(A)+ #data +CY →A
三、增量指令(5条)
INC A ;(A)+1 →A
•INC Rn ;(Rn)+1 → Rn
•INC direct ;(direct)+1 → direct
•INC @Ri ;((Ri))+1 →(Ri)
•INC DPTR ;(DPTR)+1 →DPTR
四、十进制调整指令(1条)
DA A
减法指令
一、带进位减法指令
SUBB A,Rn
SUBB A,direct
SUBB A,@Ri
SUBB A,#data
二、减1指令(4条)
DEC A
DEC direct
DEC @Ri
乘法指令
MUL AB
除法指令
DIV AB
逻辑运算指令
累加器A的逻辑操作指令
一、累加器A清0
CLR A
二、累加器A取反
CPL A
三、左环移指令
RL A
四、带进位左环移指令
RLC A
五、右环移指令
RR A
七、累加器ACC半字节交换指令
SWAP A
两个操作数的逻辑操作指令
逻辑与指令
ANL A,Rn
ANL A,direct
ANL A, @Ri
ANL A,#data
ANL direct ,A
ANL direct,#data
逻辑或指令
ORL A,Rn
ORL A,direct
ORL A, @Ri
ORL A,#data
ORL direct,A
ORL direct,#data
逻辑异或指令
XRL A,Rn
XRL A,direct
XRL A,@Ri
XRL A,#data
XRL direct,A
XRL direct,#data
位操作指令
位变量传送指令
MOV C,bit
MOV bit,C
位变量修改指令
CLR C
CLR bit
CPL C
CPL bit
SETB C
SETB bit
位变量逻辑与指令
ANL C,bit
ANL C,/bit
位变量逻辑或指令
ORL C,bit
ORL C,/bit
控制转移指令
无条件转移指令(4条)
1、 短跳转指令
AJMP addr11 ;先(PC)+2→PC ;addr11→PC10~0 ,(PC15~11)
2、 跳转指令
LJMP addr16 ;Addr16→PC
3、 转移指令
4、
SJMP rel ;先(PC)+2→PC;后(PC)+rel→PC
4、 寄存器加变址存器间接转移指令(散转指令)
JMP @A+DPTR ;(A)+(DPTR)→PC
条件转移指令(8条)
一、测试条件符合转移指令
JZ rel ; 当A=0 时,(PC)+rel→(PC)转移;当A≠0时,顺序执行。
JNZ rel ; 当A≠0 时,(PC)+rel→(PC)转移;当A=0时,顺序执行。
JC rel 如果进位标志CY为1,则执行转移;
JNC rel 如果进位标志CY为0,则执行转移;
JB bit, rel 如果直接寻址位的值为1,则执行转移;
JNB bit , rel 如果直接寻址位的值为0,则执行转移;
JBC bit , rel 如果直接寻址位的值为1,则执行转移;然后清“0”直接寻址位(bit)。
二、比较不相等转移指令
CJNE (目的操作数),(源操作数),rel
CJNE A,direct,rel
CJNE A,#data,rel
CJNE Rn,#data,rel
;若(Rn) >#data ,则(PC)+rel→PC,且0→CY;
;若(Rn) < #data ,则(PC)+rel→PC,且1→CY;
;若(Rn) = #data ,则顺序执行,且0→CY。
三、减1不为0转移指令
DJNZ Rn,rel ;(Rn)-1→Rn;
;若(Rn)≠0, 则(PC)+rel →PC;
;若(Rn) = 0, 则结束循环, 顺序执行
•DJNZ direct,rel ;(direct )-1→ direct ;
;若(direct)≠0,则(PC)+rel →PC;
;若(direct) = 0,则结束循环,顺序执行
调用和返回指令
一、短调用指令
ACALL addr11 ;(PC)+2→PC
;(SP)+1→SP,(PC 0~7)→(SP)
;(SP)+1→SP,(PC 8~15)→(SP)
;addr0~10→PC0~10,(PC11~15)不变
二、长调用指令
LCALL addr16 ;(PC)+3→PC
;(SP)+1→SP,(PC 0~7)→(SP)
;(SP)+1→SP,(PC 8~15)→(SP)
;addr0~15→PC
三、返回指令
1. 从子程序返回指令
RET ;((SP)) →PC 8~15 ,(SP) -1→SP
;((SP)) →PC 0~7 ,(SP) -1→SP
2. 中断服务程序返回指令
RETI ;((SP)) →PC 8~15 , (SP) -1→SP
; ((SP)) →PC 0~7 , (SP) -1→SP
;开放中断逻辑
四、空操作指令
NOP
标签:
原文地址:http://www.cnblogs.com/20135316wjq/p/4869553.html