汇编 - 理解函数调用栈

首先介绍下面会用到的几个寄存器:
rsp : 栈指针寄存器,指向栈顶
rbp : 栈基址寄存器,指向栈底
edi : 函数参数
rsi/esi : 函数参数
eax : 累加器或函数返回值用

int test2(int a, int b) {
    int v1 = a + 1;
    int v2 = b + 2;
    int c = v1 + v2 + 3;
    return  c + 4;
}

void test1() {
    int a = 1;
    int b = 2;
    int c = a + b + test2(a, b);
}

int main(int argc, const char * argv[]) {
    test1();
    return 0;
}

首先我们要知道,函数栈里面的内存地址是从高到低的。
下面从main函数开始一句句汇编进行解读:

image.png

0、初始时
rsp = 0x00007ffeefbff418
rbp = 0x00007ffeefbff428

image.png

1、 pushq %rbp
将rbp的地址压栈,rsp继续指向栈顶,所以我们可以看到
rsp = 0x00007ffeefbff418 - 0x8 = 0x00007ffeefbff410
此时栈顶地址存放的内容就是刚才压栈的rbp的地址,即
*0x00007ffeefbff410 = 0x00007ffeefbff428(我这里就用*表示取地址的内容,如果有不懂打印的为什么是反的,可以先去了解下大小端)。

2、 movq %rsp, %rbp
将栈顶rsp的值赋值给栈底rbp,即
rsp = rbp = 0x00007ffeefbff410

image.png

3、 subq $0x10, %rsp
栈顶往下移16个字节,可以理解成给后面预留的16字节的空间。此时
rsp = 0x00007ffeefbff410 - 0x10 = 0x00007ffeefbff400

4、 movl $0x0, -0x4(%rbp)
5、 movl %edi, -0x8(%rbp)
6、 movq %rsi, -0x10(%rbp)
这三句可以理解成将寄存器edi和rsi之前的值先用第三步预留的内存存储下来,因为下面调用函数里面可能会修改这两个寄存器里面的值。

image.png
image.png

7、 callq 0x100002f80
call表示调用函数,同时call有一个作用:将call指令的下一条指令地址压栈。所以此时栈顶
rsp = 0x00007ffeefbff400 - 0x8 = 0x00007ffeefbff3f8
rbp = 0x00007ffeefbff410
并且里面存放的内容就是call下一条指令的地址,即
*0x00007ffeefbff3f8 = 0x100002fdb

从这里开始进入test1函数

8、 pushq %rbp
rbp压栈,则
rsp = 0x00007ffeefbff3f8 - 0x8 = 0x00007ffeefbff3f0
*0x00007ffeefbff3f0 = 0x00007ffeefbff410

9、 movq %rsp, %rbp
将栈顶rsp的值赋值给栈底rbp,即
rsp = rbp = 0x00007ffeefbff3f0

10、 subq $0x10, %rsp
栈顶往下移16个字节,可以理解成给test1函数栈空间分配16字节内存。
rsp = 0x00007ffeefbff3f0 - 0x10 = 0x00007ffeefbff3e0

image.png

11、 movl $0x1, -0x4(%rbp)
将1放入内存地址0x00007ffeefbff3f0 - 0x4中,即
*0x00007ffeefbff3ec = 1
正好对应a = 1,所以可以猜测0x00007ffeefbff3ec就是a的地址

12、 movl $0x2, -0x8(%rbp)
同上,可知将2放入内存地址0x00007ffeefbff3f0 - 0x8中,即
*0x00007ffeefbff3e8 = 2
正好对应 b = 2,可猜测0x00007ffeefbff3e8就是b的地址

image.png

13、 movl -0x4(%rbp), %eax
14、 addl -0x8(%rbp), %eax
15、 movl %eax, -0x10(%rbp)
在文章最前面提过eax寄存器一般作为累加器,所以源码里面的int c = a + b + test2(a, b);这里就很好理解,前面不是分配了16个字节的内存么,我们只用到了高8个字节的内存分别存储a、b,这里将a、b的值通过累加器加起来,再用低8个字节的内存 -0x10(%rbp)存储a+b的和,即
*0x00007ffeefbff3e0 = 3

image.png

16、 movl -0x4(%rbp), %edi
17、 movl -0x8(%rbp), %esi
前面提到过寄存器edi、esi一般用来做函数参数存储。所以这里就是将a的值用edi存储,b的值用esi存储,即
edi = 1
esi = 2
此时rsp = 0x00007ffeefbff3e0
rbp = 0x00007ffeefbff3f0

image.png
image.png

18、 callq 0x100002f50
同第7步,call的下一条汇编指令地址压栈,所以
rsp = 0x00007ffeefbff3e0 - 0x8 = 0x00007ffeefbff3d8
*0x00007ffeefbff3d8 = 0x100002faa

从这里开始进入test2函数

19、 pushq %rbp
rbp压栈,则
rsp = 0x00007ffeefbff3d8 - 0x8 = 0x00007ffeefbff3d0
*0x00007ffeefbff3d0 = 0x00007ffeefbff3f0

20、 movq %rsp, %rbp
将栈顶rsp的值赋值给栈底rbp,即
rsp = rbp = 0x00007ffeefbff3d0

image.png

