前言
上一章我们说了汇编语言的基础,包含数据格式,寄存器以及操作数的标识方式,接下来我们就应该去认识一下hiU币按语言当红真难过的格各个指令了.这些指令大部署非常easy,可是组合在一起却能模拟出我们程序其中香烟的不论什么效果,确实非常奇妙.
数据传送指令
数据传送指令的目的是我了将一个数据从一个位置拷贝到还有一个位置.既然如此,那么数据传送至零就会包括一个源操作数和一个目的操作数,指令会将源操作数的值拷贝到目的操作数并覆盖.
数据传送指令一共能够分为5种,各自是mov,movs,movz,push以及pop.假设你多少懂一点编程语言的话,看名字就能知道一个大概,只是在这里我还是说一下各个指令的作用.
mov指令
mov指令的作用是将源操作数S中的数据拷贝到目的操作数D中,mov指令有一个数据格式和两个操作数,因此一般的形式为[movx S D].当中x为数据格式,S为源操作数,D为目的操作数.
举个简单的案例,比方我们有一条指令为movl %edx %eax.那么他的运行步骤例如以下图所看到的:
能够看到,在指令运行之后,%edx寄存器其中的内容就会被拷贝到%eax寄存器.须要一提的是,mov指令能够在后面加上不论什么数据格式,比方上面这一过程中,数据格式为4个字节,也就是双字.因此不难判断出,我们还能够使用movb和movw去复制一个字节或两个字节.
movs指令
movs指令的作用是将源操作数S中的数据做符号扩展后,再拷贝到目的操作数D中,movs指令有两个数据格式和两个操作数,因此一般的形式为[movsxy S D].当中x,y位数据格式,S为源操作数,D为目的操作数.当中x,y的组合一共同拥有三种,各自是bw,bl,wl,这三个组合代表的意思各自是单字节到双字节,单字节到双字以及双字节到双字.
还是举个样例,对于指令
movswl %dx %eax
来说,它的作用例如以下图:
这里为了看出来符号位的扩展,由于我们在这里使用了十六进制的整数表示方式.能够看到,movs指令将0x8FFF扩展以后存入%eax寄存器,当中%dx为寄存器%edx的后16位表示.
movz指令
movz指令的作用是将源操作数S做零扩展后,再拷贝到目的操作数中.它与movs指令十分相似,也有两个数据格式和两个操作数,因此一般形式为[movzxy S D].各个字母代表的含义和movsxy代表的一样.
还是看案例,对于指令
movzwl %dx %eax
来说,他的作用和上面的movs有何不同.
能够看出,movs和movz指令十分相似,仅仅是这里扩展后,目标寄存器%eax的前16位是0而不是1.
push指令
push指令与上面的mov族的指令不同,他的目的是操作数被固定为栈顶,因此他的指令其中没有目的操作数另外友谊县须要注意,他在进行复制操作之前,须要移动栈顶指针(-4).push指令的一般形式为[pushl S],其中l代表数据格式为双字,S为源操作数,目的操作数默觉得栈顶
案例,比方
pushl %edx
这条指令,他的任务是将%edx寄存器的值拷贝到栈顶.我们首先来看一下命令运行前,寄存器以及存储器的状态.
能够看到,寄存器%ebp和%esp分别指向帧指针和栈指针,而%esp实际上就是指向栈顶.因为如今栈顶位于-16的位置,因此若要将%dex压入栈,则现须要将栈顶移动到-20的位置,然后再进行赋值,移动后的状态例如以下所看到的:
能够看到,这里的栈指针的位置已经发生了变化,向下移动了四位,而且将%edx寄存器的值放入新的栈顶,因此pushl %edx指令就相当于以下两条指令:
subl $4,%esp
movl %edx,(%esp)
这里能够看出,事实上push指令做了一个隐藏操作,就是移动栈指针(-4),这一点希望能引起大家的注意.
坚持住,还有最后一个命令!
pop指令
pop指令与push指令的做的相反的操作,一个是入栈一个是出栈.对于pop指令来说,他的源操作数背负定位栈顶,相反,它会先进行复制操作,然后再移动栈指针.pop指令的一般形式为[popl D],当中l代表数据类型,D为目的操作数,源操作数默觉得栈顶.
案例,考虑
popl %edx
这条指令的效果,他会将栈顶的值弹出到寄存器%edx.首先来看运行之前,寄存器以及存储器的状态.
接下来运行pop指令时,会相减栈顶的值拷贝到%edx,然后在将栈指针移动(+4).我们来看一下它运行后的状态.
能够看到,之前栈顶的内容已经 被弹出到%edx寄存器,而且当前栈顶已经移动到了-16的位置,也就是进行了+4操作.因此popl %edx指令就相当于以下这两条指令:
movl (%esp),%edx
addl $4,%esp
这里可以看出,事实上popl指令也相同做了一个隐藏的操作,就是移动栈指针(+4).
说了非常多,貌似有人会说都是花架子,以下咱们就结合详细的案例来看看
数据复制演示样例
上面我们说了差点儿全部的复制指令,接下来的一小段代码,让我们来看一下这些数据指令,怎样完毕我们的程序操作.
simple(int *xp,int y){
int t=*xp;
*xp=y;
return t;
}
上面是一个简单的C语言程序,它当中包括了一些复制操作,我们来看看他的汇编代码.使用GCC -O1 -S sum.c来获取我们的汇编代码,并使用cat sum.c来查看一下:
.file "sum.c"
.text
.globl simple
.type simple, @function
simple:
pushl %ebp
movl %esp, %ebp
//以上为栈的建立部分
movl 8(%ebp), %edx
movl (%edx), %eax
movl 12(%ebp), %ecx
movl %ecx, (%edx)
//下面为栈的完毕部分
popl %ebp
ret
.size simple, .-simple
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
.section .note.GNU-stack,"",@progbits
分析这段汇编代码的时候,我们应该分为三个部分来看待,首先是栈的建立,然后是使用,最后是完毕部分.看到这里,里面差点儿全是数据复制指令,我们先开看看栈的建立部分.
事实上地狱与一開始pushl和movl指令来说,他们主要做了两件事.第一个是将原来的帧指针备份到栈顶,然后再将栈指针和帧指针统一指向这个新的栈顶,也就是完毕了一个新站的建立.它在完毕后,栈的状态例如以下所看到的.
能够看到,寄存器%ebp和寄存器%esp都指向当前栈指针的位置,当中变量xp位于+8的位置,而y位于+12的位置.因为xp是一个指针变量,因此它会指向一个内存中的区域,当中的值为*xp.
了解完寄存器和存储器的转台,此时栈已经建立完成,接下来我们看紧接着的一句汇编代码的作用.
movl 8(%ebp), %edx
这一句将内存地址为%ebp+8的值拷贝到%edx,非常明显,从上面的图中能够看出,%ebp+8这个位置存储着xp变量.这一句指令做了一个简单的操作,就是将xo提取到%edx寄存器,例如以下所看到的:
此时已经将%edx的值改变为了变量xp,看接下来的一句操作.
movl (%edx) ,%eax
这一句话将内存地址为%edx的值赋给类寄存器%eax,并准备返回值.此时%edx寄存器的值已经改变为了xp变量,因此(%edx)事实上就是*xp,而%eax寄存器一般回座位函数的返回值,因此他事实上取代了暂时变量t.运行后的状态例如以下所看到的.
此时事实上已经完毕了程序中的int t=*xp以及为return t准备好了返回值,接下来的一句汇编代码也非常easy,例如以下.
movl 12(%ebp),%ecx
它的作用是将地址为%ebp+12的值拷贝到寄存器%ecx,从图中能够看出,%ebp+12就是存储的变量y.因此他的作用就是将y拷贝到寄存器%ecx,例如以下所看到的:
上面这一步非常easy,我们来看最后一步操作,例如以下:
movl %ecx ,(%edx)
他的作用是将%ecx寄存器的值拷贝到内存中%edx的位置.此时%ecx的值为y,而%edx中为xp,因此目的操作数则为xp运行的位置,也就是*xp.这一句话运行的就是程序代码其中*xp=y这个操作,它运行后的状态例如以下所看到的:
能够看到,在运行了*xp=y以后,xp指针所指向的位置,其值已经变成了y.此时程序当中已经基本运行完毕,剩下的工作也就是栈的完毕操作了,也就是popl指令.在栈完毕之后,也就是pop指令运行之后,当前帧会恢复到调用者的帧上面去,例如以下所看到的:
此时当前帧已经恢复到了调用者的帧,最后ret指令会改变程序计数器(PC)的值,然后跳出子函数,继续运行调用者其中的代码.到此,我们的数据复制实力就完毕了,虽然着了样例不难,可是非常可以说明问题,仅仅要能了解了这个过程,相信一些复杂的汇编指令也仅仅是分析的时间长点罢了.
小小的结一下
一般人家都说,字不如表,表不如图,有了这么多的好图,你肯定想求个种子啥的对吧,这不是让你看图猜链接....