汇编看C函数调用

http://blog.csdn.net/wishfly/article/details/5022008

简单的函数调用,通过简单的函数调用反汇编可以清楚了解如下

1.栈到底是什么,如何操纵栈的?

2.参数和临时变量是以什么形式在哪存放?

3.如何传递返回值?

测试代码如下(这是一个简单的通过调用函数计算两数之和的程序):

C++ Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


#include <stdio.h>

int add(int a, int b)

{
    int c = 0;

c = a + b;
    return c;

}

int main(void)

{
    int x = 0;
    int y = 3;
    int z = 4;

x = add(y, z);
    return 0;

}

解释如下

ASM Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112


add函数

{
;ebp=1000,esp=900
    0040D750   push        ebp

;把main函数的ebp压栈,ebp=1000,esp=896
    0040D751   mov         ebp, esp

;得到“新”栈基址,这里的新的意思是每个函数访问属于自己的一块栈区域,其实是相邻的内存区域,或者说栈只有一个
    ;ebp=896,esp=896
    0040D753   sub         esp, 44h

;ebp=896,esp=828
    0040D756   push        ebx

0040D757   push        esi

0040D758   push        edi

;ebp=896,esp=816
    0040D759   lea         edi, [ebp - 44h]

0040D75C   mov         ecx, 11h

0040D761   mov         eax, 0CCCCCCCCh

0040D766   rep stos    dword ptr [edi]

;初始化内部变量区
5:  int c = 0;
    0040D768   mov         dword ptr [ebp - 4], 0

;c放入“新”栈基址
    ;因为“新”栈基地址就是“旧”栈顶地址,所以通过ebp访问传过来的参数
    ;ebp+8到ebp+c是因为ebp上方的8字节用于存储调用函数的调用地址和“旧”堆栈基地址了
    ;ebp-4则位于新栈中
6:  c = a + b;
    0040D76F   mov         eax, dword ptr [ebp + 8]

0040D772   add         eax, dword ptr [ebp + 0Ch]

;运算结果放入c中
    0040D775   mov         dword ptr [ebp - 4], eax

;用寄存器eax返回结果
7:  return c;
    0040D778   mov         eax, dword ptr [ebp - 4]

;恢复寄存器的值,ebp=896,esp=828
8:  }

0040D77B   pop         edi

0040D77C   pop         esi

0040D77D   pop         ebx

;恢复“旧”栈顶地址,ebp=896,esp=896,此函数堆栈被释放!
0040D77E   mov         esp, ebp

;恢复“旧”栈基地址,ebp=1000,esp=900,此时恢复到调用前的栈基地址和顶地址
0040D780   pop         ebp

;返回调用点,ebp=1000,esp=904,调用点地址被弹出,返回到调用点
0040D781   ret

main函数

{
;用栈顶地址作为栈基地址,目的是不和调用前栈空间冲突
    ;为了叙述方便假设原 ebp=1004,esp=1004
    ;执行完下面两行汇编语句后 ebp=1000,esp=1000
    0040D790   push        ebp

0040D791   mov         ebp, esp

;esp下移,开辟出0x4C字节的空间,这个空间是留给内部参数用的,这个空间的大小随内部变量多少由编译器决定。
    ;ebp=1000,esp=1000-0x4C=924
    0040D793   sub         esp, 4Ch

;保存 ebx,esi,edi的值,ebp=1000,esp=924-12=912
    0040D796   push        ebx

0040D797   push        esi

0040D798   push        edi

;把内部参数占用的空间每个字节都初始化为0xCC,这个是为了在DUBUG程序的方便,编译器加入的
    ;如果不在DEBUG状态下,这个区域是没有被初始化的,也就是说是随机值。
    0040D799   lea         edi, [ebp - 4Ch]

0040D79C   mov         ecx, 13h

0040D7A1   mov         eax, 0CCCCCCCCh

0040D7A6   rep stos    dword ptr [edi]

;内部变量放入刚才被初始化为0xCC的内部变量区,x占用四字节在地址9996-9999,y,z一次类推
12: int x = 0;
    0040D7A8   mov         dword ptr [ebp - 4], 0
13: int y = 3;
    0040D7AF   mov         dword ptr [ebp - 8], 3
14: int z = 4;
    0040D7B6   mov         dword ptr [ebp - 0Ch], 4

;把参数按照stdcall方式从右到左压栈,ebp=1000,esp=912-8=904,z首先入栈在908-911,y在904-907
15: x = add(y, z);
    0040D7BD   mov         eax, dword ptr [ebp - 0Ch]

0040D7C0   push        eax

0040D7C1   mov         ecx, dword ptr [ebp - 8]

0040D7C4   push        ecx

;把返回下一行代码即 0040D7CA [add esp,8] 的地址压栈
    ;转向add函数,ebp=1000,esp=900,看add函数
    0040D7C5   call        @ILT + 15(_add) (00401014)

;ebp=1000,esp=912,恢复到压入参数前栈基地址和顶地址,这个步骤叫做堆栈修正
    0040D7CA   add         esp, 8

;返回的变量放到x中
    0040D7CD   mov         dword ptr [ebp - 4], eax

16: return 0;
17: }

main函数堆栈变化示意图:

add函数堆栈变化示意图:

现在来总结开始提出的三个问题

1.栈到底是什么,如何操纵栈的?

栈是操作系统分配给程序运行的一块内存区域,有以下特点:

1.1、改变堆栈用push, pop,用的esp栈顶指针,而读指针则用ebp栈基指针灵活访问

