C语言函数调用过程

2023-11-20

  •  0 推荐
  •  0 收藏,392 浏览

*** 本文是《老码识途》第一章的读书笔记 ***

函数调用

例子代码如下所示:

int Add(int x, int y) {
    int sum;
    sum = x + y;
    return sum;
}

void main() {
    int z;
    z = Add(1, 2);
    printf("z=%d\n", z);
}

下面分析一下 Add函数的调用过程。

首先断点在z = Add(1, 2);处, 反汇编如下所示:

    int z;
    z = Add(1, 2);
002C141E 6A 02                push        2  
002C1420 6A 01                push        1  
002C1422 E8 60 FC FF FF       call        002C1087  
002C1427 83 C4 08             add         esp,8  
002C142A 89 45 F8             mov         dword ptr [ebp-8],eax

首先压入参数1和2:

002C141E 6A 02                push        2  
002C1420 6A 01                push        1  

通过观察ESP可以看到参数从右到左依次入栈,ESP往低内存方向移动8字节:

ESP=0025FCCC
...
0x0025FCAA  00 00 78 4c 33 00 bc fc 25 00 a9 fe aa 0f 78 4c 33 00 c8 fc 25 00 3d 5a b2 0f *** 01 00 00 00 02 00 00 00 ***
0x0025FCCC  00 00 00 00

然后执行:

002C1422 E8 60 FC FF FF       call        002C1087  

call指令执行时,首先压入call指令的返回地址,即add esp,8这一句的地址002C1427:

0x0025FCAA  00 00 78 4c 33 00 bc fc 25 00 a9 fe aa 0f 78 4c 33 00 c8 fc 25 00 *** 27 14 2c 00 *** 01 00 00 00 02 00 00 00

然后跳转到02C1087。02C1087处为jmp语句,跳转到Add函数入口地址002C13C0:

int Add(int x, int y) {
002C13C0 55                   push        ebp  
002C13C1 8B EC                mov         ebp,esp  
002C13C3 81 EC CC 00 00 00    sub         esp,0CCh  
002C13C9 53                   push        ebx  
002C13CA 56                   push        esi  
002C13CB 57                   push        edi  
002C13CC 8D BD 34 FF FF FF    lea         edi,[ebp+FFFFFF34h]  
002C13D2 B9 33 00 00 00       mov         ecx,33h  
002C13D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
002C13DC F3 AB                rep stos    dword ptr es:[edi]  
    int sum;
    sum = x + y;
002C13DE 8B 45 08             mov         eax,dword ptr [ebp+8]  
002C13E1 03 45 0C             add         eax,dword ptr [ebp+0Ch]  
002C13E4 89 45 F8             mov         dword ptr [ebp-8],eax  
    return sum;
002C13E7 8B 45 F8             mov         eax,dword ptr [ebp-8]  
}
002C13EA 5F                   pop         edi  
002C13EB 5E                   pop         esi  
002C13EC 5B                   pop         ebx  
002C13ED 8B E5                mov         esp,ebp  
002C13EF 5D                   pop         ebp  
002C13F0 C3                   ret  

获取参数

目前为止,栈上的情况如下图所示,从上往下内存地址从高到低:

          +----------------+
          |       2        |
          +----------------+
          |       1        |
          +----------------+
ESP       | return address |
 +------> +----------------+

此时参数可由ESP + 4,ESP + 8获得。但是由于程序执行时ESP会变化,为了方便定位栈上的数据,引入EBP(Extended Base Pointer,扩展基址指针寄存器),保存进入函数时ESP的值。
由于函数可以嵌套调用,所以在进入函数时必须将EBP的旧值保存起来,以防覆盖EBP导致函数返回后无法恢复EBP。这里通过将EBP压入栈来保存旧值。如:

002C13C0 55                   push        ebp  
002C13C1 8B EC                mov         ebp,esp  
...
002C13EF 5D                   pop         ebp  

所以在函数开头有如下代码:

