函数调用相关指令

在开始函数调用约定之前我们需要先了解一下几个相关的指令

1.1 push

pushq立即数# q/l是后缀,表示操作对象的大小pushl 寄存器

push指令将数据压栈。具体就是将esp(stack pointer)寄存器减去压栈数据的大小,再将数据存储到esp寄存器所指向的地址。

1.2 pop

popq寄存器popl寄存器

pop指令将数据出栈并写入寄存器。具体就是将数据从esp寄存器所指向的地址加载到指令的目标寄存器中,再将esp寄存器加上出栈的数据的大小。

1.3 call

call立即数call寄存器call内存

call指令会调用由操作数所代表的地址指向的函数,一般都是call一个符号。call指令会将当前指令寄存器中的内容(即这条call指令下一条指令的地址,也就是函数执行完的返回地址)入栈,然后跳到函数对应的地址开始执行。

1.4 ret

ret指令用于从子函数中返回,ret指令会先弹出当前栈顶的数据,这个数据就是先前调用这个函数的call指令压入的“下一条指令的地址”,然后跳转到这个地址执行。

1.5 leave

leave相当于执行了movq %rbp, %rsp; popq %rbp,即释放栈帧。

二、 函数调用约定

函数调用约定约定了caller如何传参即将实参放到何处,应该按照何种顺序保存,以及callee如何返回返回值即将返回值放到何处。

x86的32位机器之上C语言一般是通过栈来传递参数,且一般都是倒序push,即先push最后一个参数再push倒数第二个参数,并通过ax寄存器返回结果,这称为cdecl调用约定(C有三种调用约定,linux系统中使用cdecl),Go与之类似但是区别在于Go通过栈来返回结果,所以Go支持多个返回值。

x64架构中增加了8个通用寄存器,C语言采用了寄存器来传递参数,如果参数超过。在x64系统默认有System V AMD64和Microsoft x64两种C语言函数调用约定,System V AMD64实际是System V AMD64 ABI文档的一部分,类UNIX系统多采用System V的调用约定。

System V AMD64 ABI文档地址https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf

本文主要讨论x64架构下Linux系统的函数调用约定即System V AMD64调用约定。

三、 x64架构下Linux系统函数调用

3.1 如何传递参数

System V AMD64调用约定规定了caller将第1-6个整型参数分别保存到rdi、rsi、rdx、rcx、r8、r9寄存器中,第7个及之后的整型参数从右往左倒序的压入栈中。前8个浮点类型的参数放到xmm0-xmm7寄存器中,之后的浮点类型的参数从右往左倒序的压入栈中。

3.2 如何返回返回值

对于整型返回值要保存到rax寄存器中,浮点型返回值保存到xmm0寄存器中。

3.3 栈的对齐问题

System V AMD64要求栈必须按照16字节对齐,就是说在通过call指令调用目标函数之前栈顶指针即rsp指针必须是16的倍数。之所以要按照16字节对齐是因为x64架构引入了SSE和AVX指令,这些指令要求必须从16的整数倍地址取数,为了兼顾这些指令所以就要求了16字节对齐。

3.4 变长参数

这部分没看懂,待后续发掘。

四、 实际案例分析

4.1 案例1

看下下面这段C代码

unsignedlonglongfoo(unsignedlonglongparam1,unsignedlonglongparam2){unsignedlonglongsum = param1 + param2;returnsum;}intmain(void){unsignedlonglongsum = foo(8589934593,8589934597);return0;}

uname -a: Linux xxx 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 15:04:05 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

gcc -v: gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)

转为汇编代码,gcc -S call.c :

    .file"call.c"    .text    .globlfoo    .typefoo, @functionfoo:.LFB0:    .cfi_startprocpushq  %rbp    .cfi_def_cfa_offset16    .cfi_offset6, -16movq%rsp, %rbp    .cfi_def_cfa_register6movq%rdi, -24(%rbp)movq%rsi, -32(%rbp)movq-32(%rbp), %raxmovq-24(%rbp), %rdx    addq    %rdx, %raxmovq%rax, -8(%rbp)movq-8(%rbp), %rax    popq    %rbp    .cfi_def_cfa7,8ret    .cfi_endproc.LFE0:    .sizefoo, .-foo    .globlmain    .typemain, @functionmain:.LFB1:    .cfi_startprocpushq  %rbp    .cfi_def_cfa_offset16    .cfi_offset6, -16movq%rsp, %rbp    .cfi_def_cfa_register6subq$16, %rsp    movabsq$8589934597, %rsi    movabsq$8589934593, %rdicallfoomovq%rax, -8(%rbp)    movl$0, %eaxleave    .cfi_def_cfa7,8ret    .cfi_endproc.LFE1:    .sizemain, .-main    .ident"GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)"    .section.note.GNU-stack,"",@progbits

