如何根据栈信息确定函数调用过程

ARM架构下函数调用时的栈空间使用

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中。如果您保存了返回地址,则可以在调用之间将 r14 用于其它用途,程序返回时要恢复。
  6. r15(pc):程序计数器,它指向当前程序执行位置,不能用于任何其它用途。

栈的原理:栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。进函数需要压栈操作,保存需要的信息;出函数时需要出栈操作,恢复现场。

函数调用时栈使用的一般原理:进入A函数前,依次将A函数的地址PC、返回地址LR、A函数的栈底地址、调用A函数的栈基址、A函数的参数(从右向左的顺序入栈)、局部变量的值压入栈。如函数有嵌套关系,按照上述原理入栈。

image-20210827225943567]

在C语言中,上图中的参数argc、argv和p1 – p4的压栈顺序应该调换一下。

示例代码

int func(int a, int b, int c, int d)
{	
	return 1;
}
 
int main(int argc, char **argv)
{
	int i = 1, j = 2;
	func(i, j, 3, 4);
	return 0;
}
  • 反汇编文件arm-linux-gcc main.c -o main; arm-linux-objdump -D main > main.dis;
.text:000083D0                 EXPORT main
.text:000083D0 main               ; DATA XREF: .text:000082C4o
.text:000083D0                    ; .text:off_82DCo
.text:000083D0
.text:000083D0                 IP = R12
.text:000083D0                 FP = R11
.text:000083D0                 MOV     IP, SP //保存SP
.text:000083D4                 STMFD   SP!, {FP,IP,LR,PC} //压栈
.text:000083D8                 SUB     FP, IP, #4 //取得FP基址,便可访问栈内所有的地址数据
.text:000083DC                 SUB     SP, SP, #8 //局部变量用
.text:000083E0                 MOV     R3, #1
.text:000083E4                 STR     R3, [FP,#a]
.text:000083E8                 MOV     R3, #2
.text:000083EC                 STR     R3, [FP,#b]
.text:000083F0                 LDR     R0, [FP,#a]
.text:000083F4                 LDR     R1, [FP,#b]
.text:000083F8                 MOV     R2, #3
.text:000083FC                 MOV     R3, #4
.text:00008400                 BL      func
.text:00008404                 MOV     R3, #0
.text:00008408                 MOV     R0, R3
.text:0000840C                 SUB     SP, FP, #0xC
.text:00008410                 LDMFD   SP, {FP,SP,PC} //出栈恢复现场
.text:00008410 ; End of function main

每个linux函数的汇编源码开头基本都是如下结构

  • 0xc03240d4 <+0>: mov r12, sp

    • 任何一个函数被调用的那一刻,第一步都是把指向父函数(上一层函数)栈底的sp存到r12(ip)中。(注意:SP只有一个,没有父子之说,执行完子函数,SP还会指回父函数的栈的)
  • 0xc03240d8 <+4>: push {r4, r5, r6, r7, r8, r9, r11, r12, lr, pc}

    • 编译器知道到底需要用多少个寄存器,一并入栈准备,push入栈是从pc->lr->r12->r11->…->r5-r4
  • 0xc03240dc <+8>: sub r11, r12, #4

    • r11- 4,是往低地址(memory low address)走4个字节,r11是用于访问函数中局部变量的指针(非栈指针sp)
  • 0xc03240e0 <+12>: sub sp, sp, #24

    • 此部分全部给局部变量用,一般需要多少就偏移多少个字节。

下面就以一张真实的程序出错时,打印的oops栈信息为例,进行分析

  • 栈信息:
    image-20210827204500040

  • 能得出的结论:0x9e80代表函数出错时的sp寄存器地址;0x9fe0代表最初的栈顶sp寄存器地址。

  • 假设从oops信息中得知,出错时的PC值为0xbf000078,且通过对比反汇编文件,已经定位到出错的函数为first_drv_open:

  • image-20210827205958993

  • 上图所示,在first_drv_open()函数里,通过stmdb sp!, {r4, r5, fp, ip, lr, pc}向栈里存入了6个值。所以, 返回到上个函数的值lr =c008d888

  • 搜索当前文件的反汇编文件,发现地址c008d888不在其中。所以判断应该在内核中。

  • 通过查看程序的地址映射mmap,发现c008d888为内核地址。所以查看下内核的反汇编文件:# arm-linux-objdump -D vmlinux > vmlinux.dis,其中-D表示反汇编所有段,vmlinux表示未压缩的内核。

  • 打开vmlinux.dis,搜索c008d888,发现其位于**chrdev_open()**函数中:

    继续分析:

    image-20210827234057517

  • 上图所示, chrdev_open()函数存了10个值,所以,返回到上个函数的值lr= c0089e48

  • 继续搜索,找到c0089e48位于函数**__dentry_open** ()下。

继续分析

image-20210827234522080

  • 上图所示, __dentry_open()函数存了10个值,所以,第二个值lr= c0089f64
  • 继续搜索,找到c0089f64位于函数nameidata_to_filp()下。

继续分析

image-20210827234653533

  • 上图所示, nameidata_to_filp函数存了6个值,所以,第二个值lr= c0089fb8
  • 继续搜索c0089fb8,。。。。。

最终调用关系为:

  • ret_fast_syscall()->
  • sys_open()->
  • do_sys_open()->
  • do_filp_open()->
  • nameidata_to_filp()->
  • chrdev_open()->
  • first_drv_open();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值