C语言和汇编语言函数调用关系

1.汇编语言函数调用

X86结构中,cs寄存器和rip寄存器共同控制着CPU要执行的下一条指令(当前在不同的模式中控制方式不同,如:实地址2模式和保护模式,长模式等),一般会按照指令在内存中存储的顺序,依次执行。如果想要在普通程序(除去系统调用和中断)执行中跳转到某一条指令,就需要使用JMPCALLRET及其变种指令。

  • jmp指令是无条件跳转指令,直接跳转到某条指令。
  • call指令在执行时,会先将下一条指令地址压栈,然后跳转到目标指令。
  • ret指令执行时,会从当前栈顶弹出目标指令的地址,然后跳转到目标指令。

利用上述相关指令进行搭配,就可以实现在一块汇编代码(a)执行中,跳转到另一块汇编代码(b),执行完(b)后返回到(a)的下一条指令继续执行;从而可以实现函数的调用和返回。

2.c语言函数调用

通过分析c语言编译->反汇编后汇编代码,可以看出c语言的调用方式,注意不同的编译环境调用机制可能不一样,实验环境是gcc 4.4.7

1.测试代码

int add(int a,int b){int c = a+b;return c;
}void main(){int i = 0;int j = 1;add(i,j);
}

2.反汇编后的汇编代码

栈的生长方向是由高地址到低地址,即压栈时(pop),rsp寄存器会减小;出栈时(push),rsp寄存器会增大。

leaveq等效于movq %rbp, %rsp; popq %rbp;这条指令的主要为了对应函数入口处的push %rbp;mov %rsp,%rbp。

000000000000009a <add>: ;add函数机器码和对应的汇编代码(64位)
; 地址   机器码                   汇编代码9a:    55                      push   %rbp9b:  48 89 e5                mov    %rsp,%rbp9e: 89 7d ec                mov    %edi,-0x14(%rbp)a1:  89 75 e8                mov    %esi,-0x18(%rbp)a4:  8b 45 e8                mov    -0x18(%rbp),%eaxa7:  8b 55 ec                mov    -0x14(%rbp),%edxaa:  8d 04 02                lea    (%rdx,%rax,1),%eaxad:    89 45 fc                mov    %eax,-0x4(%rbp)b0:   8b 45 fc                mov    -0x4(%rbp),%eaxb3:   c9                      leaveq b4:  c3                      retq   00000000000000b5 <main>: ;main函数机器码和对应的汇编代码(64位)
; 地址   机器码                   汇编代码b5:    55                      push   %rbpb6:  48 89 e5                mov    %rsp,%rbpb9: 48 83 ec 10             sub    $0x10,%rspbd:    c7 45 f8 00 00 00 00    movl   $0x0,-0x8(%rbp)c4:   c7 45 fc 01 00 00 00    movl   $0x1,-0x4(%rbp)cb:   8b 55 fc                mov    -0x4(%rbp),%edxce:   8b 45 f8                mov    -0x8(%rbp),%eaxd1:   89 d6                   mov    %edx,%esid3: 89 c7                   mov    %eax,%edid5: 48 b8 9a 00 00 00 00    mov    $0x9a,%raxdc:    00 00 00 df:    ff d0                   callq  *%raxe1: 89 45 f8                mov    %eax,-0x8(%rbp)e4:   c9                      leaveq e5:  c3                      retq

分析上述反汇编后的汇编代码。

main函数和add函数的入口部分代码和出口部分代码都是:

;函数入口
push %rbp   ;保存rbp的值
mov %rsp,%rbp ;将当前栈顶数值(a)->rbp寄存器
;中间部分,不会改变rbp寄存器的值
;函数出口
;以下2条汇编代码等同于leaveq
mov %rbp,%rsp ;将rbp寄存器的数值(a)->rsp,现在栈顶的数就是入口保存的rbp值
popq %rbp ;将栈顶值恢复到rbp寄存器