我们先看main函数的汇编代码,main函数中首先执行了三条指令:

pushq  %rbp# 将当前栈基底地址压入栈中movq    %rsp, %rbp# 将栈基底地址修改为栈顶地址subq    $16, %rsp# 栈顶地址-16,栈扩容,这里没搞懂为什么要扩容,有懂的同学欢迎评论区指点下

这三条指令是用来分配栈帧的,执行完成后栈变成下方的样子:

继续往下看:

movabsq $8589934597, %rsi# 先将第二个参数保存到rsi寄存器movabsq $8589934593, %rdi# 再将第一个参数保存到rdi寄存器call foo# 调用foo函数,这一步会将下一条指令的地址压到栈上

执行完call foo指令后,栈的情况如下:

然后我们跳到foo函数中看下:

pushq  %rbp# 将当前栈基底地址压入栈中movq    %rsp, %rbp# 将栈基底地址修改为栈顶地址

开头仍然是建立栈帧的指令,执行完成后,此时栈帧的样子如下:

继续往下看:

movq    %rdi, -24(%rbp)movq    %rsi, -32(%rbp)movq    -32(%rbp), %rax# 将第二个参数保存到rax寄存器movq    -24(%rbp), %rdx# 将第一个参数保存到rdx寄存器addq    %rdx, %rax# 执行加法并将结果保存在rax寄存器movq    %rax, -8(%rbp) movq    -8(%rbp), %rax# 将返回值保存到rax寄存器

这里没搞懂为什么需要先挪到内存中再保存到rax寄存器上,可能是编译器实现起来比较方便吧,有懂的同学欢迎评论区指点下

此时栈情况:

foo函数最后执行了以下两条指令:

popq%rbp# 将栈顶值pop出来保存到rbp寄存器,即修改栈基底地址为当前栈顶值,同时栈顶指针-8ret# 从子函数中返回到main函数中

最终结果如图:

4.2 案例2

我们修改下函数foo,使它接收9个参数验证下上面的理论。

unsignedlonglongfoo(unsignedlonglongparam1,unsignedlonglongparam2,unsignedlonglongparam3,unsignedlonglongparam4,unsignedlonglongparam5,unsignedlonglongparam6,unsignedlonglongparam7,unsignedlonglongparam8,unsignedlonglongparam9){unsignedlonglongsum = param1 + param2;returnsum;}intmain(void){unsignedlonglongsum = foo(8589934593,8589934597,3,4,5,6,7,8,9);return0;}

编译为汇编后:

foo:.LFB0:    .cfi_startproc    pushq  %rbp    .cfi_def_cfa_offset16.cfi_offset6, -16movq    %rsp, %rbp    .cfi_def_cfa_register6movq    %rdi, -24(%rbp)    movq    %rsi, -32(%rbp)    movq    %rdx, -40(%rbp)    movq    %rcx, -48(%rbp)    movq    %r8, -56(%rbp)    movq    %r9, -64(%rbp)    movq    -32(%rbp), %rax    movq    -24(%rbp), %rdx    addq    %rdx, %rax    movq    %rax, -8(%rbp)    movq    -8(%rbp), %rax    popq    %rbp    .cfi_def_cfa7,8ret    .cfi_endproc.LFE0:    .size  foo, .-foo    .globl  main    .type  main, @functionmain:.LFB1:    .cfi_startproc    pushq  %rbp    .cfi_def_cfa_offset16.cfi_offset6, -16movq    %rsp, %rbp    .cfi_def_cfa_register6subq    $40, %rsp    movq    $9,16(%rsp)# 后6个参数放到栈上movq    $8,8(%rsp)    movq    $7, (%rsp)    movl    $6, %r9d# 前6个参数分别使用rdi rsi rdx ecx r8 r9寄存器movl    $5, %r8d    movl    $4, %ecx    movl    $3, %edx    movabsq $8589934597, %rsi    movabsq $8589934593, %rdi    call    foo    movq    %rax, -8(%rbp)    movl    $0, %eax    leave    .cfi_def_cfa7,8ret

龙华大道1号 http://www.kinghill.cn/Dynamics/2106.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容