逆向学习从零开始(1)栈帧、调用约定明白函数和寄存器、栈的关系


PE程序拖入OD有四个基础窗口:代码执行窗口、寄存器窗口、数据窗口、栈窗口,帮助我们对程序进行逆向分析,运行程序,处理器执行的是汇编代码,代码执行时临时保存数据的地方在寄存器和栈。

栈帧

栈的改变是临时的,比如我们调用一个函数,就会生成一个栈空间,调用函数执行结束,根据前面保存在栈里的值,会恢复调用函数之前寄存器的值和汇编代码执行到哪里的地址。

栈帧( stack frame )和栈有关,栈帧是在程序的运行时栈中分配的内存块,专门用于特定的函数调用。程序员通常会将可执行语句分组,划分成叫做函数(也称过程、子例程或方法)的单元。有时候,这样做是遵照所使用的语言的要求。多数情况下,以这些函数单元为基础构建程序是一种编程思维。

如果一个函数并未执行,通常它并不需要内存。但是,当函数被调用时,它就可能因为某种原因需要用到内存。这源于几方面的原因。其一,函数的调用方可能希望以参数(实参)的方式,向该函数传递信息,这些参数需要存储到函数能够找到它们的位置。其二,在执行任务的过程中,函数可能需要临时的存储空间。程序员通常会通过声明局部变量来分配这类临时空间,这些变量将在函数内部使用,完成函数调用以后,就无法再访问它们。

编译器通过栈帧(也叫做激活记录)使得对函数参数和局部变量进行分配和释放的过程对程序员透明。在将控制权转交给函数之前,编译器会插人代码,将函数参数放人栈帧内,并分配足够的内存,以保存函数的局部变量。鉴于栈帧的结构,该函数的返回地址也存储在新的栈帧内。使用栈帧使得递归成为可能,因为每个递归函数调用都有它自己的栈帧,这恰好将当前调用与前一次调用分隔开来。

下面是调用一个函数栈的变化:

在调用779E9250函数之前有四个push操作,其意思是将四个函数参数压入堆栈,注意右下方栈的值

执行完四个push后,我们可以看到栈里多了四个值,从下往上依次是0、1、0、401860

F7进入函数,调用779E9250函数这条命令的下一条命令地址会压入栈,也就是被调用函数执行完毕后会通过栈里的这个值返回被调用函数执行完之后的地方。

一个函数的汇编代码格式如下,先是几个push保存现场(保存寄存器的值),中间是函数的具体操作,然后几个pop恢复现场(恢复调用函数之前寄存器的值),然后retn返回就是jmp跳转的意思,这个跳转的地址,就是先前我们F7进入函数时,压入栈的地址。retn 0x10这个和调用约定有关,这里是被调用的函数清理栈空间,先前压入了四个参数,每个参数大小4字节,4*4=16就是要清理16字节的栈空间

执行完retn 0x10命令,我们看到ESP寄存器恢复了调用779E9250函数之前所指向的栈地址,意为调用完函数栈是不变的。而当前执行的汇编代码在call 779E9250代码(被调用函数)的下一条命令处,刚才从push参数到F7进入函数栈里一堆数据的变化,就是栈帧。当函数执行完毕栈恢复原来的样子,栈帧消失,是不是很神奇,堆栈平衡啦。

调用约定

了解栈帧的基本概念后,接下来详细介绍它们的结构。下面的例子涉及x86体系结构和与常见的x86编译器(如Mircosoft Visual C/C++或GNU的gcc/g++ )有关的行为。创建栈帧时最重要的步骤是,通过调用函数将函数参数存人栈中。调用函数必须存储被调用函数所需的参数,否则可能导致严重的问题。各个函数会选择并遵照某一特定的调用约定,以表明它们希望以何种方式接收参数。

调用约定指定调用方放置函数所需参数的具体位置。调用约定可能要求将参数放置在特定的寄存器、程序栈、或者寄存器和栈中。同样重要的是,在传递参数时,程序栈还要决定被调用函数完成其操作后,由谁负责从栈中删除这些参数。一些调用约定规定,由调用方负责删除它放置在栈中的参数,,而另一些调用约定则要求被调用函数负责删除栈中的参数。遵照指定的调用约定对于维护程序栈指针的完整性尤为重要。

