汇编语言分析C语言的函数调用过程
1.要分析C语言的函数调用过程,理解汇编指令push,pop是关键,
在汇编中,栈的增长方式是从高地址往低地址增长,栈底在高地址,栈顶在低地址。
push eax入栈指令相当于:
ESP=ESP-4,[SS:ESP]<--eax内容; (32 bit)
pop eax出栈指令相当于:
eax<--[SS:ESP]内容,ESP=ESP+4
2.先看下在vs2013工程下的C语言示例源码,以__cdecl调用方式调用函数
1 #include <stdio.h> 2 3 int add(int a, int b, int c) 4 { 5 return (a+b+c); 6 } 7 8 int main(int argc, char *argv[]) 9 { 10 int sum = 0; 11 int a = 1; 12 int b = 2; 13 int c = 3; 14 sum = add(a,b,c); 15 return 0; 16 }
反汇编后的源码
1 __RTC_Initialize: 2 01091145 jmp _RTC_Initialize (01092D40h) 3 __controlfp_s: 4 0109114A jmp __controlfp_s (0109376Ch) 5 _GetSystemTimeAsFileTime@4: 6 0109114F jmp _GetSystemTimeAsFileTime@4 (01093C22h) 7 add: 8 01091154h jmp add (010913C0h) 9 _DecodePointer@4: 10 01091159 jmp _DecodePointer@4 (01093C28h) 11 __invoke_watson: 12 0109115E jmp __invoke_watson (01093766h) 13 ___report_rangecheckfailure: 14 01091163 jmp __report_rangecheckfailure (01093940h) 15 _RTC_GetSrcLine: 16 01091168 jmp _RTC_GetSrcLine (010933D0h) 17 __wmakepath_s: 18 0109116D jmp __wmakepath_s (0109377Eh) 19 __CRT_RTC_INITW: 20 01091172 jmp __CRT_RTC_INITW (0109262Ch) 21 22 #include <stdio.h> 23 24 //调用函数add的地址 25 int add(int a, int b, int c) 26 { 27 010913C0 push ebp ;ebp=0030FB10 esp=0030FA00 28 010913C1 mov ebp,esp ;ebp=0030FA00 29 010913C3 sub esp,0C0h ;esp=0030F940 30 010913C9 push ebx ;保护现场,esp=0030F93C 31 010913CA push esi ;esp=0030F938 32 010913CB push edi ;edi=0030FB10, esp=0030F934 33 010913CC lea edi,[ebp-0C0h] ;edi=0030F940 34 010913D2 mov ecx,30h ;初始化 35 010913D7 mov eax,0CCCCCCCCh 36 010913DC rep stos dword ptr es:[edi] 37 return (a+b+c); 38 010913DE mov eax,dword ptr [a] ;取参数值,并计算 39 010913E1 add eax,dword ptr [b] 40 010913E4 add eax,dword ptr [c] 41 } 42 010913E7 pop edi ;恢复现场 edi=0030FB10, esp=0030F938 43 010913E8 pop esi ;esp=0030F93C 44 010913E9 pop ebx ;esp=0030F940 45 } 46 010913EA mov esp,ebp ;esp=0030FA00 47 010913EC pop ebp ;ebp=0030FB10, esp=0030FA04 48 010913ED ret ;esp=0030FA08 【__cdecl】调用方式内部没有平衡栈,由调用者负责平栈(编译器自动处理),【__stdcall】调用方式指令“return 0Ch”内部平栈 49 50 //主调函数 51 int main(int argc, char *argv[]) 52 { 53 01091400 push ebp ;将ebp内容压栈 ebp=0030FB60 esp=0030FB14 54 01091401 mov ebp,esp ;esp传给ebp ebp=0030FB10 esp=0030FB10 55 01091403 sub esp,0F0h ;改变栈顶值,腾出空间,esp=esp-0F0h esp=0030FA20 56 01091409 push ebx ;压栈ebx,esi,edi,保护现场. ebp=0030FB10 esp=0030FA1C 57 0109140A push esi ;esp=0030FA18 58 0109140B push edi ;esp=0030FA14 59 0109140C lea edi,[ebp-0F0h] ;将(ebp-0F0h)=0030FA20的值放入edi中,edi=0030FA20 60 01091412 mov ecx,3Ch ;ecx=3Ch,rep指令循环次数 61 01091417 mov eax,0CCCCCCCCh ;eax=CCCCCCCC 62 0109141C rep stos dword ptr es:[edi] ;重复填充CCCCCCCC,(3Ch)次 63 int sum = 0; 64 0109141E mov dword ptr [sum],0 ;&sum=0030fb08, sum=0 65 int a = 1; 66 01091425 mov dword ptr [a],1 ;&a=0030fafc, a=1 67 int b = 2; 68 0109142C mov dword ptr [b],2 ;&b=0030faf0, b=2 69 int c = 3; 70 01091433 mov dword ptr [c],3 ;&c=0030fae4, c=3 71 sum = add(a,b,c); 72 0109143A mov eax,dword ptr [c] ;参数按从右至左的顺序压栈, eax=3 73 0109143D push eax ;esp=0030FA10 74 0109143E mov ecx,dword ptr [b] ;ecx=2 75 01091441 push ecx ;esp=0030FA0C 76 01091442 mov edx,dword ptr [a] ;edx=1 77 01091445 push edx ;esp=0030FA08 78 01091446 call add (01091154h) ;调用函数add,此时会将返回函数的下一指令地址0109144B压栈,调用前esp=0030FA04,调用后esp=0030FA08 79 0109144B add esp,0Ch ;esp=0030FA14 【__cdecl】调用方式调用返回后外部平栈,【__stdcall】调用方式没有这条指令 80 0109144E mov dword ptr [sum],eax ;将值传给变量sum 81 return 0; 82 01091451 xor eax,eax 83 } 84 01091453 pop edi ;恢复现场 85 01091454 pop esi 86 } 87 01091455 pop ebx ;esp=0030FA20 88 01091456 add esp,0F0h ;esp=0030FB10 89 0109145C cmp ebp,esp 90 0109145E call __RTC_CheckEsp (01091136h) 91 01091463 mov esp,ebp 92 01091465 pop ebp ;ebp=0030FB60 esp=0030FB14 93 01091466 ret ;esp=0030FB18
3.大概的过程是调用函数add前,参数先从右至左放入栈中,同时把返回地址也放入栈中。在add函数中,取出栈中的参数,执行完后返回,继续执行。