小标题
1.x64下反汇编
2.调试64位4参数中寄存器的值
3.调试64位中5参数及多次调用函数栈时栈区结构
4.64位中少于4参数情况
1.x64下反汇编
64位下函数的调用约定全部用FASTCALL,就是前4个参数依次用rcx,rdx,r8,r9传递,多余的参数从右至左压参。
x86下栈帧是比较清楚的,但是x64位下栈帧的资料好少。然后调了下熟悉栈帧的结构。
int Add(int a,int b,int c,int d); int _tmain(int argc, _TCHAR* argv[]) { int a = 0; Add(1,2,3,4); return 0; } int Add(int a,int b,int c,int d) { int xx = a+b+c+d; int yy = a+b-c-d; int zz = -a-b+c+d; return xx; }
使用Vs2010 ,64位下调试,打开寄存器窗口,Alt+8 反汇编
可以看到首先将1,2,3,4放在寄存器中,然后调用call指令,call指令可以分解为将下一条指令压参,然后jmp到函数地址
在执行push指令的时候,RSP-8
000000013F931049 mov r9d,4 000000013F93104F mov r8d,3 000000013F931055 mov edx,2 000000013F93105A mov ecx,1 000000013F93105F call Add (13F931005h) //指令为 push rip ;RSP-8 // jmp Add
Add函数的反汇编代码如下:
可以看到前4句将寄存器中传递的参数赋值给rsp+8h,rsp+10h,rsp+18h,rsp+20h
如果多余4个参数,其余的依次放入rsp+28h 之后
然后保存前栈帧栈底,开辟栈区保存局部变量,由于是三个变量12字节,对齐内存是16字节,sub rsp,10h
初始化栈区,rep stos指令
int Add(int a,int b,int c,int d) { 000000013F251080 mov dword ptr [rsp+20h],r9d 000000013F251085 mov dword ptr [rsp+18h],r8d 000000013F25108A mov dword ptr [rsp+10h],edx 000000013F25108E mov dword ptr [rsp+8],ecx 000000013F251092 push rdi //保存前栈底 RSP-8 000000013F251093 sub rsp,10h //开辟栈区 16字节 RSP-10h 000000013F251097 mov rdi,rsp //新栈帧栈底rdi=rsp 000000013F25109A mov ecx,4 //循环次数 000000013F25109F mov eax,0CCCCCCCCh 000000013F2510A4 rep stos dword ptr [rdi] //将rdi开始赋值eax中的值,循环4次 000000013F2510A6 mov ecx,dword ptr [rsp+20h] //此处是第一个参数a int xx = a+b+c+d; 000000013F2510AA mov eax,dword ptr [b] 000000013F2510AE mov ecx,dword ptr [a] 000000013F2510B2 add ecx,eax 000000013F2510B4 mov eax,ecx 000000013F2510B6 add eax,dword ptr [c] 000000013F2510BA add eax,dword ptr [d] 000000013F2510BE mov dword ptr [rsp],eax //rsp 保存 xx int yy = a+b-c-d; 000000013F2510C1 mov eax,dword ptr [b] 000000013F2510C5 mov ecx,dword ptr [a] 000000013F2510C9 add ecx,eax 000000013F2510CB mov eax,ecx 000000013F2510CD sub eax,dword ptr [c] 000000013F2510D1 sub eax,dword ptr [d] 000000013F2510D5 mov dword ptr [yy],eax //rsp+4 保存yy int zz = -a-b+c+d; 000000013F2510D9 mov eax,dword ptr [a] 000000013F2510DD neg eax 000000013F2510DF sub eax,dword ptr [b] 000000013F2510E3 add eax,dword ptr [c] 000000013F2510E7 add eax,dword ptr [d] 000000013F2510EB mov dword ptr [zz],eax //rsp+8 保存 return xx; 000000013F2510EF mov eax,dword ptr [rsp] //将返回值保存在eax寄存器中 } 000000013F2510F2 add rsp,10h //恢复开辟的栈区 000000013F2510F6 pop rdi //恢复前栈帧的栈底 000000013F2510F7 ret //pop rip 将之前保存的call下一条指令弹出给rip , 继续执行 //RSP - 8 等于调用call之前的值
2.调试64位4参数中寄存器的值
下面是调试中的一些寄存器的值
此时的RSP和RDI都是_tmain函数的栈顶和栈底,alt+8反汇编
此时RSP经过 call 中的push rip 减去8,push edi 减去8,sub rsp,10h 一共减去20h
rdi赋值为rsp,为当前Add的栈底
rdi经过rep stos 指令将eax中值初始化到rdi中,共4*4字节,rdi初始化之后加10h
此时我们看内存中的情况如上图
栈帧情况如下
3.调试64位中5参数及多次调用函数栈时栈区结构
我们在试试5参数的函数调用情况,同时我们知道函数会把4个寄存器中的值赋值到栈上面的区域,要开辟4*8=20h的区域,在调试的时候没有发现对于rsp的操作
于是猜测是在上一个函数中已经开辟好了额外的空间存储参数的数据。
int _tmain(int argc, _TCHAR* argv[]) { int a = 0; Sub(1,2,3,4,5); return 0; } int Add(int a,int b,int c,int d) { int xx = a+b+c+d; int yy = a+b-c-d; int zz = -a-b+c+d; return xx; } int Sub(int a,int b,int c,int d,int e) { int xx = a+b+e+d; int yy = a+b-c-d; int zz = -a-b+c+d; Add(b,c,d,e); return xx; }
我们使用Sub()5个参数的函数,同时在Sub函数中调用Add函数
Sub(1,2,3,4,5); 000000013F4F2EF9 mov dword ptr [rsp+20h],5 //当前rsp + 20 就是存储4个参数之后的位置 000000013F4F2F01 mov r9d,4 000000013F4F2F07 mov r8d,3 000000013F4F2F0D mov edx,2 000000013F4F2F12 mov ecx,1 000000013F4F2F17 call Sub (13F4F100Fh) int Sub(int a,int b,int c,int d,int e) { 000000013FC32FB0 mov dword ptr [rsp+20h],r9d //当前的rsp 比之前操作的 -8 ,所以这个位置是 第五个参数的前面一个 000000013FC32FB5 mov dword ptr [rsp+18h],r8d 000000013FC32FBA mov dword ptr [rsp+10h],edx 000000013FC32FBE mov dword ptr [rsp+8],ecx 000000013FC32FC2 push rdi 000000013FC32FC3 sub rsp,30h //这个地方是调用Add函数 额外分配的20h 存储4个参数 000000013FC32FC7 mov rdi,rsp 000000013FC32FCA mov ecx,0Ch 000000013FC32FCF mov eax,0CCCCCCCCh 000000013FC32FD4 rep stos dword ptr [rdi] 000000013FC32FD6 mov ecx,dword ptr [rsp+40h]
我在函数中调用了上面的Add()函数,结果rsp - 30 开辟栈空间,这是为了调用Add的时候 多开的4*8 保存4个寄存器中的值在栈中。
其中局部变量保存在 rsp + 20h 的地方,依次保存xx,yy,zz和没用到的4字节
在Add函数的栈区,我们果然看到了Sub开辟栈区的20h地址空间,存储了传递给寄存器的参数
4.64位中少于4参数情况
还有就是传递少于4个参数的情况,也会开辟 4*8的区域
没有传递参数也会开辟 4*8区域。