XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog

尽管可以使用相对于栈顶(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 = 0019FDBC0019FDC0 差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(返回地址), 00000065101)esp+4, 000000C9201)esp+8, 0000012D301)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>

 

posted on 2023-01-16 14:41  不及格的程序员-八神  阅读(274)  评论(0编辑  收藏  举报