首页
社区
课程
招聘
[原创]C语言——简单的函数调用过程
2011-6-16 12:49 8134

[原创]C语言——简单的函数调用过程

2011-6-16 12:49
8134
说明: 函数太常见了,这里只是对函数的调用过程进行的简单的描述,希望对刚接触的童鞋有点帮助,高手也望不吝指点。

简单函数的过程
void TestFun()
{
    printf("I'm comming!\r\n");
}

int _tmain(int argc, _TCHAR* argv[])
{    
    TestFun();
    return 0;
}


//手动去除了调试版的一些信息,只留需要的
int _tmain(int argc, _TCHAR* argv[])
{    
//保存当前的栈信息,以便函数返回时平衡自身栈
01182140  push        ebp  
01182141  mov         ebp,esp 
    TestFun();//函数调用
0118215E  call        TestFun (118100Ah) 
    return 0;
01182163  xor         eax,eax 
}
//Debug版这里有个跳转指令
0118100A  jmp         TestFun (11815E0h)

void TestFun()
{
011815E0  push        ebp  
011815E1  mov         ebp,esp 
    char* lpTest = "I'm comming!";
011815FE  mov         dword ptr [lpTest],offset string "I'm comming!" (11879A0h) 
}
01181608  mov         esp,ebp 
0118160A  pop         ebp  
0118160B  ret   

简单描述下TestFun函数的调用过程
1)当执行TestFun时,将其下一条指令地址压入栈中,以便函数调用结束时可以回来继续执行
2)进入函数(Debug会多一个JMP),保存当前的栈信息,以便函数出去时还原到进来时状态
3)执行函数功能
4)用2保存的栈信息还原到进来时栈状态
5)跳转到1保存的下一条要执行的指令处去执行。
实际调试看下具体情况
执行到TestFun函数调用处时栈信息

执行函数调用时栈,将函数的下一条指令压栈(函数执行完的返回地址,有参数时会先压参数在调函数,过程一样)
0118100A > /E9 D1050000     JMP First.011815E0 (DEBUG有个跳转不会影响栈)


保存寄存器信息,执行函数功能,恢复寄存器信息
常用的就这些指令(push< -- > pop / sub esp, xx < -- > add esp xx / mov xx, esp < -- > move sp, xx)
准备返回Ret


此时在回头看下栈调用的简单描述会有多一点的理解
调用TestFun栈
Esp –> Ret:1182163
进入TestFun后
Esp 值保存到 ebp中
Esp -- > 局部变量
                环境信息 (各种寄存器值)
                           Ret:1182163
函数功能完成后,清理局变量和恢复环境信息,esp  ebp,这是esp还是执行Ret
Esp -- > Ret : 1182163

主要涉及的知识在下面简单介绍
栈的结构, 老大发一篇 "逆向基础知识学习 " 很不错,有个栈的图比较亮
这里直接copy过来了,哈哈

栈有关的操作,这些在汇编书籍上都会有比较清晰的讲解。
Push xx,
抬高栈 esp = esp -4  (从低到高的排列顺序)
Mov [esp] = xx
Pop xx
        Mov xx, [esp]
        降低栈 esp = esp + 4
Call MyFun
NextCode:
   Push offset NextCode
        JMP MyFun
补充 只要理解了,操作的实质就可以了,这里在后面有使用,补充下,push xx ret == JMP XX
call MyFun 的其他方式
  push offset NextCode
   push MyFun
   Ret

Ret
   Pop eip
        JMP EIP
在使用局部变量时也会通过push, sub esp来抬高栈留空足够使用的空间,在使用完毕后要pop,add esp来将使用的栈还原。

有了上面的东西下面就可以简单模拟无参数函数调用过程
int _tmain(int argc, _TCHAR* argv[])
{    
    _asm JMP Test1
MyTestFun:
    _asm 
    {
        push ebp
        mov ebp, esp
        ;环境变量保存
    }

    //申请栈作局变量,执行具体函数功能
    printf("This is my TestFun %s\r\n", lpBuf);
    _asm
    {
        ;恢复环境变量
        mov esp, ebp
        pop ebp
    }
    _asm
    {
        ret
    }


Test1:   
    _asm 
    {
        mov eax , MyTestFun
        call eax
    }
    
NextCode:    printf("这里是返回地址\r\n");
    return 0;
}