可以得出所有c语言函数的入口和出口部分都会保存和恢复rbp的值,并且在保存rbp旧值到当前栈顶后,会将当前栈顶赋值给rbp;阅读函数中间部分汇编代码,发现不会改变rbp的值了,这样的话不管函数中间部分如何改变rsp的值(可能该函数会使用push或pop指令,或者使用call指令进行了压栈等等可以改变rsp寄存器的值的指令),最终都可以在函数出口时,将栈环境恢复到入口未执行时的栈环境(栈环境指:rsp和rbp寄存器的值,还有栈中的值)。

add函数中间部分:

mov    %edi,-0x14(%rbp) ;保存第一个参数的值
mov    %esi,-0x18(%rbp) ;保存第二个参数的值
mov    -0x18(%rbp),%eax
mov    -0x14(%rbp),%edx
lea    (%rdx,%rax,1),%eax
mov    %eax,-0x4(%rbp) ;保存局部变量c的值
mov    -0x4(%rbp),%eax ;保存返回值到eax

首先rbp在入口部分结束时,被赋予了新值(当前栈顶),其次因为栈时由高地址向低地址扩展;所以上述代码等于先将edi和esi的值保存到内存栈扩展方向的某2个位置上(rbp-0x14,rbp-0x18),然后将内存中这两个值读出来进行运算,完成后保存到内存栈扩展方向的另一个位置上(rbp-0x4);通过对比add函数的c语言代码,可以发现内存(rbp-0x14,rbp-0x18)位置最终保存该函数的2个输入参数,内存(rbp-0x4)位置,最终保存局部变量c;由此得出,寄存器edi,esi分别为add传入了2个参数,寄存器eax保存了返回参数。

观察main函数中间部分,来验证上述假设:

sub    $0x10,%rsp        ;rsp=rsp-0x10 使得在callq调用时,将下一条指令地址保存到(rsp-0x10)位置
movl   $0x0,-0x8(%rbp)  ;保存第一个局部变量i的值0
movl   $0x1,-0x4(%rbp)  ;保存第二个局部变量j的值1
mov    -0x4(%rbp),%edx
mov    -0x8(%rbp),%eax
mov    %edx,%esi        ;将j的值保存到esi中(第二个参数)
mov    %eax,%edi        ;将i的值保存到edi中(第一个参数)
mov    $0x9a,%rax       ;将add函数的入口地址保存到rax中callq  *%rax            ;调用add函数(将下一条指令地址压栈,然后跳转到0x9a处执行,即跳到add函数执行第一条指令)
mov    %eax,-0x8(%rbp)  ;将返回值赋值给i,(rbp-0x8)位置代表变量i

当改变add函数的输入参数为一个float变量和一个int变量时:

int add(float a,int b){int c = a+b;return c;}

查看反汇编后的汇编代码:

0000000000000027 <add>:27: 55                      push   %rbp28:  48 89 e5                mov    %rsp,%rbp2b: f3 0f 11 45 ec          movss  %xmm0,-0x14(%rbp)30: 89 7d e8                mov    %edi,-0x18(%rbp)33:  f3 0f 2a 45 e8          cvtsi2ssl -0x18(%rbp),%xmm038:  f3 0f 58 45 ec          addss  -0x14(%rbp),%xmm03d: f3 0f 2c c0             cvttss2si %xmm0,%eax41: 89 45 fc                mov    %eax,-0x4(%rbp)44:   8b 45 fc                mov    -0x4(%rbp),%eax47:   c9                      leaveq 48:  c3                      retq

可以发现函数会从xmm0寄存器获取输入的float参数。
add函数和mian函数返回时,都使用了下述指令:

retq

该指令时是64位时,ret指令的变种。因为执行到该指令时,被调用函数的出口部分已经执行完毕,栈恢复到调用者刚进入时的环境,被调用者执行ret指令将会弹出当前rsp指向的栈顶的指令地址,然后跳转到该指令执行。所以在a函数调用b函数时,要确保跳转到b入口处时,当前rsp指向的栈顶保存了a函数在b函数返回后想要指行的指令地址。返回值保存到了寄存器eax中。

3.总结