1.2、每当一个函数跳转到另一个函数时,会在上一个函数用到的栈空间下方开辟空间

2.参数和临时变量是以什么形式在哪存放?

2.1、参数放在旧栈的返回地址和旧栈基地址的上方,而临时变量则在新栈的最上方处,变量名会被编译器连接一个地址,程序在被编译成汇编以后,变量名就是虚无了。

3.如何传递返回值?

3.1、传递一个值的情况下,通过eax传递

可以看出,栈溢出是由于编译器没有检查栈是否还有空间。

来自为知笔记(Wiz)

附件列表

时间: 2024-10-08 03:06:49

汇编看C函数调用的相关文章

从汇编看c++函数的默认参数

在c++中,可以为函数提供默认参数,这样,在调用函数的时候,如果不提供参数,编译器将为函数提供参数的默认值.下面从汇编看其原理. 下面是c++源码: int add(int a = 1, int b = 2) {//参数a b有默认值 return a + b; } int main() { int c= add();//不提供参数 } 下面是mian函数里面的汇编码: ; 4 : int main() { push ebp mov ebp, esp push ecx;为局部变量c分配了4字节的

从汇编看c++中的多态

在c++中,当一个类含有虚函数的时候,类就具有了多态性.构造函数的一项重要功能就是初始化vptr指针,这是保证多态性的关键步骤. 构造函数初始化vptr指针 下面是c++源码: class X { private: int i; public: X(int ii) { i = ii; } virtual void set(int ii) {//虚函数 i = ii; } }; int main() { X x(1); } 下面是对应的main函数汇编码: _main PROC ; 16 : in

从汇编看尾递归的优化

对于尾递归,很多人的理解仅局限于它是递归和尾调用的一个合体,比普通递归效率高.至于效率为什么高,高在哪,可能没有深究过. 尾调用 要说尾递归,得先说尾调用.我理解的尾调用大概是这么一种情况:k7娱乐城 函数A里面调用了函数B. 函数B执行后,函数A马上返回. 也就是说调用函数B(并返回执行结果)是函数A所做的最后一件事. 相当于执行完函数B后,函数A也就执行完. 因此在执行函数B时,函数A的栈帧其实是已经大部分没用了,可以被修改或覆盖.编译器可以利用这一点进行优化,函数B执行后直接返回到函数A的

从汇编看for和while循环的效率

这事情得从C语言开始说起,先编写下面这两段代码: 1. #include<stdio.h> int main(void) {     int i;     for(i = 0; i < 10; i++) {     }     return 0; } 2. #include<stdio.h> int main(void) {     int i = 0;     while (i < 10) {         i++;     }     return 0; } 然后

从一段代码的汇编看计算机的工作原理

朱宇轲 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 大家都知道,现在的计算机主要遵循的是所谓的“冯诺依曼框架”.那什么是冯诺依曼框架呢,其实就是计算机通过总线从内存中读取一条条的程序和数据,将它们存储在自己的寄存器中一条条地执行,如下图所示. 而今天,我们将通过汇编一个具体的C程序来探讨计算机工作的流程. 首先写下这么一段C程序: 1 //linux.c 2 int g(x)

从汇编看计算机的工作过程

本周学习了Linux内核分析第一课,老师通过讲解一个简单的c程序使我了解了计算机(特别是堆栈部分)工作过程. 下面通过一个简单的例子来分析一下: 下面是c程序: int g(int x) { return x + 6; } int f(int x) { return g(x); } int main(void) { return f(5) + 2; } 使用下面命令反汇编c程序得到汇编程序 gcc –S –o main.s main.c -m32 首先从main函数开始分析,可以发现每个函数中前

C编译器剖析_6.3.4 汇编代码生成_为函数调用与返回产生汇编代码

6.3.4        为函数调用与返回产生汇编代码 在这一小节中,我们来讨论一下如何为函数调用和函数返回生成汇编代码.函数调用对应的中间指令如下所示: //中间指令的四元式: < opcode, DST, SRC1, SRC2> <CALL, 用于接收返回值的变量retVal, 函数名func,  参数列表[arg1,arg2, -,argn]> 让我们先熟悉一下C函数的调用约定CallingConvention,我们需要把参数从右向左入栈(即从argn到arg1依次入栈),不

(转)函数调用过程探究

转自:http://www.cnblogs.com/bangerlee/archive/2012/05/22/2508772.html 引言 如何定义函数.调用函数,是每个程序员学习编程的入门课.调用函数(caller)向被调函数(callee)传入参数,被调函数返回结果,看似简单的过程,其实CPU和系统内核在背后做了很多工作.下面我们通过反汇编工具,来看函数调用的底层实现. 基础知识 我们先来看几个概念,这有助于理解后面反汇编的输出结果. 栈(stack) 栈,相信大家都十分熟悉,push/p

[转]iOS高级调试&amp;逆向技术-汇编寄存器调用

前言 本文翻译自Assembly Register Calling Convention Tutorial 序言 通过本教程,你会可以看到CPU使用的寄存器,并探索和修改传递给函数调用的参数.还将学习常见的苹果计算机架构以及如何在函数中使用寄存器.这就是所谓架构的调用约定. 了解汇编是如何工作的,以及特定架构调用约定是如何工作是一项极其重要的技能.它可以让你在没有源码的情况下,观察和修改传递给函数的参数.此外,因为源码存在不同或未知名称的变量情况,所以有时候更适合使用汇编. 比如说,假设你总想知