C语言在ARM中函数调用时,栈是如何变化的?

2015-09-15


为什么会写篇栈变化的文章?做系统分析的话你肯定遇到过一些crash, oops等棘手问题,一般大家都会用 gdb, objdump 或者 addr2line等工具分析 pc 位置来定位出错的地方。但是这些分析工具背后的本质原理就不见得理解深刻了,而且有的时候面对一系列 backtrace 或者 stack 日志处于懵逼的状态。


今天和大家一起看下面对 crash 日志的时候,如何利用 stack 来分析其变化的来龙去脉。


Arm指令集介绍

崇尚简单粗暴的介绍方式,我们直接来看各个寄存器的大体用法,详细用法可百度,不,谷歌。
1.    r0-r3 用作传入函数参数,传出函数返回值。在子程序调用之间,可以将 r0-r3 用于任何用途。被调用函数在返回之前不必恢复 r0-r3。---如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。
2.    r4-r11 被用来存放函数的局部变量。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。r11 是栈帧指针 fp
3.    r12 是内部调用暂时寄存器 ip它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。
4.    寄存器 r13 是栈指针 sp。它不能用于任何其它用途。sp 中存放的值在退出被调用函数时必须与进入时的值相同。
5.    寄存器 r14 是链接寄存器 lr如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复
6.    寄存器 r15 是程序计数器 pc它不能用于任何其它用途。

演示代码

假如现在你已经掌握了 arm 指令的用法,即便没有掌握也没关系,“书到用时回头翻”。这里以一段简单的 c 语言为例:
#include <stdio.h>
int m = 8;int fun(int a,int b){ int c = 0; c = a + b; return c;}int main(){ int i = 4; int j = 5; m = fun(i, j); return 0;}

编译一下,然后反汇编:

$ arm-linux-gnueabi-gcc main.c -o main 
$ arm-linux-gnueabi-objdump -D -D main
00010400 <fun>:   10400:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)   10404:       e28db000        add     fp, sp, #0   10408:       e24dd014        sub     sp, sp, #20   1040c:       e50b0010        str     r0, [fp, #-16]   10410:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec   10414:       e3a03000        mov     r3, #0   10418:       e50b3008        str     r3, [fp, #-8]   1041c:       e51b2010        ldr     r2, [fp, #-16]   10420:       e51b3014        ldr     r3, [fp, #-20]  ; 0xffffffec   10424:       e0823003        add     r3, r2, r3   10428:       e50b3008        str     r3, [fp, #-8]   1042c:       e51b3008        ldr     r3, [fp, #-8]   10430:       e1a00003        mov     r0, r3   10434:       e24bd000        sub     sp, fp, #0   10438:       e49db004        pop     {fp}            ; (ldr fp, [sp], #4)   1043c:       e12fff1e        bx      lr
00010440 <main>: 10440: e92d4800 push {fp, lr} 10444: e28db004 add fp, sp, #4 10448: e24dd008 sub sp, sp, #8 1044c: e3a03004 mov r3, #4 10450: e50b300c str r3, [fp, #-12] 10454: e3a03005 mov r3, #5 10458: e50b3008 str r3, [fp, #-8] 1045c: e51b1008 ldr r1, [fp, #-8] 10460: e51b000c ldr r0, [fp, #-12] 10464: ebffffe5 bl 10400 <fun> 10468: e1a02000 mov r2, r0 1046c: e59f3010 ldr r3, [pc, #16] ; 10484 <main+0x44> 10470: e5832000 str r2, [r3] 10474: e3a03000 mov r3, #0 10478: e1a00003 mov r0, r3 1047c: e24bd004 sub sp, fp, #4 10480: e8bd8800 pop {fp, pc} 10484: 00021024 andeq r1, r2, r4, lsr #32


图解栈的变化过程

如何能让读者接受吸收的更快,我一直觉得按照学习效率来讲的话顺序应该是视频,图文,文字。反正我是比较喜欢视频类的教学。这里给大家画下栈变化的过程是什么样子的。这里的图是结合上面的代码来画的,希望有助于读者的理解。

1.程序在内存分布区域




2.全局变量m赋值



3.保存进入main之前的栈底, fp-sp之间是当前函数栈




4.函数main的栈已经准备好了



5.i入栈



6.j入栈




7.准备函数fun的调用, 形参反向入栈 先形参b入栈



8.形参a入栈



9.留空一个地址作为fun返回值, 待后面返回时填入



10.fun返回地址入栈, 通常是main函数当前pc指针的下一个



11.main函数的栈底地址入栈



12.pc指针跳转fun代码



13.c入栈



14.可以看到函数fun的数据 形参a,b 在上一层函数的栈中. 一部分在自己的栈上. 此步取值到加法器中进行加法运算,再赋值给c



15.c赋给返回值,填入上面的留空位置



16.栈底恢复上一层



17.lr赋值给pc, 实现了跳转



18.返回值赋值给全局变量m



