[音乐] 下面我们来看一下入口参数的位置。
这个是一个示例。
说明在call指令之前准备了三个参数, 都是用movl指令实现的。
在参数传递的 时候,通常是最右边的参数先入栈。
然后,一个一个,最后是最左边的参数。
所以在栈里面,我们可以看到在这个栈帧当中, 最上面的是最右边的参数,
最下边的是最左边的参数。
因此我们可以看到在这个里面, 第一个参数是ESP所指的位置。
ESP所指的位置,然后第二个参数是ESP加4的位置。
就是在这个地方加4,ESP加4的位置。
第三个参数呢,是ESP加8,加8的位置。
这是ESP自己 所在的那个位置。
参数准备好了以后,我们就调用call指令, 转到被调用过程去执行。
第一件事情,是先要在栈里面长出一个位置来,
这个位置是为了存放返回地址的。
所以减4 使得ESP指向这个位置。
也就是空出一个位置,这个位置空出来以后就把返回地址放到这个位置上去。
就是返回地址放到ESP所指的那个位置,就是存储单元。
第三件事情,那就是把调用的目标
地址,就是被调用过程转向执行的那个目标地址。
也就是这个被调用过程的首地址送到EIP寄存器里面去。
因为在IA-32当中用EIP寄存器存放将要执行
的指令的地址,所以我们把这个地址送到这个里面去以后,
这call指令执行完了以后,紧接着就会执行add函数 的第一条指令,call指令执行完了以后,实际上
在这个栈帧当中已经存放了返回地址,这个
返回地址我们前面讲过,它一定是call指令的下一条指令的地址。
这个返回地址呢,就被送到了栈里面。
这样的话,转到这个add去执行的时候,是从第一条指令
开始执行,前面我们讲过每一个过程,包括这个add过程, 它的第一条指令,总是pushl指令。
这个pushl指令是把旧的EBP的值压栈。
这边就是EBP的值,这个旧的EBP的值实际上是
调用它的这个调用过程的栈底的这个指针。
紧接着呢,就是执行一条movl指令。
因为压栈以后,这个栈顶,就是ESP指向了这个位置。
然后再把ESP的值赋给EBP,那么使得EBP呢也 指向这个位置,因此我们可以看到当前
这个EBP在这个位置,因为这放了一个返回地址,所以 入口参数1这个位置就是EBP加8。
然后入口参数2的这个位置就是EBP加12, 3的这个位置就是EBP加16。
这个就是入口参数的位置,因为在IA-32
当中如果这个参数类型是char类型,
或者short类型,它也会被分配4个字节,所以这个入口参数 总是在加8、
加12、 加16的这样的位置上面。
下面我们来看一个例子,在这个例子当中 实现的是两个变量的交换,这里面
这个函数当中有两个局部变量a和b,这两个 局部变量呢,它在调换之前我们先把它打印出来。
然后呢实现调换,就是用swap函数 把两个变量的位置换一下,然后再把它打印出来。
这边也是,这样子的,只不过
交换的时候,一个是按地址传递参数的,
一个呢是按值传递参数的,因此在交换函数 当中,swap函数当中,这边取的就是内容。
这边都是取的内容,而这边呢直接是取的值。
仅这个不一样,其它都是一样的。
显然这边是按地址传递参数的方式,这边是按值传送参数的方式。
在这个地方,我们看一下,它的执行结果是什么呢?
我们可以看到程序1的执行结果是:a等于15,b等于22。
调换位置以后,再打印出来的时候,a呢就 等于22,b就等于15了,也就是说它真正实现了交换。
程序2的执行结果打印出来以后我们可以看到,a和b的位置并没有交换,就是swap语句
没有起作用,为什么这两个程序,它的结果不一样,我们来分析一下原因。
这边是按地址传递参数的方式,我们可以看到这个参数传的是地址。
传地址的时候,用的指令是这样的指令。
在这,局部变量b的位置,在EBP减8。
局部变量a的位置呢,是在EBP减4,因为是按地址传递参数的,
所以我们用的指令是leal指令。
这个是装入有效地址,这样的一条指令,它是把
EBP减8里面的内容作为地址放到EAX里面去。
所以这个地方实际上取的是地址。
而不是把EBP减8作为地址再去取内容放到EAX里面。
然后这个再作为参数送到ESP加4那个位置。
现在这个ESP是指向这个地方,所以加4的那个位置就是在这。
因此这两条语句实现的是把b的地址 作为参数传递过来的,同样的下面这两条指令是把
a的地址送到ESP,送到这来,下面紧接着呢执行call指令
去调用这个函数,这个函数 实际上在执行第一条指令转过来之前,call指令
会把返回地址压栈,然后呢执行pushl指令,执行pushl指令的话实际上就是
把EBP在main里面的值,也就是这个地方的位置值压栈。
紧接着让EBP指向这个位置。
在这里面我们用到了EBX,所以EBX我们前面讲过是
被调用者保存寄存器,在这里面如果用到EBX的话,EBX一定要先保存,先入栈。
这边我们刚才讲过了,在参数传递完 以后一旦形成EBP栈帧的这个底部,
那么EBP加8就是第一个参数,EBP加12就是第二个参数的位置。
这边是EBX。
EBX入栈要保存。
两条语句,实际上是吧EBP加上8,
这个内容送到EDX里面去,然后EDX里面实际上是
一个地址,这个地址里面的内容实际上就是EBP
加8,也就是a的地址,a的地址放到EDX。
这个EDX里面的内容当然就是15。
把15送到了ECX里面去,ECX这个寄存器的内容现在是15。
下面的这两条语句,就是把EBP加12,EBP加12就是b的地址。
b的地址放到EAX,所以这个加了括号就表示EAX的内容送到EBX。
EAX里面放的是b的地址,所以b的地址里面的内容当然就是b,就是22。
22送到了EBX里面,所以EBX这个寄存器 里面的值呢就是22,然后下面的这条语句,那就
是把EBX再送到EDX所指的那个内存里面去。
实际上是就把EBX里面的内容,就是22。
22送到a这个地址所指出的那个地方,实际上就是把22
替换到这个地方来,这个地方就变成22,下面这条语句呢, 就把ECX的内容,那ECX里面现在是15。
15送到EAX里面,而EAX里面实际上是b的地址。
所以就把15,ECX的内容这个15送到了b那个
地方去,就是15送到这,这样的话a和b就发生了交换。
局部变量当中进行了交换,我们在printf打印的时候,a就变成22,b就变成15。
这是按地址传递参数的情况,如果是按值传递参数的话, 我们可以看到参数准备的时候,就不是用的
取有效地址这样的一条指令,而是直接是取内容。
这个内容实际上就是EBP减8,也就是22。
把22放到ESP加4的位置。
就在这,然后这个呢是EBP减4,这边就是15。
把15送到ESP的位置,ESP这边就是15。
完了以后就去call,call完了时候那当然这个地方 就是相当于形成swap的这个栈帧。
在这儿ESP呢就指向了
swap的栈底,EBP加8是第一个参数,EBP加12是第二个参数。
在这就是把EBP加8的这个15送到EDX。
然后呢再把EBP加12,这边的22送到EAX。
这边的话,就把EAX送到EBP
这个加8那个位置,也就是把22送到这个位置。
下面呢是把 15送到这个位置,所以我们可以看到,交换的是入口参数所在的位置,而不是交换的
局部变量所在的位置,交换的局部变量a、 b这个位置当中,
还是15、 22,只是把入口参数这个地方进行交换了,因为这是按值传递。
因此我们打印局部变量的时候,a还是等于15,b还是等于22。
所以没有进行交换,只把入口参数的地方交换了。
这个就是按值传递和按地址传递它们的差别。
[音乐] [音乐]