int Add(int x, int y) {
002C13C0 55                   push        ebp  
002C13C1 8B EC                mov         ebp,esp

此时栈上的内存布局如下图所示:

          +----------------+
          |       2        |
          +----------------+
          |       1        |
          +----------------+
          | return address |
          +----------------+
ESP       |     ebp        |
 +------> +----------------+

取出1,2参数的代码如下所示:

    int sum;
    sum = x + y;
002C13DE 8B 45 08             mov         eax,dword ptr [ebp+8]  
002C13E1 03 45 0C             add         eax,dword ptr [ebp+0Ch]

其中ebp+8取出参数1,ebp+0Ch取出参数2(0C为十进制的12),然后计算结果放在EAX中。

初始化堆栈和分配局部变量

接下来有如下代码段,将esp下移0CC,然后push ebx,esi,edi这三个寄存器:

002C13C3 81 EC CC 00 00 00    sub         esp,0CCh  // 1
002C13C9 53                   push        ebx  
002C13CA 56                   push        esi  
002C13CB 57                   push        edi

其中语句1是为了给局部变量分配足够大的栈空间,然后再保存三个寄存器的值。局部变量利用ebp定位,存于ebp和OCCh之间。

ebx,esi,edi的作用如下所示来源

寄存器%ebx、%esi和%edi为被调函数保存寄存器(callee-saved registers),即被调函数在覆盖这些寄存器的值时,必须先将寄存器原值压入栈中保存起来,并在函数返回前从栈中恢复其原值,因为主调函数可能也在使用这些寄存器。

然后运行:

002C13CC 8D BD 34 FF FF FF    lea         edi,[ebp+FFFFFF34h]  
002C13D2 B9 33 00 00 00       mov         ecx,33h  
002C13D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
002C13DC F3 AB                rep stos    dword ptr es:[edi]  

首先,通过lea指令,将ebp+FFFFFF34h地址(栈帧的底部)写入edi。然后设置ecx和eax,最后运行rep stos语句。

rep stos dword ptr es:[edi]语句的意思是:将栈上从ebp+FFFFFF34h开始的位置向高地址方向的内存赋值eax(0xCCCCCCCC),次数重复ecx(0x33, 51)次。每运行一次edi的值会增加。注意0xCCCCCCCC代表着未被初始化(int3中断)。这样做的原因是防止分配好的局部变量空间中的代码被意外执行。

局部变量

示例代码中,x+y的结果保存在局部变量sum中,由如下代码可知,sum分配在栈上。

    int sum;
    sum = x + y;
002C13DE 8B 45 08             mov         eax,dword ptr [ebp+8]  
002C13E1 03 45 0C             add         eax,dword ptr [ebp+0Ch]
002C13E4 89 45 F8             mov         dword ptr [ebp-8],eax   <<==== 结果保存在局部变量sum中

目前栈的分配情况如下所示:

          +----------------+                
          |       2        |                
          +----------------+                
          |       1        |                
          +----------------+                
          | return address |                
          +----------------+                
          |      ebp       |                
          +----------------+                
          |       ?        |                
          +----------------+               
ESP       |      sum       |                
 +------> +----------------+                

“?”处的4字节是编译器为了防止溢出攻击而设置的。

返回值

函数返回处的代码如下:

002C13EA 5F                   pop         edi  
002C13EB 5E                   pop         esi  
002C13EC 5B                   pop         ebx  
002C13ED 8B E5                mov         esp,ebp  
002C13EF 5D                   pop         ebp  
002C13F0 C3                   ret  

函数返回时需要考虑两件事情:恢复栈和保存返回值。

恢复栈

首先,通过pop栈恢复edi,esi和ebx的值,然后将esp恢复到ebp处,然后pop ebp,将ebp恢复旧值。此时esp指向return address:

          +----------------+                
          |       2        |                
          +----------------+                
          |       1        |                
          +----------------+                
 ESP      | return address |                
  +------>+----------------+                
          |      ebp       |                
          +----------------+                
          |       ?        |                
          +----------------+               
          |      sum       |                
          +----------------+  

接下来运行ret指令。ret指令会将栈顶保存的地址压入指令寄存器EIP,相当于pop eip。运行后EIP和ESP都会有变化。

然后程序跳转到return address处,如下所示:

     
     
<button href="javascript:void(0);" _xhe_href="javascript:void(0);" class="copyCode btn btn-xs" data-clipboard-text="" int="" z;"="" data-toggle="tooltip" data-placement="bottom" title="" style="color: rgb(255, 255, 255); font-family: inherit; font-size: 12px; font-style: inherit; font-variant: inherit; line-height: 1.5; margin: 0px 0px 0px 5px; overflow: visible; cursor: pointer; vertical-align: middle; border: 1px solid transparent; white-space: nowrap; padding-right: 5px; padding-left: 5px; border-radius: 3px; -webkit-user-select: none; box-shadow: rgba(0, 0, 0, 0.0980392) 0px 1px 2px; background-image: none; background-color: rgba(0, 0, 0, 0.74902);">复制
int z; z = Add(1, 2); 002C141E 6A 02 push 2 002C1420 6A 01 push 1 002C1422 E8 60 FC FF FF call 002C1087 002C1427 83 C4 08 add esp,8 // ret跳转到此处 002C142A 89 45 F8 mov dword ptr [ebp-8],eax

其中add esp,8语句的目的是将1,2参数出栈,将栈恢复到函数调用之前的状态。接下来便可以从eax中取出返回值:

002C142A 89 45 F8             mov         dword ptr [ebp-8],eax

由于ebp已被恢复,故其中ebp-8即为临时变量z的地址

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言函数调用过程 的相关文章

  • 应用程序中所有函数调用的列表

    我们如何列出应用程序中调用的所有函数 我尝试使用 GDB 但它的回溯列表仅到主函数调用 我需要更深入的列表 即主函数调用的所有函数以及从这些被调用函数调用的函数的列表 等等 有没有办法在 gdb 中得到这个 或者你能给我一些关于如何获得这个
  • GDB:警告:在重载方法上设置了多个断点

    anisha linux dopx gt g Wall pedantic breakpoints cpp g anisha linux dopx gt gdb a out gdb b X X Breakpoint 1 at 0x400ac1
  • 显示 GDB 中当前的汇编指令

    我正在 GDB 中进行一些汇编级调试 有没有办法让 GDB 以与显示当前源代码行相同的方式显示当前的汇编指令 每个命令后的默认输出如下所示 0x0001433f 990 Foo bar p 这给了我当前指令的地址 但我必须继续参考disas
  • 使用gdb将地址转换为行

    我有一个由剥离的应用程序生成的堆栈跟踪 如下所示 Check failure stack trace 0x7f0e442d392d unknown 0x7f0e442d7b1f unknown 0x7f0e442d7067 unknown
  • 如何在Windows上构建GDB

    如何在 Windows 上从源代码构建 GDB GNU 调试器 我需要构建它才能使用 Python 支持进行构建 我无法使用随 Cygwin 一起发布的版本 因为它在 Windows 上解释反斜杠时出现问题 构建需要哪些工具链 GnuWin
  • 是什么让GDB拒绝崩溃?

    我在这里不知所措 我正在用 C 编写一个编译器 出于爱好 并使用 GDB 7 3 在 amd64 Linux 2 6 32 上使用 GCC 4 6 1 进行编译 除了通常的 I 等之外 标志还有 Wall Wextra O0 g 我有一个函
  • gdb:显示源代码和asm中的相应行

    在 TUI 模式下运行 gdb 并显示源代码和汇编时 是否有一个选项可以突出显示映射到选定源代码行的指令集 您可以使用 GDB Dashboard 部分完成此操作 https github com cyrus and gdb dashboa
  • windows下无法使用mingw/gdb在eclipse helios cdt上调试小程序,控制台冻结

    我一直在尝试使用 Eclipse CDT 来做一些 C 示例 我可以使用 run 命令很好地运行它们 但是每当我尝试调试时 控制台窗口就会冻结 我可以输入 但程序不运行不继续了 当我调试时 我在控制台窗口上得到以下输出 没有断点 但由于默认
  • 如何在使用 GDB 遍历代码时禁用 C++ 模板中的单步执行?

    我试图使用 GDB 遍历代码 而 GDB 总是尝试显示 C 模板源代码 这使得调试不方便并且浪费了我很多时间 GDB 尝试介入该函数 当它找不到实现模板的文件时 它会显示错误 或者它会跳转到我不想看到的模板代码 我找不到如何禁用显示 单步进
  • 在 Linux 的 gdb 中启用 memleak 选项

    我试图查看哪个进程导致内存泄漏 并转储导致它的堆以查看问题所在 我应该在 gdb 中使用哪个命令来设置内存泄漏并检查堆 I tried gdb check leaks checkpoint can t find fork function
  • 为什么 GNU binutils 和 GDB 合并为一个包?

    https sourceware org git gitweb cgi p binutils gdb git https sourceware org git gitweb cgi p binutils gdb git 尤其是请参阅tags
  • 将大核心文件转换为“minicore”文件

    如何将核心文件减少到仅线程堆栈 我希望能够运行 gdbthread apply all bt在迷你核心上 仅此而已 我正在处理大型 gt 4GB 多线程 Linux ELF 核心文件 这些文件太大而无法返回进行分析 我见过谷歌断点器 htt
  • 使用 gdb 在指定的可执行文件之外单步执行汇编代码会导致错误“无法找到当前函数的边界”

    我在 gdb 的目标可执行文件之外 甚至没有与该目标对应的堆栈 无论如何 我想单步执行 以便我可以验证汇编代码中发生了什么 因为我不是 x86 汇编方面的专家 不幸的是 gdb 拒绝进行这种简单的汇编级调试 它允许我设置并停止在适当的断点上
  • 如何在 gdb 中打印长字符串的完整值?

    我想在 GDB 中打印 C 字符串的完整长度 默认情况下它是缩写的 如何强制 GDB 打印整个字符串 set print elements 0 来自GDB手册 https sourceware org gdb onlinedocs gdb
  • GDB可以杀死一个特定的线程吗?

    我正在运行一个应用程序 firefox 我想知道是否可以使用 GDB 附加到进程并杀死特定线程 有没有办法做到这一点 我知道此操作可能会使应用程序崩溃 EDIT 在此调试会话中 ps ax显示firefox pid是1328 gdb App
  • 使用 gdb 调试时彻底退出 valgrind

    我正在使用 valgrind 和 gdb 调试程序 然而 我以一种野蛮的方式终止了这些调试会话 这真的是它应该做的吗 设置调试会话 按照来自的指示valgrind 官方网站 http valgrind org docs manual man
  • 在 gdb 中设置应用程序关联

    有没有一种简单的方法可以设置我正在调试的应用程序的亲和力 而无需将 gdb 锁定到同一核心 我问的原因是应用程序以实时优先级运行 并且需要在单核上运行 目前我使用这个命令行 taskset c 3 gdbserver 1234 app ou
  • 在 GDB 中显示结构体值

    在 GDB 中 给定一个指向结构体的变量 print将显示原始指针值并x将显示指向的原始字节 有什么方法可以显示指向该结构的数据 即字段及其值的列表 print variable 如果这样做 它将在 GDB 中显示该变量的值 您还可以选择显
  • 使用 gdb 调试反汇编库

    在Linux和Mac OS X中可以使用strapi和next来调试应用程序而无需调试信息 在 Mac OS X 上 gdb 显示在库内部调用的函数 尽管有时会在每个 stepi 指令中推进多个汇编程序指令 在 Linux 上 当我进入动态
  • “劣质调试器”一词中的“劣质”是什么意思?

    我不太明白GDB手册中对inferior的解释 谷歌也没有提供任何更有帮助的信息 谁能简单地解释一下 低等 劣质 是一个通用术语 表示 您正在使用 gdb 来调试的东西 通常是在模拟器或通过串行线路连接的其他硬件上运行的进程或内核 当您使用

随机推荐

  • 选择结构程序设计和循环控制(if语句,switch语句,条件运算符,for循环,while及do while语句,break,continue,goto语句)折半查找法及猜数字游戏带你巩固理解

    本章我们来迅速学习一下选择语句和循环语句 首先我们来了解何为语句 C语言中语句可以分为以下五类 1 表达式语句 2 函数调用语句 3 控制语句 4 复合语句 5 空语句 这篇文章我们主要讲的是控制语句 控制语句 控制语句主要用于控制程序的运
  • IDEA 三种注释生成方式

    三种注释方式 行注释 块注释 方法或类说明注释 一 快捷键 Ctrl 使用Ctrl 添加行注释 再次使用 去掉行注释 二 演示代码 if hallSites null hallSites size gt 0 行注释 最大行号 int max
  • 【Python】基于Python利用熵权法计算数据权重——一个简单的图形用户界面编程

    目录 1 简介 2 技术流程 3 数据 4 代码 4 1 代码 极差标准化 4 2 代码 熵权法赋权 5 实验操作与流程 6 关于数据获取 1 简介 师弟又催我给他公众号写文了 这次还点名要这个题目 所以我就先到自己的博客里写写练练手 下面
  • ​Java 输入一个字符串,统计其中字符 A 的数量并输出。

    题目描述 输入一个字符串 统计其中字符 AA 的数量并输出 输入 输入一个不带空格的字符串 字符串长度不超过 100 输出 输出字符串中字符 A 的数量 样例输入 AabdkeaoektA 样例输出 2 import java util S
  • sublime text 3上使用Git连接Github

    首先下载的sublime text 3我是在PHP中文网下载的 是汉化版 一进去就有git插件 之前很久下过的好像不能使用插件 所以我就下了最新的汉化版 进去就有git插件 再者就是git要自己下载一下安装 配置好path环境变量 也就是c
  • 点击按钮复制链接

    做点击按钮复制链接 网上找的方法是用原生js document execCommand Copy window clipboardData setData Text url value 发现微信上存在不兼容 在安卓和PC段都可以 但是在苹果
  • 2023问题汇总

    问题汇总 Linux相关 1 vim 修改挂载文件时 报错 read only filesystem 2 root 用户密码无法更改 3 linux 用户被锁定 4 linux 查看登录日志 其他小问题 1 linux 的dns 修改配置
  • 企微报错60020

    errcode 60020 errmsg not allow to access from your ip hint 1655882753557010848204971 from ip more info at https open wor
  • wazuh安装手册

    一 wazhu部署架构 1 服务器上运行的Agent端会将采集到的各种信息通过加密信道传输到管理端 2 管理端负责分析从代理接收的数据 并在事件与告警规则匹配时触发警报 3 LogStash会将告警日志或者监控日志发送到Elasticsea
  • 第九章 细分着色器

    第九章 细分着色器 细分面片 细分着色器只能处理面片 patch 类型的图元 如果启用细分着色器 将其他类型图元传递给它会产生GL INVALID OPERATION错误 如果没有启用细分着色器 那么渲染面片数据也会得到GL INVALID
  • 【JFinal最省代码的框架】JFinal+Bootstrap实现后台管理系统主页

    原文 JFinal最省代码的框架 JFinal Bootstrap实现后台管理系统主页 源代码下载地址 http www zuidaima com share 1893049233067008 htm
  • 爱情和婚姻的区别

    有一天 柏拉图问老师苏格拉底什么是爱情 老师就让他先到到麦田里去 摘一棵全麦田里最大最金黄的麦穗来 期间只能摘一次 并且只可向前走 不能回头 柏拉图于是按照老师说的去做了 结果他两手空空的走出了田地 老师问他为什么摘不到 他说 因为只能摘一
  • 【DevOps视频笔记】6 - 7. Jenkins 介绍 和 安装

    一 Integrate 工具 二 Jenkins 介绍 1 Jenkins 最主要的工作 2 CI CD 可以理解为 2 1 CI 过程 2 2 CD 过程 三 Jenkins 安装 1 安装准备工作 2 安装 Jenkins Stage
  • 环境配置--解决torch.cuda.is_available()返回:False

    在网上很多都说是因为pytorch版本和CUDA不匹配造成的 但对于我的情况并不是 而是因为在官网默认下载到了的pytorch是cpu版本 真坑 另外在pycharm默认下载到的pytorch 1 9 0也是cpu版本 用不了cuda 1
  • 计算机设计大赛答辩提问,电子设计大赛答辩常见问题合集

    电子设计大赛答辩常见问题合集 由会员分享 可在线阅读 更多相关 电子设计大赛答辩常见问题合集 2页珍藏版 请在人人文库网上搜索 1 答辩常见问题合集1 本课题的选课背景 意义等等 这个论文中有的 也都是一些套话 我就不答了 我整理的都是技术
  • PLSQL创建新用户并导入导出.dmp文件

    一 登录管理员账号 用户名 密码 登录身份 说明 system manager SYSDBA 或 NORMAL 不能以 SYSOPER 登录 可作为默认的系统管理员 sys change on install SYSDBA 或 SYSOPE
  • 电路设计相关

    本人才疏学浅 孤陋寡闻 下文若有不当之处 还请赐教 1 一些概念 施密特触发器 施密特触发器采用电位触发方式 其状态由输入信号电位维持 对于负向递减和正向递增两种不同变化方向的输入信号 施密特触发器有不同的阈值电压 对于标准施密特触发器 当
  • 天眼使用指南--分析平台

    天眼分析平台 提供全面的溯源分析能力 涵盖图中模块 负责存储日志 分为三类 告警日志 告警日志 来自探针和沙箱的告警 探针的告警可以记录双向完整对话 如果网络流量中没有恶意信息 就会储存一些关键信息 如http请求部分状态码 tcp上下前一
  • 用python批量插入clickhouse

    用python批量插入clickhouse 以下是一个示例代码 演示了如何使用clickhouse driver模块实现批量插入和分布式查询 import clickhouse driver import random connect to
  • C语言函数调用过程

    文 读书笔记 C语言函数调用过程 c legendmohe 2015年07月12日发布 推荐 0 推荐 收藏 0 收藏 392 浏览 本文是 老码识途 第一章的读书笔记 函数调用 例子代码如下所示 int Add int x int y i