上面的没问题的话,就来个稍微完整一点的,每个部分都有说明。
int _tmain(int argc, _TCHAR* argv[])
{    
    char* lpBuf = NULL;
    _asm JMP Test1
MyTestFun: 
    //保存环境部分
    _asm 
    {
        push ebp
        mov ebp, esp
        ;环境变量保存
    }
    //申请局部变量栈空间,此外也有push,xx的方式来申请少量的空间
    _asm 
    {
        sub esp, 0x110
    }

    //局部变量的使用,通常见到的是EBP做标杆偏移的定位方式
    _asm	
    {
        mov [esp], 'a'          ;优化才会用esp,正常用ebp,不过自己算晕了,
        mov [esp+1], 'b'
        mov [esp+2], '\0'
        mov lpBuf, esp
    }

    //申请栈作局变量,执行具体函数功能
    printf("This is my TestFun %s\r\n", lpBuf);
    //释放申请的局变量空间
    _asm 
    {
        add esp, 0x10
    }
    //恢复环境
    _asm
    {
        ;恢复环境变量
        mov esp, ebp
        pop ebp
    }
    //o,要回去喽!
    _asm
    {
        ;ret
        pop eax ; 获取返回地址并清栈,这里假设eax没有用到
        JMP eax
    }


Test1:   
    _asm 
    {
        ;mov eax , MyTestFun
        ;call eax
        push Test2
        push MyTestFun
        ret 
    }
Test2:
        _asm
    {
        push NextCode
        JMP MyTestFun
    }

NextCode:    printf("这里是返回地址\r\n");
    
     return 0;
}


声明: 个人水平比较菜,不足和错误难免,还望不吝指教。

                                 五边形

论坛资源:
标 题: 【原创】学什么编程语言?汇编+C似乎是个不错的选择。(99%YY篇)
作 者: vshhw
时 间: 2010-11-18,23:01:09
链 接: http://bbs.pediy.com/showthread.php?t=125062
标 题: 【注意】逆向基础知识学习
作 者: kanxue
时 间: 2007-09-01,10:45
链 接: http://bbs.pediy.com/showthread.php?t=50879
标 题: 献给汇编初学者-函数调用堆栈变化分析
作 者: 堕落天才
时 间: 2007-01-19,19:20:09
链 接:http://bbs.pediy.com/showthread.php?t=38234

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞5
打赏
分享
最新回复 (8)
雪    币: 270
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
swzices 2011-6-16 12:59
2
0
学习了...
文章很简洁,表述很清晰
雪    币: 223
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ahyanglf 2011-6-16 13:12
3
0
感谢分享基础性文章

学习,,
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2011-6-16 13:13
4
0
LZ 花了不少心机
用汇编去理解 C/C++ 没错,有时候是个捷径。

不过这样的 c 学习方式会使你走上旁门歪道

倒不如做一系列的逆向学习
雪    币: 185
活跃值: (130)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
五边形 1 2011-6-16 13:25
5
0
感谢LS的提议,我也是刚刚接触,只是拿出来,希望能多给点意见以便改进自身之不足。

其实所有的语言都是浮云,看雪老大的那个栈才是重点

补充没有说明的部分,关于参数,调用的简单介绍在下面链接1楼。
http://bbs.pediy.com/showthread.php?t=50879
雪    币: 777
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lnheart 2011-6-16 13:33
6
0
好的。学习下
雪    币: 596
活跃值: (449)
能力值: ( LV12,RANK:320 )
在线值:
发帖
回帖
粉丝
evilkis 7 2011-6-16 13:36
7
0
语言浮云?要是你只会汇编将来肯定没前途.暂且只谈C语言 VC编译器的情况,那个栈只有函数是_cdecl 和_stdcall调用约定时,并且编译器不做优化才是那样的.语言没学好就看汇编这种方式不好,最终你会高不成低不就.好好学习C语言,达到能随便写写项目的水平,这时在来看逆向你会觉得逆向是浮云,而不是语言.逆向只是手段,如果你当技术来学,而忽略了语言本身,你会失去很多..
雪    币: 185
活跃值: (130)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
五边形 1 2011-6-16 13:52
8
0
LS上的意思我明白了,非常感谢提醒。不过这里还是要简单的说明下,这里主要讲述函数调用过程,栈的分布情况,语言只是描述的需要。
所有的重点都围绕在那个栈的结构上,调用的过程是怎么变化的,后面的是自己按照理解简单模拟下,加深了对这个调用过程的理解,当然了,我也接触时间短很多东西确实搞不太清方向,所以跟大家交流,感谢各位的意见。
雪    币: 185
活跃值: (130)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
五边形 1 2011-6-17 09:17
9
0
楼上2位的真正意思我晚上睡觉时好好反思了下。
可能是我太浮躁,只停留在表面一些,没有看的比较长远。
再次感谢,人生路上就需要这样的朋友,来消除毛躁修正bug。
谢谢!

                    五边形
游客
登录 | 注册 方可回帖
返回