如果将调用者mian函数callq指令和被调用者add函数的retq指令联系起来,如下:

00000000000000b5 <main>: ;main函数机器码和对应的汇编代码(64位)
; 地址   机器码                   汇编代码b9:    48 83 ec 10             sub    $0x10,%rsp  ;将rsp栈指针赋予新值newRsp,相当于初始化了一个空栈newStack;...........d5: 48 b8 9a 00 00 00 00    mov    $0x9a,%raxdc:    00 00 00  df:   ff d0                   callq  *%rax       ;将mian函数下一条指令地址(0xel)压到栈newStack,然后跳转到(0x9a)执行e1: 89 45 f8                mov    %eax,-0x8(%rbp)  ;add函数retq执行后,跳回到该指令继续执行main函数。;...........e4:   c9                      leaveq             ;mian函数执行完成后,恢复到main函数入口时的栈环境,同时销毁了newStack(rsp = rbp);...........
000000000000009a <add>: ;add函数机器码和对应的汇编代码(64位)
; 地址   机器码                   汇编代码9a:    55                      push   %rbp        ;将mian函数的rbp保存到新栈newStack9b: 48 89 e5                mov    %rsp,%rbp   ;让rbp指向新栈newStack栈顶;........................b3:  c9                      leaveq             ;add业务代码执行完成,恢复到入口时的新栈newStack环境,此时新栈newStack中只保存了(0xel),即main下一条指令地址b4:  c3                      retq               ;弹出0xel,并跳转到(0xel)执行,此时rsp=newRsp,恢复栈环境到mian函数未执行Call时。

当执行一个c语言函数X时,都会在执行到函数X的第一条指令前,初始化好一个栈(rsp减去一个栈单位的地址)是当前函数X的栈基址—减去一个栈单位,是因为在函数X退出时(ret)会额外在栈中弹出一个栈单位的数作为返回地址,因为ret指令也是在当前函数X中执行,因此函数X的栈基地址应该为函数X执行第一条指令前的(rsp减去一个栈单位的地址)

栈底部保存着函数的返回地址(即函数X执行完后,要返回到哪个地址处执行指令)和函数X入口时的rbp值,紧接着保存函数X会使用到的局部变量和函数参数。函数X如果有输入参数,则按照从左向右的顺序依次将通用寄存器RDI、RSI、RDX、RCX、R8和R9中的参数值保存到栈中;同时,寄存器XMM0~XMM7用来获取浮点参数,而RAX寄存器则用于保存函数的返回值;因为函数的所有参数还有局部变量都会被保存到该函数的栈中,在参数保存到栈中后,对于这些寄存器就可以随意使用,因为对于变量和参数的赋值都会直接通过进行。

函数X执行结束返回后,会使当前的rsp指向函数 X的栈基址

3.参考

AMD64 Architecture Programmer’s Manual, Volume 1: Application Programming

一个64位操作系统的设计与实现 第 2 章 环境搭建及基础知识

自制操作系统GitHub地址