19.前面函数调用的形参已经无用,回滚sp



20.函数返回,清理main的栈空间



总结

这么多图有没有看花?相信到这里你已经了解了栈背后的来龙去脉,下一篇我们一起根据实际的 stack 错误案例剖析错误的可能性。



添加极客助手微信,加入技术交流群

长按,扫码,关注公众号

相关文章

C 语言程序设计---函数

2015-09-17
上次写了 C 语言入门级别的指针,这篇写函数,指针和函数在一起使用,更有意思,这也才能最大程度的发挥指针的作用.基本概念C...

C语言,函数不可返回指向栈内存的指针

2015-09-17
【C语言笔记】内存总结例子:return返回指向栈内存指针先看一个return返回指向栈内存指针的例子:#include char *GetStr(void){ char ...

C语言函数

2015-09-17
语言中的函数定义的一般形式如下:return_type function_name( parameter list ){ body of the function} 在 C 语言中,函数由一个函数头...

C语言常用的几个工具函数

2015-09-17
(0x11)-END-文章来源:芯片之家【1】都说C语言的精髓是指针,但是指针太难懂了,怎么办?【2】NTC热敏电阻测温原理,电路设...

手册:常用的C语言参考函数

2015-09-17
Linux C函数参考手册,便于查阅....点击阅读原文可直接下载完整资料,如果您的手机下载出错,请使用电脑访问网站下载,下载链接...

为何C语言的函数调用需要堆栈,而汇编却不需?

2015-09-16
即:为何C语言的函数调用要用到堆栈,而汇编却不需要初始化堆栈?要明白这个问题,首先要了解堆栈的作用.关于堆栈的作用,要...

C语言-函数定义

2015-09-16
C语言无参函数的定义1.函数需要接收用户传递的数据,那么定义时就要带上参数.#includeint sum(int c);//声明函数 int main(){ int i; ...

C语言-排序算法(四)+C语言-利用函数自写计算器

2015-09-16
quick_sort_recursive(arr, 0, len - 1);}C语言-利用函数自写计算器程序(一)①开始&获取选择数函数:int start_getnum() 开始计算器...

C语言、嵌入式重点知识:回调函数

2015-09-16
在C语言中,指针很重要,函数指针更重要.正如前辈们常说类似这样子的话:不会C指针,就没学会C语言;不会函数指针,就不要...

C语言 main 函数到底为啥这么写?

2015-09-16
main() 直接的mian()并没有返回值,没有入参.这种写法实际来说,部分编译器会显示警告,并且会返回默认值为int...void main() 初学者经常会使用的形式,但是并不知道来源在哪,在C89/C99/C11等文档中都没有提到这种形式的痕迹...int main(void) 比较常见的写法,这种写法的形参为void,表明它在调用的时候不能传入任何参数...

随机推荐

【重大威胁情报预警】phpStudy后门植入预警

2020-06-11
phpStudy软件是国内的一款免费的PHP调试环境的程序集成包,通过集成Apache、PHP、MySQL、phpMyAdmin、ZendOptimizer多款...

phpstorm第21节:拖拽打开文件

2020-06-05

JavaScript for PHP Developer

2020-03-22
PHP有is_nan().•isFinite()如果参数不是Infinity值的话,会告... "543"在PHP中,与implode()相反的函数是explode();在...

亚马逊汇总!入乐高农历新年积木、小米行车记录仪、无烟电烧烤炉、眼部按摩器!

2020-03-08
近史低价!Xiaomi 小米 Yi 小蚁 Nightscape 智能Wi-Fi超广角行车记录仪4.9折 50.56加元包邮!AI智能防碰撞!Amazon网店销售,原价...

在 Laravel 中处理请求验证的智能方法

2020-02-14
php中文网最新课程每日17点准时技术干货分享Laravel 是网络工匠的 PHP 框架.这有助于我们构建强大的应用程序和 API.很多人都...

龙岗长龙地铁站|一室一厅1600元

2015-08-17
长龙地铁站|一室一厅1600元由于本人工作地点变动,需转租房子.房子位于长龙新村的公寓,面积20平,一房一厅,楼层9楼,带电梯...

【大未来报考讲堂专业篇】——网络工程

2015-08-08
网络工程网络工程专业是工学学科门类计算机大类的学科,学生毕业以后拿的是工学学士学位.该专业,专业培养掌握数学和其他相关...

Java是如何实现自己的SPI机制的? JDK源码(一)

2015-08-01
注:该源码分析对应JDK版本为1.81 引言这是JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码.2 什么是SPI机...

【视频】| 编译原理

2015-05-27
一次性进群,长期免费索取教程,没有付费教程.教程列表见微信公众号底部菜单进微信群回复公众号:微信群;QQ群:16004488微...

堆栈技巧全攻略

2015-02-12
今天的教程,给大家带来堆栈技巧全攻略!这是ps中的小众冷门功能却是风光摄影中非常重要的必备技!教程详解堆栈技巧的三大功能...