数据传送指令详解
前言
上一章我们说了汇编语言的基础,包括数据格式,寄存器以及操作数的标识方式,接下来我们就应该去认识一下hiU币按语言当红真难过的格各个指令了.这些指令大部署很简单,但是组合在一起却能模拟出我们程序当中香烟的任何效果,确实很神奇.
数据传送指令
数据传送指令的目的是我了将一个数据从一个位置复制到另一个位置.既然如此,那么数据传送至零就会包含一个源操作数和一个目的操作数,指令会将源操作数的值复制到目的操作数并覆盖.
数据传送指令一共可以分为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准备好了返回值,接下来的一句汇编代码也很简单,如下.
movl 12(%ebp),%ecx
它的作用是将地址为%ebp+12的值复制到寄存器%ecx,从图中可以看出,%ebp+12就是存储的变量y.因此他的作用就是将y复制到寄存器%ecx,如下所示:
上面这一步很简单,我们来看最后一步操作,如下:
movl %ecx ,(%edx)
他的作用是将%ecx寄存器的值复制到内存中%edx的位置.此时%ecx的值为y,而%edx中为xp,因此目的操作数则为xp执行的位置,也就是*xp.这一句话执行的就是程序代码当中*xp=y这个操作,它执行后的状态如下所示:
可以看到,在执行了*xp=y以后,xp指针所指向的位置,其值已经变成了y.此时程序其中已经基本运行完毕,剩下的工作也就是栈的完成操作了,也就是popl指令.在栈完成之后,也就是pop指令执行之后,当前帧会恢复到调用者的帧上面去,如下所示:
此时当前帧已经恢复到了调用者的帧,最后ret指令会改变程序计数器(PC)的值,然后跳出子函数,继续执行调用者当中的代码.到此,我们的数据复制实力就完成了,尽管着了例子不难,但是很能够说明问题,只要能了解了这个过程,相信一些复杂的汇编指令也只是分析的时间长点罢了.
小小的结一下
一般人家都说,字不如表,表不如图,有了这么多的好图,你肯定想求个种子啥的对吧,这不是让你看图猜链接....
版权声明:本文为博主原创文章,未经博主允许不得转载。