[应用方案] 【转】C语言在ARM中函数调用时,栈是如何变化的? 嵌入式...

[复制链接]
1406|30
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:33 | 显示全部楼层 |阅读模式
https://mp.weixin.qq.com/s/s2adcgeF5gcRi2USY2e-wA

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

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



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


 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:35 | 显示全部楼层
演示代码
假如现在你已经掌握了 arm 指令的用法,即便没有掌握也没关系,“书到用时回头翻”。这里以一段简单的 c 语言为例:

  1. #include <stdio.h>

  2. int m = 8;
  3. int fun(int a,int b)
  4. {
  5.     int c = 0;
  6.     c = a + b;
  7.     return c;
  8. }
  9. int main()
  10. {
  11.     int i = 4;
  12.     int j = 5;
  13.     m = fun(i, j);
  14.     return 0;
  15. }
编译一下,然后反汇编:
  1. $ arm-linux-gnueabi-gcc main.c -o main
  2. $ arm-linux-gnueabi-objdump -D -D main

  1. 00010400 <fun>:
  2.    10400:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
  3.    10404:       e28db000        add     fp, sp, #0
  4.    10408:       e24dd014        sub     sp, sp, #20
  5.    1040c:       e50b0010        str     r0, [fp, #-16]
  6.    10410:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec
  7.    10414:       e3a03000        mov     r3, #0
  8.    10418:       e50b3008        str     r3, [fp, #-8]
  9.    1041c:       e51b2010        ldr     r2, [fp, #-16]
  10.    10420:       e51b3014        ldr     r3, [fp, #-20]  ; 0xffffffec
  11.    10424:       e0823003        add     r3, r2, r3
  12.    10428:       e50b3008        str     r3, [fp, #-8]
  13.    1042c:       e51b3008        ldr     r3, [fp, #-8]
  14.    10430:       e1a00003        mov     r0, r3
  15.    10434:       e24bd000        sub     sp, fp, #0
  16.    10438:       e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
  17.    1043c:       e12fff1e        bx      lr

  18. 00010440 <main>:
  19.    10440:       e92d4800        push    {fp, lr}
  20.    10444:       e28db004        add     fp, sp, #4
  21.    10448:       e24dd008        sub     sp, sp, #8
  22.    1044c:       e3a03004        mov     r3, #4
  23.    10450:       e50b300c        str     r3, [fp, #-12]
  24.    10454:       e3a03005        mov     r3, #5
  25.    10458:       e50b3008        str     r3, [fp, #-8]
  26.    1045c:       e51b1008        ldr     r1, [fp, #-8]
  27.    10460:       e51b000c        ldr     r0, [fp, #-12]
  28.    10464:       ebffffe5        bl      10400 <fun>
  29.    10468:       e1a02000        mov     r2, r0
  30.    1046c:       e59f3010        ldr     r3, [pc, #16]   ; 10484 <main+0x44>
  31.    10470:       e5832000        str     r2, [r3]
  32.    10474:       e3a03000        mov     r3, #0
  33.    10478:       e1a00003        mov     r0, r3
  34.    1047c:       e24bd004        sub     sp, fp, #4
  35.    10480:       e8bd8800        pop     {fp, pc}
  36.    10484:       00021024        andeq   r1, r2, r4, lsr #32


 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:36 | 显示全部楼层
图解栈的变化过程
如何能让读者接受吸收的更快,我一直觉得按照学习效率来讲的话顺序应该是视频,图文,文字。反正我是比较喜欢视频类的教学。这里给大家画下栈变化的过程是什么样子的。这里的图是结合上面的代码来画的,希望有助于读者的理解。

1.程序在内存分布区域 339336274ec0b51e5d.png

 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:37 | 显示全部楼层
2.全局变量m赋值
377286274ec36484c3.png


 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:39 | 显示全部楼层
3.保存进入main之前的栈底, fp-sp之间是当前函数栈
87336274ecc086f58.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:41 | 显示全部楼层
4.函数main的栈已经准备好了
708316274ecfe21ee6.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:42 | 显示全部楼层
5.i入栈
436196274ed3ee1305.png

 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:43 | 显示全部楼层
6.j入栈
159616274ed8a6c85f.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:45 | 显示全部楼层
7.准备函数fun的调用, 形参反向入栈 先形参b入栈
859046274edc3b3ff5.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:46 | 显示全部楼层
8.形参a入栈 668636274ee2d7e000.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:47 | 显示全部楼层
9.留空一个地址作为fun返回值, 待后面返回时填入
721756274ee847d726.png

 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:48 | 显示全部楼层
10.fun返回地址入栈, 通常是main函数当前pc指针的下一个
254566274eec395059.png

 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:49 | 显示全部楼层
11.main函数的栈底地址入栈
611986274ef0b07813.png


 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:51 | 显示全部楼层
12.pc指针跳转fun代码
564526274ef47b7626.png
23696274ef44a43ce.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:52 | 显示全部楼层
13.c入栈
497116274efa841649.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:53 | 显示全部楼层
14.可以看到函数fun的数据 形参a,b 在上一层函数的栈中. 一部分在自己的栈上. 此步取值到加法器中进行加法运算,再赋值给c
557246274efe7095f9.png

 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:54 | 显示全部楼层
15.c赋给返回值,填入上面的留空位置
324776274f01561587.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:55 | 显示全部楼层
16.栈底恢复上一层
615246274f05903716.png
 楼主| 盗铃何须掩耳 发表于 2022-5-6 17:57 | 显示全部楼层
17.lr赋值给pc, 实现了跳转
889586274f0b981c81.png
您需要登录后才可以回帖 登录 | 注册

本版积分规则

50

主题

385

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部