尽管可以使用相对于栈顶(ESP寄存器)的偏移量来引用局部变量’ 但是因为ESP寄存器经常变化’所以用这种方法引用同—个局部变量的偏移量是不固定的。
这种不确定性对于CPU来说不成什么问题但在调试时’如果要跟踪这样的代码’那么很容易就被转得头晕眼花’
因为现实的函数大多有多个局部变量’可能还有层层嵌套的循环’栈指针变化非常频繁°
为了解决以上问题’x86CPU设计了另一个寄存器’这就是EBP寄存器。
EBP的全称是 ExtendedBasePointer。使用EBP寄存器’函数可以把自己将要使用的栈空间的基地址记录下来’ 然后使用这个基地址来引用局部变量和参数°
在同_函数内’EBP寄存器的值是保持不变的’ 这样函数内的局部变量便有了一个固定的参照物。
通常一个函数在入口处将当时的EBP值(即父函数的EBP)压人栈’然后把ESP值(栈顶)赋给EBP寄存器’这样EBP寄存器中的地址就是进人本函数时的栈顶地址,
这—地址上面(地址值递减方向)的空间便是这个函数将要使用的栈空间’它下面(地址值递增方向)是父函数使用的空间。
如此设置EBP寄存器后’便可以使用EBP寄存器加正偏移量来弓|用父函数的内容
使用EBP加负偏移量来引用本函数的局部变量°
比如 ebp: 存放调用函数前ebp的值
EBP+4指向的是CALL指令压人的函数返回地址;
EBP+8是父函数压在栈上的第—个参数’
EBP+0xC是第二个参数’以此类推;EBP-N是第N个局部变量的起始地址
因为在将栈顶地址(ESP寄存器的值)赋给EBP寄存器之前先把|日的EBP寄存器的值保存在栈中’
所以EBP寄存器所指向的栈单元中保存的是前一个EBP寄存器的值’
这通常也就是父函数的EBP寄存器的值。类似的父函数的EBP寄存器所指向的栈单元中保存的是更上一层函数的EBP寄存器的值’
以此类推’直到当前线程的最顶层函数。这也正是栈回溯的基本原理°
x86, vc++
36: // TODO: add construction code here, 37: // Place all significant initialization in InitInstance 38: testEBP(101,201, 301); 00402BBC push 12Dh 00402BC1 push 0C9h 00402BC6 push 65h 00402BC8 mov ecx,dword ptr [ebp-10h] 00402BCB call CEx08aApp::testEBP (00402c90) eip 指向这里,马上就要调用函数地址:00402c90 39: } 00402BD0 mov dword ptr [ebp-4],0FFFFFFFFh //return address 00402BD7 mov eax,dword ptr [ebp-10h] 00402BDA mov ecx,dword ptr [ebp-0Ch] 00402BDD mov dword ptr fs:[0],ecx 00402BE4 pop edi 00402BE5 pop esi 00402BE6 pop ebx 00402BE7 add esp,50h 00402BEA cmp ebp,esp 00402BEC call _chkesp (00405ea6) 00402BF1 mov esp,ebp 00402BF3 pop ebp 00402BF4 ret ------------------------------------------------------------ EAX = 004091C8 EBX = 003DA000 ECX = 004091C8(this) EDX = 004091C8 ESI = 00405F20 EDI = 0019FE1C EIP = 00402BCB (下条指令) ESP = 0019FDC0 EBP = 0019FE28 EFL = 00000246 --- test\ex08a\ex08a.cpp ----------------------------------------------------------------------------------------------------------- EAX = 004091C8 EBX = 003DA000 ECX = 004091C8 EDX = 004091C8 ESI = 00405F20 EDI = 0019FE1C EIP = 00402C90 ESP = 0019FDBC 与 0019FDC0 差4,一个地址的长度 EBP = 0019FE28 EFL = 00000246 esp地址处内存为: 0019FDBC D0 2B 40 00 65 00 00 00 C9 00 00 00 2D 01 00 \r\n00 字节序反转 00402BD0(返回地址), 00000065(101)esp+4, 000000C9(201)esp+8, 0000012D(301)esp + 0n12 0019FD62 40 00 C8 91 40 00 1C FE 19 00 20 5F 40 00 00 @.葢@..... _@.. 0019FD71 A0 3D 00 CC CC CC CC CC CC CC CC CC CC CC CC .=.烫烫烫烫烫烫 0019FD80 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 烫烫烫烫烫烫烫. 0019FD8F CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 烫烫烫烫烫烫烫. 0019FD9E CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 烫烫烫烫烫烫烫. 0019FDAD CC CC CC CC CC CC CC CC CC CC CC 28 FE 19 00 烫烫烫烫烫.(... 0019FDBC D0 2B 40 00 65 00 00 00 C9 00 00 00 2D 01 00 .+@.e.......-.. 0019FDCB 00 7C FE 19 00 20 5F 40 00 00 A0 3D 00 CC CC .|... _@...=.烫 40: 41: CEx08aApp::testEBP(int a, int b, int c) 42: { 00402C90 push ebp 00402C91 mov ebp,esp 00402C93 sub esp,44h 00402C96 push ebx 00402C97 push esi 00402C98 push edi 00402C99 push ecx 00402C9A lea edi,[ebp-44h] 00402C9D mov ecx,11h 00402CA2 mov eax,0CCCCCCCCh 00402CA7 rep stos dword ptr [edi] //重复11次,上面的 cccc指令,修正代码区 00402CA9 pop ecx 00402CAA mov dword ptr [ebp-4],ecx 43: printf("a=%d, b=%d, c=%d", a, b, c); 00402CAD mov esi,esp 00402CAF mov eax,dword ptr [ebp+10h] ////取参数c 00402CB2 push eax 00402CB3 mov ecx,dword ptr [ebp+0Ch] //取参数b 00402CB6 push ecx 00402CB7 mov edx,dword ptr [ebp+8] //取参数a 00402CBA push edx 00402CBB push offset string "a=%d, b=%d, c=%d" (0040746c) 内存:0040746C 61 3D 25 64 2C 20 62 3D 25 64 2C 20 63 3D 25 a=%d, b=%d, c=% 0040747B 64 00 00 00 00 4C 6F 63 61 6C 20 41 70 70 57 d....Local AppW 00402CC0 call dword ptr [__imp__printf (0040aa4c)] 00402CC6 add esp,10h //返回地址 00402CC9 cmp esi,esp 00402CCB call _chkesp (00405ea6) 44: } 00402CD0 pop edi 00402CD1 pop esi 00402CD2 pop ebx 00402CD3 add esp,44h 00402CD6 cmp ebp,esp 00402CD8 call _chkesp (00405ea6) 00402CDD mov esp,ebp 00402CDF pop ebp 00402CE0 ret 0Ch --- No source file ---------------------------------------------------------------------------------------------------------------------------------- 00402CE3 int 3 ---------------------- EAX = 0000012D EBX = 003DA000 ECX = 000000C9 EDX = 00000065 ESI = 0019FD68 EDI = 0019FDB8 EIP = 1023B980 ESP = 0019FD54 地址:0019FD54 (C6 2C 40 00)返回地址 (6C 74 40 00)格式字符串 (65 00 00 00)参数1 (C9 00 00 00)参数2 EBP = 0019FDB8 EFL = 00000206 40: int __cdecl printf ( 41: const char *format, 42: ... 43: ) 44: /* 45: * stdout 'PRINT', 'F'ormatted 46: */ 47: { 1023B980 push ebp 1023B981 mov ebp,esp 1023B983 sub esp,0Ch ...
X64位 寄存器传参,然后通过RSP获取被调用函数的局部变量
0:011> !clrstack OS Thread Id: 0x3f70 (11) Child SP IP Call Site 000000652b9fd6a8 00007ffb404f6e70 TestThreadCrashFormsApplication1.Form1.testEBP(System.Object, System.Object) //被调用函数 000000652b9fd6b0 00007ffb404f6e41 TestThreadCrashFormsApplication1.Form1.button1_Click(System.Object, System.EventArgs) //调用者凼数 000000652b9fd710 00007ffb595f4747 System.Windows.Forms.Control.OnClick(System.EventArgs) 0:011> dq 000000652b9fd6b0-8 //返回地址 00000065`2b9fd6a8 00007ffb`404f6e41 000001ef`03e1ebc0 0:011> dq 000000652b9fd6b0 //this -> from本身 00000065`2b9fd6b0 000001ef`03e1ebc0 000001ef`03e39010 0:011> !do 000001ef`03e1ebc0 (mov rcx,qword ptr [rbp+60h]) 调用者将this指针 通过RCX传到 被调用者 Name: TestThreadCrashFormsApplication1.Form1 0:011> dq 000000652b9fd6b0+8 (mov r8,qword ptr [rbp+28h]) 00000065`2b9fd6b8 000001ef`03e39010 000001ef`03e39028 0:011> !do 000001ef`03e39010 Name: System.Object 0:011> dq 000000652b9fd6b0+0n16 mov rdx,qword ptr [rbp+30h] //两个对象的传递, r8,rdx 00000065`2b9fd6c0 000001ef`03e39028 00000000`00000000 0:011> !do 000001ef`03e39028 Name: System.Object 0:011> !U 00007ffb404f6e41 //反汇编调用者函数 Normal JIT generated code TestThreadCrashFormsApplication1.Form1.button1_Click(System.Object, System.EventArgs) Begin 00007ffb404f6dc0, size 8b 00007ffb`404f6dc0 55 push rbp 00007ffb`404f6dc1 57 push rdi 00007ffb`404f6dc2 56 push rsi 00007ffb`404f6dc3 4883ec40 sub rsp,40h 00007ffb`404f6dc7 488bec mov rbp,rsp 00007ffb`404f6dca 488bf1 mov rsi,rcx 00007ffb`404f6dcd 488d7d28 lea rdi,[rbp+28h] 00007ffb`404f6dd1 b906000000 mov ecx,6 00007ffb`404f6dd6 33c0 xor eax,eax 00007ffb`404f6dd8 f3ab rep stos dword ptr [rdi] 00007ffb`404f6dda 488bce mov rcx,rsi 00007ffb`404f6ddd 48894d60 mov qword ptr [rbp+60h],rcx 00007ffb`404f6de1 48895568 mov qword ptr [rbp+68h],rdx 00007ffb`404f6de5 4c894570 mov qword ptr [rbp+70h],r8 00007ffb`404f6de9 833db8d1080000 cmp dword ptr [00007ffb`40583fa8],0 00007ffb`404f6df0 7405 je 00007ffb`404f6df7 00007ffb`404f6df2 e8d95ca35f call clr!JIT_DbgIsJustMyCode (00007ffb`9ff2cad0) 00007ffb`404f6df7 90 nop 00007ffb`404f6df8 48b9d85df19cfb7f0000 mov rcx,offset mscorlib_ni!CheckEnvironmentVariableName+0x38 (00007ffb`9cf15dd8) (MT: System.Object) 00007ffb`404f6e02 e859b7525f call clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`9fa22560) 00007ffb`404f6e07 48894530 mov qword ptr [rbp+30h],rax 00007ffb`404f6e0b 488b4d30 mov rcx,qword ptr [rbp+30h] 00007ffb`404f6e0f e80c44f95c call mscorlib_ni!System.Object..ctor (00007ffb`9d48b220) 00007ffb`404f6e14 48b9d85df19cfb7f0000 mov rcx,offset mscorlib_ni!CheckEnvironmentVariableName+0x38 (00007ffb`9cf15dd8) (MT: System.Object) 00007ffb`404f6e1e e83db7525f call clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`9fa22560) 00007ffb`404f6e23 48894528 mov qword ptr [rbp+28h],rax 00007ffb`404f6e27 488b4d28 mov rcx,qword ptr [rbp+28h] 00007ffb`404f6e2b e8f043f95c call mscorlib_ni!System.Object..ctor (00007ffb`9d48b220) 00007ffb`404f6e30 488b4d60 mov rcx,qword ptr [rbp+60h] 00007ffb`404f6e34 488b5530 mov rdx,qword ptr [rbp+30h] 00007ffb`404f6e38 4c8b4528 mov r8,qword ptr [rbp+28h] //寄存器传参 00007ffb`404f6e3c e8ffdcffff call 00007ffb`404f4b40 (testEBP) //这里调用子函数 00007ffb`404f6e41 90 nop //返回地址 00007ffb`404f6e42 90 nop 00007ffb`404f6e43 488d6540 lea rsp,[rbp+40h] 00007ffb`404f6e47 5e pop rsi 00007ffb`404f6e48 5f pop rdi 00007ffb`404f6e49 5d pop rbp 00007ffb`404f6e4a c3 ret -------------------- 0:011> !U /d 00007ffb404f6e70 //反汇编子函数 Normal JIT generated code TestThreadCrashFormsApplication1.Form1.testEBP(System.Object, System.Object) Begin 00007ffb404f6e70, size 87 00007ffb`404f6e70 55 push rbp 00007ffb`404f6e71 57 push rdi 00007ffb`404f6e72 56 push rsi 00007ffb`404f6e73 4883ec50 sub rsp,50h 00007ffb`404f6e77 488bec mov rbp,rsp 00007ffb`404f6e7a 488bf1 mov rsi,rcx 00007ffb`404f6e7d 488d7d28 lea rdi,[rbp+28h] 00007ffb`404f6e81 b90a000000 mov ecx,0Ah 00007ffb`404f6e86 33c0 xor eax,eax 00007ffb`404f6e88 f3ab rep stos dword ptr [rdi] 00007ffb`404f6e8a 488bce mov rcx,rsi //下面将寄存器的值保存到父函数开辟的栈内存中,通过这些数据的地址就能看到,它们的地址比当前栈帧地址要高。 00007ffb`404f6e8d 48894d70 mov qword ptr [rbp+70h],rcx //0:011> dq 000000652b9fd6b0 -8(返回地址) -0n24(前三个push)- 50(局部栈) + 70 ;00000065`2b9fd6b0 000001ef`03e1ebc0 000001ef`03e39010 00007ffb`404f6e91 48895578 mov qword ptr [rbp+78h],rdx //0:011> dq 000000652b9fd6b0 -8 -0n24- 50 + 78; 00000065`2b9fd6b8 000001ef`03e39010 000001ef`03e39028 00007ffb`404f6e95 4c898580000000 mov qword ptr [rbp+80h],r8 //0:011> dq 000000652b9fd6b0 -8 -0n24- 50 + 80 ; 上层是RDX传进来的; 00000065`2b9fd6c0 000001ef`03e39028 00000000`00000000 00007ffb`404f6e9c 833d05d1080000 cmp dword ptr [00007ffb`40583fa8],0 00007ffb`404f6ea3 7405 je 00007ffb`404f6eaa 00007ffb`404f6ea5 e8265ca35f call clr!JIT_DbgIsJustMyCode (00007ffb`9ff2cad0) 00007ffb`404f6eaa 90 nop 00007ffb`404f6eab 488b4d78 mov rcx,qword ptr [rbp+78h] 00007ffb`404f6eaf 48894d40 mov qword ptr [rbp+40h],rcx 00007ffb`404f6eb3 488b8d80000000 mov rcx,qword ptr [rbp+80h] 00007ffb`404f6eba 48894d38 mov qword ptr [rbp+38h],rcx 00007ffb`404f6ebe 488b4d40 mov rcx,qword ptr [rbp+40h] 00007ffb`404f6ec2 488b4540 mov rax,qword ptr [rbp+40h] 00007ffb`404f6ec6 488b00 mov rax,qword ptr [rax] 00007ffb`404f6ec9 488b4040 mov rax,qword ptr [rax+40h] 00007ffb`404f6ecd ff10 call qword ptr [rax] 00007ffb`404f6ecf 48894530 mov qword ptr [rbp+30h],rax 00007ffb`404f6ed3 488b4d38 mov rcx,qword ptr [rbp+38h] 00007ffb`404f6ed7 488b5530 mov rdx,qword ptr [rbp+30h] 00007ffb`404f6edb e84055f85c call mscorlib_ni!System.String.Concat (00007ffb`9d47c420) 00007ffb`404f6ee0 48894528 mov qword ptr [rbp+28h],rax 00007ffb`404f6ee4 488b4d28 mov rcx,qword ptr [rbp+28h] 00007ffb`404f6ee8 e8b3a7f55c call mscorlib_ni!System.Console.WriteLine (00007ffb`9d4516a0) 00007ffb`404f6eed 90 nop 00007ffb`404f6eee 90 nop 00007ffb`404f6eef 488d6550 lea rsp,[rbp+50h] //开始平栈 00007ffb`404f6ef3 5e pop rsi 00007ffb`404f6ef4 5f pop rdi 00007ffb`404f6ef5 5d pop rbp 00007ffb`404f6ef6 c3 ret 0:011> kv # Child-SP RetAddr : Args to Child 14 00000065`2b9fd6a8 00007ffb`404f6e41 : 000001ef`03e1ebc0 000001ef`03e39010 000001ef`03e39028 00000000`00000000 : 0x00007ffb`404f6e70 testEBP 15 00000065`2b9fd6b0 00007ffb`595f4747 : 000001ef`03e1ebc0 000001ef`03e1f6f0 000001ef`03e31788 00000000`00000000 : 0x00007ffb`404f6e41
00007ffb`404d2927 488b4d10 mov rcx, qword ptr [rbp+10h] 00007ffb`404d292b 488b55f8 mov rdx, qword ptr [rbp-8] 00007ffb`404d292f 4c8b45f0 mov r8, qword ptr [rbp-10h] //父函数用寄存器传参 00007ffb`404d2933 e870dbffff call 00007FFB404D04A8 //调用子函数 00007ffb`404d2938 90 nop 00007ffb`404d2939 90 nop 00007ffb`404d293a 488d6500 lea rsp, [rbp] 00007ffb`404d293e 5d pop rbp 00007ffb`404d293f c3 ret ------------------未调用之前---------------- 0:000> r rax=0000000002facb50 rbx=0000000002f5c098 rcx=0000000002ef83e8 rdx=0000000002facb38 rsi=0000000002f5ad28 rdi=0000000002faa4f8 rip=00007ffb404d2933 rsp=0000000000efe210 rbp=0000000000efe240 r8=0000000002facb50 r9=0000000000000000 r10=0000000002fadfd0 r11=00000000010c4b60 r12=00000000010c4b60 r13=0000000000000202 r14=0000000000000001 r15=0000000000000002 iopl=0 nv up ei ng nz na pe cy cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000283 TestThreadCrashFormsApplication1!TestThreadCrashFormsApplication1.Form1.button1_Click+0x73: 00007ffb`404d2933 e870dbffff call 00007ffb`404d04a8 ---------------------查看各参数值------------------------------ 0:000> dq 0000000000efe240 + 10h 00000000`00efe250 00000000`02ef83e8 00000000`02f5ad28 //form 0:000> dq 0000000000efe240 - 0x8 00000000`00efe238 00000000`02facb38 00000000`00efe390 //obj 0:000> dq 0000000000efe240 - 0x10 00000000`00efe230 00000000`02facb50 00000000`02facb38 //obj 0:000> !do 00000000`02facb38 Name: System.Object 0:000> !do 00000000`02facb50 Name: System.Object ------------------------进入TebtEBP内部,call指令自动压入了RetAddr返回地址存在地址:[RSP-8] -------------------------- 0:000> r rax=00007ffb404d2960 rbx=0000000002f5c098 rcx=0000000002ef83e8 rdx=0000000002facb38 rsi=0000000002f5ad28 rdi=0000000002faa4f8 rip=00007ffb404d2960 rsp=0000000000efe208 rbp=0000000000efe240 r8=0000000002facb50 r9=0000000000000000 r10=00007ff442320018 r11=00000000010c4b60 r12=00000000010c4b60 r13=0000000000000202 r14=0000000000000001 r15=0000000000000002 iopl=0 nv up ei pl nz na pe nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 TestThreadCrashFormsApplication1!TestThreadCrashFormsApplication1.Form1.testEBP: 00007ffb`404d2960 55 push rbp -------------------------反汇编内部代码片断--------------------------------- 00007ffb`404d2960 55 push rbp 00007ffb`404d2961 57 push rdi 00007ffb`404d2962 56 push rsi 00007ffb`404d2963 4883ec40 sub rsp, 40h 00007ffb`404d2967 488d6c2450 lea rbp, [rsp+50h] 00007ffb`404d296c 488bf1 mov rsi, rcx 00007ffb`404d296f 488d7dd0 lea rdi, [rbp-30h] 00007ffb`404d2973 b908000000 mov ecx, 8 00007ffb`404d2978 33c0 xor eax, eax 00007ffb`404d297a f3ab rep stos dword ptr [rdi] 00007ffb`404d297c 488bce mov rcx, rsi 00007ffb`404d297f 48894d10 mov qword ptr [rbp+10h], rcx 00007ffb`404d2983 48895518 mov qword ptr [rbp+18h], rdx 00007ffb`404d2987 4c894520 mov qword ptr [rbp+20h], r8 》》》00007ffb`404d298b 833d361cefff00 cmp dword ptr [00007ffb`403c45c8],0 -------------------------------指令暂停 查看寄存器---------------------------------------- 0:000> r rax=0000000000000000 rbx=0000000002f5c098 rcx=0000000002ef83e8 rdx=0000000002facb38 rsi=0000000002ef83e8 rdi=0000000000efe1f0 rip=00007ffb404d298b rsp=0000000000efe1b0 rbp=0000000000efe200 r8=0000000002facb50 r9=0000000000000000 r10=00007ff442320018 r11=00000000010c4b60 r12=00000000010c4b60 r13=0000000000000202 r14=0000000000000001 r15=0000000000000002 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 TestThreadCrashFormsApplication1!TestThreadCrashFormsApplication1.Form1.testEBP+0x2b: 00007ffb`404d298b 833d361cefff00 cmp dword ptr [00007ffb`403c45c8],0 ds:00007ffb`403c45c8=00000000 -------------查看参数地址--------------- 0:000> dq rbp+10h 00000000`00efe210 00000000`02ef83e8 00000000`02facb38 0:000> dq rbp+18h 00000000`00efe218 00000000`02facb38 00000000`02facb50 0:000> dq rbp+20h 00000000`00efe220 00000000`02facb50 00000000`00efe1e0 -----------------查看参数类型-------------------- 0:000> !do 00000000`02ef83e8 Name: TestThreadCrashFormsApplication1.Form1 0:000> !do 00000000`02facb38 Name: System.Object 0:000> !do 00000000`02facb50 Name: System.Object -----------转储打开后,再次对比------- 0:000> k # Child-SP RetAddr Call Site 00 00000000`00efe1b0 00007ffb`404d2938 TestThreadCrashFormsApplication1!TestThreadCrashFormsApplication1.Form1.testEBP+0x2b 01 00000000`00efe210 00007ffb`595f4747 TestThreadCrashFormsApplication1!TestThreadCrashFormsApplication1.Form1.button1_Click+0x78 [C:\Users\bosd\documents\visual studio 2015\Projects\TestThreadCrashFormsApplication1\TestThreadCrashFormsApplication1\Form1.cs @ 28] 02 00000000`00efe250 00007ffb`595f7b83 System_Windows_Forms_ni!System.Windows.Forms.Control.OnClick+0x87 0:000> dq 00000000`00efe210-8 00000000`00efe208 00007ffb`404d2938 00000000`02ef83e8 //RetAddr 0:000> dq 00000000`00efe210+0 00000000`00efe210 00000000`02ef83e8 00000000`02facb38 //form 0:000> dq 00000000`00efe210+8 00000000`00efe218 00000000`02facb38 00000000`02facb50 //obj1 0:000> dq 00000000`00efe210 +0n16 00000000`00efe220 00000000`02facb50 00000000`00efe1e0 //obj2
64位 传参方式
首先说明一下,在X64下,是寄存器传参. 前4个参数分别是 rcx rdx r8 r9进行传参.多余的通过栈传参.从右向左入栈.
2.申请参数预留空间
在x64下,在调用一个函数的时候,会申请一个参数预留空间.用来保存我们的参数.
比如以前我们通过push压栈参数的值.相应的栈就会抬高.
其实x64下,一样会申请.只不过这个地方在进函数的时候并没有值.进入函数之后才会将寄存器的值在拷贝到这个栈中.
其实就相当于你还是push了.只不过我是外边申请空间,内部进行赋值.
如下:
sub rsp,0x28 //申请的栈空间为0x28,就相当于我们push rcx rdx r8 r9.只不过只是申请. call xxxx add rsp,0x28 xxx //函数内部 mov [rsp - 8],rcx mov [rsp - 0x10],rdx mov [rsp - 0x18],r8 mov [rsp - 0x20],r9 xxx
如下图:
我们编写一个简单的x64程序.对其反汇编进行查看.
首先开辟我们的参数空间,以及返回地址空间.我们单步一下查看
可以看大开辟了 5*8个字节大小的空间.
然后下方的汇编对其寄存器赋值.进行传参.说明我们只有4个参数.
此时进入Call内部.看下栈.
3.栈按照16字节对齐
现在我们应该明白了.在调用一个函数的时候. 使用 *sub rsp,xxx**进行抬栈,函数内部则进行参数赋值.
其实也是相当于push了参数.只不过它不像x86一样.在里面进行平栈了.而是外面进行平栈了.
那么有个疑问.比如说我们就4个参数. 通过上面来说.我们应该申请 sub rsp,0x20个字节才对.在CALL的时候
x86 x64都是一样的会将返回地址入栈. 那为什么要rsp,0x28.这样的话会多申请一个参数的值哪.
原因是这样的.栈要按照16字节对齐进行申请.
那么还有人会说.按照16字节对齐,那么我们的参数已经是16字节对齐了.比如为我们4个寄存器申请预留空间. rsp,0x20. (4 * 8 = 32 = 16j进制的 0x20)
那为什么还是会申请 rsp,0x28个字节,并且不对齐.
其实是这样的.当我们在 Call函数的时候.返回地址会入栈.如果按照我们之前申请的rsp,0x20个字节的话.那么当
返回地址入栈之后,现在总共抬栈大小是 0x28个字节.并不是16进制对齐. 但是当我们一开始就申请0x28个字节.
当返回地址入栈.那么就是0x28+8 = 0x30个字节. 0x30个字节不是正好跟16字节对齐吗.
所以我们的疑问也就没有了.
所以申请了0x28个字节,其实多出了的8字节是要跟返回地址一样.进行栈对齐使用.
那么申请的这个8字节空间,是没有用的.只是为了对齐使用.
所以x64汇编其实也就搞明白了.
1.在调用函数之前,会申请参数预留空间.(rcx,rdx,r8,r9)
2.函数内部,会将寄存器传参的值(rcx,rdx,r8,r9)保存到我们申请的预留空间中.
上面这两步其实就相当于x86下的 push r9 push r8 push rdx,push rcx
3.调用约定是__fastcall.传参有rcx rdx,平栈是按照c调用约定平栈. 也就是调用者平栈.
Windows x64 Changes, from Addison.Wesley.Advanced.Windows.Debugging.Nov.2007.pdf
The 32-bit compilers use multiple calling conventions, each with its own strengths and weaknesses. In Windows x64, there is a single calling convention hav- ing a similar pattern to the __fastcall calling convention used by 32-bit compilers. In the Windows x64 calling convention, the register assignments are the following:
-
rcx: Contains the first parameter passed to the function. For example, an object method member invocation in C++ passes the object address or the this pointer into the rcx register, similar to the __fastcall calling convention.
-
rdx: Contains the second parameter passed to the function.
-
r8: Contains the third parameter passed to the function.
r8=0000000000000002 r9=0000000000000000 r11=0000000078c108a0 r12=0000000000000000 r14=0000000000020000 r15=0000000078ec0000
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
Windows x64 Changes Page:607
-
r9: Contains the fourth parameter passed to the function.
-
rax: Contains the result of the function call.
Using 64-bit calling convention, most functions receive their argument through registers, and because the number of registers is greater on 64-bit architecture, the stack pointer register is not so volatile. As a result, the rsp register is often used for local variable addressing, decreasing the usage of the stack-frame based address register, rbp, which becomes a general-purpose register in most functions. The functions with more than four parameters use the stack for all parameters beyond the fourth one. Those parameters are stored in the stack from right to left, with the rightmost parameter being stored at the highest address in the stack.
The calling convention does not require to preserve the argument passing registers rcx, rdc, r8 and r9, which can be used as scratch registers. Two more registers, r10 and r11, can also be used by the called function as scratch registers. All other registers maintain the value across the function call. The calling convention is explored in the later section “What Is the Current Call Stack?”
What Is the Current Call Stack?
In general, the current stack can be easily obtained using any form of the k command.
Because there are situations in which the stack pointer is not correct or the local variable
cannot be obtained using the standard commands, knowing how the stack is
used by x86-64 processors is a requirement for understanding the real problem.
The stack grows toward the lower address, and it must be aligned to 16 bytes before calling into another function.
In the “What Are the Current Register Values?”
section, you learned that the first four parameters are always passed through registers (rcx, rdc, r8, and r9),
while the rest of them are passed on the stack. To under- stand the stack management,
we use the Function5 function, shown in Listing 12.6,
that accepts five 64-bit parameters and calls another function with the same number of parameters.
The called function is declared as external to prevent the compiler from inlining it.
Listing 12.6 Function with five parameters, calling another function with five parameters extern int CalledFunction5(int a,int b,int c,int d,int e);
int Function5(int a, int b, int c, int d, int e) { CalledFunction5(a,b,c,d,e); return 5;
}
Listing 12.7 Assembly code representing the Function5 function ; COMDAT ?Function5@@YAHHHHHH@Z _TEXT SEGMENT a$ = 64 b$ = 72 c$ = 80 d$ = 88 e$ = 96 ?Function5@@YAHHHHHH@Z PROC NEAR ;64 :{ ; Function5, COMDAT ; 00000038H $LN3: 00000 48 83 ec 38 sub rsp, 5600004 8b 44 24 60 mov eax, dword ptr e$[rsp]
00008 89 44 24 20 mov dword ptr[rsp+32],eax
0000c e8 00 00 00 00 call CalledFunction5(a,b,c,d,e); call ?CalledFunction5@@YAHHHHHH@Z ; CalledFunction5
{
00011 b805000000 mov eax,5 return 5; } 00016 48 83 c4 38 add rsp, 56 ; 00000038H ; Function5 0001a c3 ret ?Function5@@YAHHHHHH@Z ENDP _TEXT ENDS
This assembly-generated code starts with the offsets corresponding to all input parameters that can be added to the rsp stack register inside the function to find the parameter address on the stack. These offsets are calculated under the assumption that all parameters are passed through the stack with the rightmost parameter locat- ed at the highest address.
The offsets already reflect the first statement in the function that decreases the value of the stack pointer, sub rsp, 56.
For example, the offset used to access the last parameter identified by the name e is 0n96—that is, with 0n40 higher than the stack decrement value used in the first instruction.
At the beginning of the function call, the offset for the parameter e was 0n40—that is, the offset that would have been used if all parameters were to be passed through the stack.
The calling convention requires that the caller allocates the stack for all parameters passed by registers as they were passed through the stack.
The stack space allocated for the parameter passed by registers is neither used nor initialized by the caller but can be used by the called function as temporary storage.
Those temporary storage locations are normally used to save the input parameters if any of the registers are needed for other purposes, such as calling another function.
In the case in which the function uses temporary variables, the stack is adjusted to give room to such variables that will be located at the top of the stack area used by the function.
The storage allocated before calling another function is similar to the space used for temporary variables.
In this specific case, the value used to adjust the stack at the entrance is larger than the space required for those five parameters in order to preserve the stack alignment to 16 bytes.
The gap is visible in Figure 12.3.
The rightmost column contains the hexadecimal addresses relative to the rsp value used in Function5.
The next column on the left shows the hexadecimal address rel- ative to the rsp value at the Function5 start, before the adjustment.
So far, the conclusions were based on analyzing the generated code that must be validated in the debugger.
The sample can be started under the debugger, using the following command line,
which launches the 64-bit version of the sample introduced in Chapter 2 under a 64-bit debugger:
C:\>C:\Debug.x64\windbg C:\awdbin\WinLH.AMD64.fre\02sample.exe
In this process, we set a breakpoint on the 02sample.exe!CalledFunction5 func- tion that is hit when we select option ‘5’ from the menu.
At the breakpoint location, the assumptions described previously can be validated by examining the stack.
Listing 12.8 shows the function arguments for each function from the call stack.
It might be a sur- prise to see that most parameters are incorrect.
The first four parameters from Function5 or CalledFunction5 are random values, while the last one has the correct value.
Why are the values incorrect?
This is the unfortunate downside of this calling con- vention combined with symbol information available to the debugger.
The debugger shows the values stored on a stack location corresponding to arguments passed through the registers, and they have random values. Furthermore,
if one input parameter is used in a function for something else and it is not needed for the remainder of this function, the parameter value is not stored anywhere.
This parameter can’t be recovered by the debugger after this point. If it has been stored somewhere, we need to manually find that location and read its value.
For example, when Function5 in Listing 12.8 starts its execution, the fist parameter is passed into the register rcx.
When this function calls CalledFunction5, the same register, rcx, must be filled with the first parameter passed to that function.
If rcx has not been saved before, and it is not used after the return from CalledFunction5,
the compiler does not generate code to preserve it. Its value is lost right before the call to CalledFunction5.
The situation is a little better for the code compiled using no optimizations, started using the checked build WDK shortcut.
In checked builds, each function prefix has special code to save all the input parameters, even if none are used.
The checked version of CalledFunction5 looks much, much better from a debugging perspec- tive, as can be seen in Listing 12.9.
The support obtained in the generated checked build from the C/C++ compiler is handy in the development process only.
Each application’s customers expect that the code released to them is highly optimized.
All memory dumps obtained using the Windows Error Reporting feedback loop described in Chapter 13,
“Postmortem Debugging,” will usually be optimized, without the compiler support.
What Are the Local Variable’s Values?
The display variable command, dv, shows the storage holding the local variables. For input parameters, the location is the stack parameter passing area,
which is not populated in free or optimized builds. Because dv usually shows random values for the input variables,
the manual discovery process uses our knowledge about the rules
used to generate assembly code, the assembly code itself, and sometimes luck in order to find the right value.
Listing 12.10 shows the local variable displayed at the debugger stop used before, at the entrance in CalledFunction5.
What logic must be used to find the right values?
Unless the debugger is stopped exactly at the beginning of the function and the input variables have correct values from the register used for parameter passing,
the user must question himself whether the values displayed by dv make sense. When a parameter does not look right,
the real value must be found by searching the assembly code for the location, if any, where it has been previously stored.
That value is then probed to see if it makes sense. If not, the process repeats until the correct value is found.
If necessary, the search for the original of the value must continue into the caller of the current functions.
In some cases, the original value is never found, but in most of the cases, the search is successful.
It is not acceptable to stop an investigation just because the dv command is not capable of showing the correct information and conclude that the debugger session is corrupted.
There are several ways to search a register name occurrence in one of the assem- bly instructions in the unassembled function.
The debugger support assembly search- es using the pound (#) command, which takes as parameters the string pattern to search for, the start address,
and the block length in which to search that pattern. The sequential pound (#) command can be repeated without parameters,
having as a result the search continuation in the memory block following and adjacent to the last memory block searched.
The next listing shows usage of the command and its output when searching for the ebp register,
starting at the beginning of the kernel32!CreateProcessW function, with the debugger in x86 mode:
0:000> # ebp kernel32!CreateProcessW
kernel32!CreateProcessW+0x2:
771a1d29 55 push ebp
0:000>
南来地,北往的,上班的,下岗的,走过路过不要错过!
======================个性签名=====================
之前认为Apple 的iOS 设计的要比 Android 稳定,我错了吗?
下载的许多客户端程序/游戏程序,经常会Crash,是程序写的不好(内存泄漏?刚启动也会吗?)还是iOS本身的不稳定!!!
如果在Android手机中可以简单联接到ddms,就可以查看系统log,很容易看到程序为什么出错,在iPhone中如何得知呢?试试Organizer吧,分析一下Device logs,也许有用.