1. C调用约定

x86体系结构的许多C编译器使用的默认调用约定叫做C调用约定。如果默认的调用约定被重写,则C/C++程序中常用的_cdecl 修饰符会迫使编译器利用C调用约定。自现在开始,我们把这种调用约定叫做cdecl调用约定。cdecl调用约定规定:调用方按从右到左的顺序将函数参数放人栈中,在被调用的函数完成其操作时,调用方(而不是被调用方)负责从栈中清除参数。

从右到左在栈中放入参数的一个结果是,如果函数被调用,最左边的(第一个)参数将始终位于栈顶。这样,无论该函数需要多少个参数,我们都可轻易找到第一个参数。因此,cdecl调用约定非常适用于那些参数数量可变的函数(如printf )。

要求调用函数从栈中删除参数,意味着你将经常看到指令在由被调用的函数返回后,会立即对程序栈指针进行调整。如果函数能够接受数量可变的参数,则调用方非常适于进行这种调整,因为它清楚地知道,它向函数传递了多少个参数,因而能够轻松做出正确的调整。而被调用的函数事先无法知道自己会收到多少个参数,因而很难对栈做出必要的调整。

2.标准调用约定

标准调用约定叫做stdcall,和cdecl调用约定一样,stdcall调用约定按从右到左的顺序将函数参数放在程序栈上。使用stdcall调用约定的区别在于:函数结束执行时,应由被调用的函数负责删除栈中的函数参数。对被调用的函数而言,要完成这个任务,它必须清楚知道栈中有多少个参数,这只有在函数接受的参数数量固定不变时才有可能。因此,printf这种接受数量可变的参数的函数不能使用stdcall调用约定。例如,demo_ stdcall 函数需要3个整数参数,在栈上共占用12个字节(在.32位体系结构上为3*sizeof(int) )的空间。x86编译器能够使用RET指令的一种特殊形式,同时从栈顶提取返回地址,并给栈指针加上12,以清除函数参数。demo_ stdcall可能会使用以下指令返回到调用方。

使用stdcall的主要优点在于,在每次函数调用之后,不需要通过代码从栈中清除参数,因而能够生成体积稍小、速度稍快的程序。根据惯例,微软对所有由共享库( DLL)文件输出的参数数量固定的函数使用stdcall约定。如果你正尝试为某个共享库组件生成函数原型或与二进制兼容的替代者,请一定记住这一点。

3. x86 fastcall约定

fastcall约定是stdcall约定的一个变体,它向CPU寄存器(而非程序栈)最多传递两个参数。Microsoft Visual C/C+ +和GNU gcc/g++ ( 3.4及更低版本)编译器能够识别函数声明中的fastcall修饰符。如果指定使用fastcall约定,则传递给函数的前两个参数将分别位于ECX和EDX寄存器中。剩余的其他参数则以类似于stdcall约定的方式从右到左放入栈上。同样与stdcall约定类似的是,在返回其调用方时,fastcall 函数负责从栈中删除参数。

主要是这三种调用方式,c++编写的代码有this指针被保存到ECX寄存器中,作为第一个隐藏参数,其它调用方式不再说明。

通过以上学习我们明白了,之前演示栈帧的函数采用stdcall,自己清理自己的参数栈空间。


The End

