`
xpp02
  • 浏览: 1049250 次
社区版块
存档分类
最新评论

C/C++拾遗录--关于一个C语言小程序的分析

 
阅读更多

虽然编了几年程序,但是对于程序到底是什么规则变成汇编代码的,在这里搞了一个小程序。用VC查看了一下汇编代码。在此之前先介绍一下关于函数运行是堆栈变化的细节。

在高级语言编写程序时,函数的调用是很常见的事情,但是在函数调用过程中堆栈的变化通常有几个细节:

1.父函数将函数的实参按照从右至左的顺序压入堆栈;

2.CPU将父函数中函数调用指令Call的下一条指令地址EIP压入堆栈;

3.父函数通过Push Ebp指令将基址指针EBP的值压入堆栈,并通过Mov Ebp,Esp指令将当前堆栈指针Esp值传给Ebp;

4.通过Sub Esp,m(m是字节数)指令可以为存放函数中的局部变量开辟内存。函数在执行的时候如果需要访问实参或局部变量,都可以通过EBP指针来指引完成。

windows系统下常用的函数调用通常有种,__cdecl和__stdCall。

1.在VC、.net等开发环境中,编写命令行程序时的Main或者_tmain函数,以及大家自己定义的很多函数都是默认采用__cdecl调用方式;

2.通过MFC编写图形界面程序的时候,其主函数声明为extern "C" int WINAPI tWinMain(参数),该函数的调用约定是__stdCall。WINAPI和PASCAL等都是__stdCall的宏定义,是一个意思,此外,大家平时调用的API函数,绝大多数都是采用__staCall的调用方式;

3.__cdecl调用方式的函数,父函数在调用子函数的时候,先将子函数的实参按照从右至左的顺序压入堆栈中,子函数返回后,父函数通过Sub Esp,n(n=函数实参个数*4)指令来恢复堆栈;

4.__stdCall调用约定函数,子函数调用时实参入栈顺序也是从左到右,但是堆栈恢复是子函数返回时自己通过Ret n指令来完成的。

下边就是针对这些知识进行的部分实践:

  1. #include<stdio.h>
  2. #include<windows.h>
  3. #include<stdlib.h>
  4. intfun(char*szIn,intnTest)
  5. {
  6. charszBuf[9];
  7. printf("%d\n",nTest);
  8. strcpy(szBuf,szIn);
  9. return0;
  10. }
  11. intmain(intargc,char*argv[])
  12. {
  13. charsz_In[]="1234567";
  14. fun(sz_In,888);
  15. return0;
  16. }

汇编代码

  1. 00401003int3
  2. 00401004int3
  3. @ILT+0(?fun@@YAHPADH@Z):
  4. 00401005jmpfun(00401020)//进入fun函数
  5. @ILT+5(_main):
  6. 0040100Ajmpmain(00401080)//进入main函数,该位置是整段代码的入口
  7. 0040100Fint3
  8. 00401010int3
  9. 00401011int3
  10. 00401012int3
  11. 00401013int3
  12. 00401014int3
  13. 00401015int3
  14. 00401016int3
  15. 00401017int3
  16. 00401018int3
  17. 00401019int3
  18. 0040101Aint3
  19. 0040101Bint3
  20. 0040101Cint3
  21. 0040101Dint3
  22. 0040101Eint3
  23. 0040101Fint3
  24. ---c:\project\heap1\heap1.cpp--------------------------------------------------------------------------------------------------------------------------------------
  25. 1:#include<stdio.h>
  26. 2:#include<windows.h>
  27. 3:#include<stdlib.h>
  28. 4:intfun(char*szIn,intnTest)
  29. 5:{
  30. 00401020pushebp
  31. 00401021movebp,esp//保存基址指针,并将现在的栈顶保存为基址指针。
  32. 00401023subesp,4Ch//腾出一部分堆栈区用于存放局部变量。
  33. 00401026pushebx
  34. 00401027pushesi
  35. 00401028pushedi//保存三个寄存器的值。
  36. 00401029leaedi,[ebp-4Ch]
  37. 0040102Cmovecx,13h
  38. 00401031moveax,0CCCCCCCCh
  39. 00401036repstosdwordptr[edi]//将腾出的4Ch的空间初始化值为0xCC。
  40. 6:charszBuf[9];
  41. 7:printf("%d\n",nTest);
  42. 00401038moveax,dwordptr[ebp+0Ch]
  43. 0040103Bpusheax
  44. 0040103Cpushoffsetstring"%d\n"(0042201c)//先后压入栈中两个地址,nTest,一个是一个字符串指针。
  45. 00401041callprintf(004011d0)//调用printf函数时,它会自动做到堆栈平衡。
  46. 00401046addesp,8//由于刚才压入和两个参数,所以在这里手动将两个参数弹出堆栈
  47. 8:strcpy(szBuf,szIn);
  48. 00401049movecx,dwordptr[ebp+8]
  49. 0040104Cpushecx//压入szIn的指针。这个参数在高出基址的8位处,也就是调用该函数前压入栈中的。
  50. 0040104Dleaedx,[ebp-0Ch]
  51. 00401050pushedx//压入szBuf的指针,这个函数在低于基址的OCh位处,这是调用函数后分配的。局部变量的分配大
  52. 00401051callstrcpy(004010e0)//小都是按4的倍数分配的,所以尽管szBuf[9]但是也分配在了0Ch处。
  53. 00401056addesp,8
  54. 9:return0;
  55. 00401059xoreax,eax//返回值在EAX中。
  56. 10:}
  57. 0040105Bpopedi
  58. 0040105Cpopesi
  59. 0040105Dpopebx//弹出保存的数据。
  60. 0040105Eaddesp,4Ch//消除为局部变量腾出的空间。
  61. 00401061cmpebp,esp
  62. 00401063call__chkesp(00401250)//检验是否在用户自定义汇编代码中修改了ebp和esp的相对关系。一般情况下EBP>ESP
  63. 00401068movesp,ebp//将原基址恢复给栈顶寄存器。
  64. 0040106Apopebp//弹出原调用函数的堆栈基址。
  65. 0040106Bret//函数返回。
  66. ---Nosourcefile--------------------------------------------------------------------------------------------------------------------------------------------------
  67. 0040106Cint3
  68. 0040106Dint3
  69. 0040106Eint3
  70. 0040106Fint3
  71. 00401070int3
  72. 00401071int3
  73. 00401072int3
  74. 00401073int3
  75. 00401074int3
  76. 00401075int3
  77. 00401076int3
  78. 00401077int3
  79. 00401078int3
  80. 00401079int3
  81. 0040107Aint3
  82. 0040107Bint3
  83. 0040107Cint3
  84. 0040107Dint3
  85. 0040107Eint3
  86. 0040107Fint3
  87. ---c:\project\heap1\heap1.cpp--------------------------------------------------------------------------------------------------------------------------------------
  88. 11:intmain(intargc,char*argv[])
  89. 12:{
  90. 00401080pushebp
  91. 00401081movebp,esp//保存堆栈基址
  92. 00401083subesp,48h//腾出局部变量空间
  93. 00401086pushebx
  94. 00401087pushesi
  95. 00401088pushedi//保存3个寄存器
  96. 00401089leaedi,[ebp-48h]
  97. 0040108Cmovecx,12h
  98. 00401091moveax,0CCCCCCCCh//初始化局部变量空间
  99. 00401096repstosdwordptr[edi]
  100. 13:charsz_In[]="1234567";
  101. 00401098moveax,[string"1234567"(00422020)]
  102. 0040109Dmovdwordptr[ebp-8],eax
  103. 004010A0movecx,dwordptr[string"1234567"+4(00422024)]
  104. 004010A6movdwordptr[ebp-4],ecx//将字符串通过寄存器将字符拷贝到分配的空间中。
  105. 14:fun(sz_In,888);
  106. 004010A9push378h
  107. 004010AEleaedx,[ebp-8]
  108. 004010B1pushedx//从右至左将参数压入堆栈中,数字直接压入数值,字符串则压入字符串指针
  109. 004010B2call@ILT+0(fun)(00401005)
  110. 004010B7addesp,8//恢复堆栈
  111. 15:return0;
  112. 004010BAxoreax,eax//返回值在EAX中
  113. 16:}
  114. 004010BCpopedi
  115. 004010BDpopesi
  116. 004010BEpopebx//恢复3个寄存器
  117. 004010BFaddesp,48h//清除局部变量空间
  118. 004010C2cmpebp,esp
  119. 004010C4call__chkesp(00401250)//检测堆栈指针与堆栈基址
  120. 004010C9movesp,ebp//恢复调用函数的栈顶
  121. 004010CBpopebp//恢复调用函数的堆栈基址
  122. 004010CCret//函数返回
  123. ---Nosourcefile--------------------------------------------------------------------------------------------------------------------------------------------------
  124. 004010CDint3
  125. 004010CEint3

关于这个程序的堆栈使用情况也做了一下分析,如图:

分享到:
评论

相关推荐

    C/C++实现HMAC-SHA1和base64编码

    C语言版的实现HMAC-SHA1和base64编码,已经对C++做了兼容处理,在VS下运行main.c代码,可以得到经过HMAC-SHA1处理后的结果,并且可以运行里面的base64编码函数得到想要的结果,可以用于连接阿里云MQTT

    C/C++完美演绎-源代码

    总之,"C/C++完美演绎-源代码"是一个很好的学习资源,通过深入研究和实践,你将能够提升自己的编程技艺,理解和掌握这两门语言的精髓,从而在IT领域取得更大的成就。记得,学习编程不仅仅是学习语法,更重要的是理解...

    用C语言编写multipart/form-data实现上传文件

    用C语言实现multipart/form-data文件上传,没有用到curl之类的库。之前做个小的日志上传程序写的。

    C语言/C++集成开发环境 Dev-C++

    5. **调试工具**:Dev-C++集成了GDB(GNU Debugger),一个强大的源码级调试器,可以帮助开发者查找并解决程序中的错误。用户可以通过设置断点、单步执行、查看变量值等方式来调试程序。 6. **可扩展性**:Dev-C++...

    c语言版数据结构餐厅订餐程序设计

    学生

    C语言/C++基础之爱心程序源码

    C语言/C++基础之爱心程序源码,适合初学C语言/C++的小伙伴学习研究,博客中有对应的讲解和演示,避免走弯路,费时费力。也真心希望能够帮助正在苦学C语言/C++ 程序设计的小伙伴们,你们的成长是我最大的幸福

    C/C++程序设计学习与实验系统

    官方介绍:原名《Turbo C/C++ for Windows 集成实验与学习环境》,支持最新操作系统WINDOWS 7,它是从事一线教学的大学教师根据C/C++ 初学者的特点,量身定制的一个简单易用的 C/C++程序设计学习与实验软件(支持TC2/...

    C语言/C++基础之冰墩墩源码

    C语言/C++基础之冰墩墩源码,适合初学C语言/C++的小伙伴学习研究,博客中有对应的讲解和演示,避免走弯路,费时费力。也真心希望能够帮助正在苦学C语言/C++ 程序设计的小伙伴们,你们的成长是我最大的幸福

    c语言/c++/qt图形界面

    c语言/c++/qt图形界面

    C语言/C++基础之爱心源码

    C语言/C++基础之爱心源码,适合初学C语言/C++的小伙伴学习研究,博客中有对应的讲解和演示,避免走弯路,费时费力。也真心希望能够帮助正在苦学C语言/C++ 程序设计的小伙伴们,你们的成长是我最大的幸福

    二维码(QRcode)生成算法 C语言/C++源码

    #二维码(QRcode)生成算法 C语言/C++ 源码 1. 根据输入字符串识别编码模式; 2. 根据输入字符串长度选择合适的QRcode版本; 3. 将编码转换为二进制位流表示为数据码字; 4. 使用多项式生成纠错码; 5. 将数据码和...

    C语言/C++基础之跨年烟花代码

    C语言/C++基础之跨年烟花代码,适合初学C语言/C++的小伙伴学习研究,博客中有对应的讲解和演示,避免走弯路,费时费力。也真心希望能够帮助正在苦学C语言/C++ 程序设计的小伙伴们,你们的成长是我最大的幸福

    C语言/c++编写的计算器 含源码

    本资源提供了一个使用C语言和C++编写的计算器的源代码,适用于学习者进行参考和修改。 1. C语言基础: C语言是一种强大的、低级的编程语言,被广泛用于系统编程、嵌入式开发等领域。在C语言中,我们通常使用结构化...

    基于C语言/C++基础的跨年烟花代码

    C语言/C++基础之跨年烟花代码,适合初学C语言/C++的小伙伴学习研究,博客中有对应的讲解和演示,避免走弯路,费时费力。也真心希望能够帮助正在苦学C语言/C++ 程序设计的小伙伴们,你们的成长是我最大的幸福

    C/C++程序设计学习与实验系统 V2008.13.part1

    原名《Turbo C/C++ for Windows 集成实验与学习环境》,现已更名为《C/C++程序设计学习与实验系统》,现在已全面支持最新操作系统VISTA,它由从事一线教学的大学教师根据C/C++初学者的特点,量身定制的一个简单易用的...

    不错的c语言c++语言课程设计项目-学生成绩管理系统.rar

    不错的c语言c++语言课程设计项目--学生成绩管理系统 不错的c语言c++语言课程设计项目--学生成绩管理系统 不错的c语言c++语言课程设计项目--学生成绩管理系统 不错的c语言c++语言课程设计项目--学生成绩管理系统 不错...

    PC Lint --C/C++语言静态分析工具

     PC-Lint 能够检查出很多语法错误和语法上正确的逻辑错误,PC-Lint 为大部分错误消息都分配了一个错误号,编号小于1000的错误号是分配给C 语言的,编号大于1000的错误号则用来说明C++的错误消息。

    c/c++中文帮助文档(API)

    本资源“c/c++中文帮助文档(API)”为开发者提供了一个全面的参考,帮助他们理解和使用C和C++的各种库函数。 C库函数主要包括I/O操作、内存管理、字符串处理、数学运算、时间日期处理等。例如,`stdio.h`提供了...

    C语言/C++资料大全 个人收集

    此外,文件操作也是C语言中一个重要的知识点,能实现数据的读写。 "C++"的自学则需要理解面向对象编程(OOP)的核心概念,如类、对象、封装、继承、多态。在C++中,模板和异常处理也是重要特性。"进阶"阶段则涉及...

Global site tag (gtag.js) - Google Analytics