C语言和汇编语言函数调用相关推荐

  1. C语言与汇编语言相互调用原理以及实例

    下面两个分别是一个foo.asm(汇编语言文件),bar.c(c语言文件) 首先来了解C语言为什么能调用汇编语言,以及汇编语言为什么能调用C语言.其实不管是C语言还是汇编语言想要执行都是最终编译链接成 ...

  2. c语言可以调用汇编语言吗,C语言与汇编语言混编方式

    C语言是目前非常流行的一种编程语言,除具有高级语言使用方便灵活.数据处理能力强.编程简单等优点外:还可实现汇编语言的大部分功能,如可直接对硬件进行操作.生成的目标代码质量较高等,而汇编语言没有高级语言 ...

  3. C语言数据交换算法和伪指令,补充:单片机c语言与汇编语言混合编程.ppt

    补充:单片机c语言与汇编语言混合编程 reg51.h和reg52.h:实质上是没有区别的,都是一些特殊功能寄存器的申明 sfr P0 = 0x80; sfr P1 = 0x90; sfr P2 = 0 ...

  4. keil4c语言和汇编混合,keil C语言与汇编语言混合编程

    keil C语言与汇编语言混合编程 1. C语言中嵌入汇编 1.在 C 文件中要嵌入汇编代码片以如下方式加入汇编代码: #pragma ASM ; Assembler Code Here #pragm ...

  5. c语言与汇编语言混合编程

    如何从汇编语言过渡到c语言? 从编译过程谈起 编译小知识 源代码编译后得到目标文件 (二进制文件) 不同语言可编译得到相同格式的目标文件 链接器负责将目标文件组装得到可执行文件 老生常谈的问题... ...

  6. c语言与汇编语言混合编程实验,C语言与汇编语言混合编程实验

    混合编程方法: 模块链接法 汇编指令嵌入法 1: 模块链接法则 模块链接法是指分别用汇编语言和C语言实现独立的模块(或子程序),再用链接程序把各模块生成的obj文件连接成一个可执行程序. 1:C语言调 ...

  7. ARM下C语言和汇编语言混合编程

    文章目录 一. 在C语言中调用汇编语言 1. 编写代码 2. 调试验证 二. 在汇编语言中调用C语言 1. 编写代码 2. 调试验证 关于函数的传递参数以及接受返回值的原理,在另一篇博客:X86与AR ...

  8. 嵌入式c语言汇编混合编程,嵌入式C语言和汇编语言的混合编程

    此文章简单介绍 单片机 C语言和汇编语言混合编程的例子.主要用单片机汇编语言编写DS1302的底层驱动,在C语言里通过调用汇编语言,从而实现C和汇编的混合编程. ;汇编语言源文件 ;========= ...

  9. 汇编和python-python语言属于汇编语言吗?_后端开发

    c语言主函数名是什么?_后端开发 C语言主函数名是main,main函数又称主函数,是程序执行的起点,如果有其他函数,则完成对其他函数的调用后再返回到主函数,最后由main函数结束整个程序. pyth ...

最新文章

  1. 为什么分布式一定要有延时任务?
  2. Linux运维必会的实战编程笔试题(19题)
  3. linux18.04安装显卡驱动,详细介绍ubuntu18.04安装NVIDIA显卡驱动(亲测有效!)
  4. 00018计算机应用2018年4月,全国2019年4月自考00018《计算机应用基础》试题及答案...
  5. 程序员你写的代码,被人挖出了黑产
  6. 【计算机网络】物理层设备
  7. 复制书稿(信息学奥赛一本通-T1278)
  8. 《Linux编程》上机作业 ·005【进程管理与通信】
  9. 1 Oracle数据库环境搭建
  10. 孙鑫VC学习笔记:第十三讲 (五) 保存可串行化的类对象 如何获取文档与视类指针
  11. Android Listener侦听的N种写法
  12. python贝叶斯分析方法实例_python 贝叶斯分析对应的代码
  13. java数组下标从几开始的_为什么数组角标从0开始
  14. aes key iv从mysql_OpenSSL AES 算法中 Key 和 IV 是如何生成的?
  15. Mysql 窗口函数
  16. NB-IOT中eNB是什么,eNB的作用是什么
  17. 撸一个聊天室(vue+koa2+websokect+mongodb)
  18. pdf转换成jpg转换器教程
  19. 搭配Online|原光辉调研沁水县文物保护和城市建设工作
  20. Authentication failed for 解决办法

热门文章

  1. Pathon 编写程序在屏幕中心绘制正方形
  2. HTTP Referer 教程
  3. 【FFMPEG】解决截取MP4视频的中间段时,截取完成后前几帧视频卡住,但是有声音的情况
  4. 自写网络验证,支持注册 充值 在线消息 自动更新
  5. 毕设论文中第一章的图注出现“图一.1”,转化为“图1.1”的方法
  6. 调gensim库,word2vec模型的保存和加载
  7. 商务领航的网关问题解决
  8. 各种表格扫描件OCR识别为电子表格的技术
  9. leetcode 1796
  10. 初学者的长角牛的攻击和防御实验