vs调用堆栈窗口怎么弄出来_从零开始(1)栈帧、调用约定相关推荐

  1. C++工作笔记-VS中“调用堆栈”窗口的使用,实现越界的快速定位

    目录 理论 演示 理论 如下面的这个代码,会出现index out of range的提示! 在vs中可以根据"调用堆栈"窗口,实现快速的定位, 演示 构造如下错误代码: #inc ...

  2. python调用api做用户登录认证_(二)Python调用Zabbix api之从入门到放弃——登录并获取身份验证令牌...

    x.x.x.x可能是你的IP或者域名 访问流程概览: 1.首先登录 2.认证成功后zabbix server返回一个token 3.带着这个token去访问各种数据,做各种操作 4.完毕! 一.用RE ...

  3. python 窗口程序开发课程_从零开始学Python - 第019课:使用PyCharm开发Python应用程序...

    坚持学习完前18课的小伙伴应该已经感受到了,随着我们对Python语言的认知在逐步加深,我们写的代码也越来越复杂了."工欲善其事,必先利其器",如果希望能够更快更好的写出代码,选择 ...

  4. kotlin中mainactivity无法直接调用xml中的控件_使用52North 客户端接口调用OGC WPS服务...

    52°North是一个来自研究机构.工业界和公共行政管理界的研究者组成的开放国际合作组织,他们通过协作研发流程促进地理信息学创新.具体来说他们开发新的地理信息概念和技术,例如用于管理时空测量数据,以及 ...

  5. java调用下载窗口_java 从网络Url中下载文件 java调用url接口

    /** * 从网络Url中下载文件 * @param urlStr * @param fileName * @param savePath * @throws IOException */ publi ...

  6. 转:eclipse 调用堆栈 快捷键

    转自:http://www.verydemo.com/demo_c288_i65529.html Eclipse 修改Eclipse堆栈大小 修改Eclipse堆栈大小 -Xmx512M-Xms512 ...

  7. javascript调用父窗口(父页面)的方法

    window.parent与window.opener的区别 javascript调用主窗口方法 1:   window.parent 是iframe页面调用父页面对象 举例: a.html Html ...

  8. Vuepress-theme-reco 构建静态网页错误:在格式错误时超出了最大调用堆栈大小

    添加许多文章后,构建静态网站发生错误 Client 卡在92% 通过Vuepress-theme-reco 创建自己的博客网站.在blogs文件夹下创建分类文件夹及文章. 执行运行预览正常 npm r ...

  9. PHP打印调用堆栈信息,用于程序调试

    博客搬家:由于各种原因,我现在的博客将首发于blog.mojijs.com, 可以百度搜索 "姜哥的墨迹技术博客" , 或者 点击这里 本文首发地址 http://blog.moj ...

最新文章

  1. 又一家明星机器人公司倒掉:曾是全球机器人技术50强,主打性价比AI机械臂
  2. reciprocity
  3. NCNE二级复习资料-网络监视、管理和排错
  4. 2pin接口耳机_悦耳好音质,续航10小时,用了小米生态链这款耳机,扔掉其它吧...
  5. How to include html native content to UI5 page - 直接在xml view里添加html namespace
  6. Oracle数据库脚本学习:建用户、删用户、建表、改表、删表
  7. Forms Authentication With Absolute Return URLs
  8. 继电器接触器控制系统应用拓展实践——“玩转”双电机
  9. json 格式化工具/网站
  10. 关于websocket兼容IE版本
  11. Spark 关于提交任务报错 WARN scheduler.TaskSchedulerImpl: Initial job has not accepted any resources;
  12. 高频迷你信号发生器性能及其优势
  13. Win7不显示移动硬盘盘符的解决方法
  14. Python实现回归树
  15. 跑了这么久,物流机器人怎么还没跑进千家万户?
  16. 风暴孵化:手游代理应该如何去选择平台和游戏?
  17. xcode写在_迷失在Xcode领域
  18. 中国科学院计算机网络信息中心科学数据中心,中科院计算机网络信息中心简介...
  19. HDU 1166 线段树
  20. python单元测试uuitest模块

热门文章

  1. oracle的in的值超过3000,处理 Oracle SQL in 超过1000 的解决方案
  2. ip pim spare 源树 和 共享树_Pim通用规则+Dense模式规则+Sparse模式规则
  3. Jmeter性能测试之JDBC Request连接数据库
  4. prometheus 插件node_exporter 启动方式
  5. mysql大于号是否使用索引
  6. Java怎么测并发量_如何测试一个web网站的性能(并发数)?
  7. 计算机二级在学校报还是外面报好,谁知道考计算机二级在哪个学校比较好啊?...
  8. python语言的格式框架_django框架模板语言使用方法详解
  9. php通过浏览器下载json文件遇到的问题
  10. 由laravel 5.5无法获取url中的参数引发的apache的.htaccess文件问题