21、 movl %edi, -0x4(%rbp)
22、 movl %esi, -0x8(%rbp)
把前面用edi、esi存储的参数用test2的栈空间存储,即
*0x00007ffeefbff3cc = 1
*0x00007ffeefbff3c8 = 2

image.png

23、 movl -0x4(%rbp), %eax
24、 addl $0x1, %eax
25、 movl %eax, -0xc(%rbp)
将参数0x00007ffeefbff3cc里面存放的值(1)通过累加器eax,计算之后的值放入地址-0xc(%rbp),即0x00007ffeefbff3c4 = 2,正好对应源代码int v1 = a + 1;,所以这里v1的地址就是0x00007ffeefbff3c4

image.png

26、 movl -0x8(%rbp), %eax
27、 addl $0x2, %eax
28、 movl %eax, -0x10(%rbp)
将参数0x00007ffeefbff3c8里面存放的值(2)通过累加器eax,计算之后的值放入地址-0x10(%rbp),即0x00007ffeefbff3c0 = 2,正好对应源代码int v2 = b + 2;;,所以这里v2的地址就是0x00007ffeefbff3c0

image.png

29、 movl -0xc(%rbp), %eax
30、 addl -0x10(%rbp), %eax
31、 addl $0x3, %eax
32、 movl %eax, -0x14(%rbp)

这里就是通过累加器eax将-0xc(%rbp)和-0x10(%rbp)里面的值相加,再加上3,放入地址-0x14(%rbp)中,正好对应int c = v1 + v2 + 3;,所以这里c的地址就是-0x14(%rbp)即0x00007ffeefbff3bc,且*0x00007ffeefbff3bc = 9

image.png

33、 movl -0x14(%rbp), %eax
34、 addl $0x4, %eax
这里将前面计算的c的值加4,将结果放入寄存器eax,前面提到eax也做函数返回,所以此时eax = 13

image.png

35、 popq %rbp
这句指令表示出栈,同时将出栈的值放入寄存器rbp,所以有
rsp = 0x00007ffeefbff3d0 + 0x8 = 0x00007ffeefbff3d8
rbp = *0x00007ffeefbff3d0 = 0x00007ffeefbff3f0

image.png
从这里开始退出test2函数

36、 retq
这句表示退出test2函数,同时出栈,并且断点跳到出栈值的地址,所以可以看到
rsp = 0x00007ffeefbff3d8 + 0x8 = 0x00007ffeefbff3e0,而之前
*0x00007ffeefbff3d8 = 0x100002faa,所以此时跳到0x100002faa
同时我们可以发现栈顶rsp = 0x00007ffeefbff3e0 栈底rbp = 0x00007ffeefbff3f0,与进入test2函数之前的值保持一致,说明函数在调用前后会保持栈平衡,即从哪里开始,最后又会回到哪里

从这里开始回到test1函数
image.png

37、 movl %eax, %ecx
前面提到test2的返回值存放在寄存器eax,这里先将返回值用寄存器ecx存储,即ecx = 13

image.png

38、 movl -0x10(%rbp), %eax
39、 addl %ecx, %eax
40、 movl %eax, -0xc(%rbp)
因为之前a+b的值存在地址-0x10(%rbp)中,这里这三句正好对应int c = a + b + test2(a, b);,其中-0xc(%rbp)就是c的地址,所以有
*0x7ffeefbff3e4 = 16

image.png

41、 addq $0x10, %rsp
这句正好对应前面的subq $0x10, %rsp,此时
rsp = 0x00007ffeefbff3e0 + 0x10 = 0x00007ffeefbff3f0

image.png

42、 popq %rbp
这句指令表示出栈,同时将出栈的值放入寄存器rbp,所以有
rsp = 0x00007ffeefbff3f0 + 0x8 = 0x00007ffeefbff3f8
rbp = *0x00007ffeefbff3f0 = 0x00007ffeefbff410

image.png
从这里开始退出test1函数

43、 retq
这句表示退出test1函数,同时出栈,并且断点跳到出栈值的地址,所以可以看到
rsp = 0x00007ffeefbff3f8 + 0x8 = 0x00007ffeefbff400,而之前
*0x00007ffeefbff3f8 = 0x100002fdb,所以此时跳到0x100002fdb
同时我们可以发现栈顶rsp = 0x00007ffeefbff400 栈底rbp = 0x00007ffeefbff410,与进入test1函数之前的值保持一致,再次验证了前面的观点。

44、 xorl %eax, %eax
因为函数test1无返回值,所以这里eax也没实际用到

image.png

45、 addq $0x10, %rsp
这句正好对应前面的subq $0x10, %rsp,此时
rsp = 0x00007ffeefbff400 + 0x10 = 0x00007ffeefbff410

image.png

46、 popq %rbp
这句指令表示出栈,同时将出栈的值放入寄存器rbp,所以有
rsp = 0x00007ffeefbff410 + 0x8 = 0x00007ffeefbff418
rbp = *0x00007ffeefbff410 = 0x00007ffeefbff428
此时也正好对应初始时栈顶和栈底的值。

总结:函数调用会保持栈平衡
补充:函数递归没有退出条件之所以会造成死循环,就是因为rsp会一直往下减,直至减到栈区范围外,这样就会造成栈溢出。

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

推荐阅读更多精彩内容