代码 or 指令,浅析ARM架构下的函数的调用过程
摘要:linux程序运行的状态以及如何推导调用栈。
1、背景知识
1、ARM64寄存器介绍:
2、STP指令详解(ARMV8手册):
我们先看一下指令格式(64bit),以及指令对于寄存机执行结果的影响
类型1、STP <Xt1>, <Xt2>, [<Xn|SP>], #<imm>
将Xt1和Xt2存入Xn|SP对应的地址内存中,然后,将Xn|SP的地址变更为Xn|SP + imm偏移量的新地址
类型2、STP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>]!
将Xt1和Xt2存入Xn|SP的地址自加imm对应的地址内存中,然后,将Xn|SP的地址变更为Xn|SP + imm的offset偏移量后的新地址
类型3、STP <Xt1>, <Xt2>, [<Xn|SP>{, #<imm>}]
将Xt1和Xt2存入Xn|SP的地址自加imm对应的地址内存中
手册中有三种操作码,我们只讨论程序中涉及的后两种
Pseudocode如下:
Shared decode for all encodings
integer n = UInt(Rn);
integer t = UInt(Rt);
integer t2 = UInt(Rt2);
if L:opc<0> == '01' || opc == '11' then UNDEFINED;
integer scale = 2 + UInt(opc<1>);
integer datasize = 8 << scale;
bits(64) offset = LSL(SignExtend(imm7, 64), scale);
boolean tag_checked = wback || n != 31;
Operation for all encodings
bits(64) address;
bits(datasize) data1;
bits(datasize) data2;
constant integer dbytes = datasize DIV 8;
boolean rt_unknown = FALSE;
if HaveMTEExt() thenSetNotTagCheckedInstruction(!tag_checked);
if wback && (t == n || t2 == n) && n != 31 thenConstraint c = ConstrainUnpredictable();assert c IN {Constraint_NONE, Constraint_UNKNOWN, Constraint_UNDEF, Constraint_NOP};case c ofwhen Constraint_NONE rt_unknown = FALSE; // value stored is pre-writebackwhen Constraint_UNKNOWN rt_unknown = TRUE; // value stored is UNKNOWNwhen Constraint_UNDEF UNDEFINED;when Constraint_NOP EndOfInstruction();
if n == 31 thenCheckSPAlignment();address = SP[];
elseaddress = X[n];
if !postindex thenaddress = address + offset;
if rt_unknown && t == n thendata1 = bits(datasize) UNKNOWN;
elsedata1 = X[t];
if rt_unknown && t2 == n thendata2 = bits(datasize) UNKNOWN;
elsedata2 = X[t2];
Mem[address, dbytes, AccType_NORMAL] = data1;
Mem[address+dbytes, dbytes, AccType_NORMAL] = data2;
if wback thenif postindex thenaddress = address + offset;if n == 31 thenSP[] = address;elseX[n] = address;
红色部分对应推栈的关键逻辑,其他汇编指令含义可自行参考armv8手册或者度娘。
2、一个例子
熟悉了上面的部分,接下来我们看一个实例:
C代码如下:
相关的几个函数反汇编如下(和推栈相关的一般只有入口两条指令):
main\f3\f4\strlen
我们通过gdb运行后,可以看到strlen地方会触发SEGFAULT,引发进程挂掉
上述通过代码编译后,没有strip,因此elf文件是带着符号的
查看运行状态(info register):关注$29、$30、SP、PC四个寄存器
一个核心的思想:CPU执行的是指令而不是C代码,函数调用和返回实际是在线程栈上面的压栈和弹栈的过程
接下来我们来看上面的调用关系在当前这个任务栈是如何玩的:
函数调用在栈中的关系(call function压栈,地址递减;return弹栈,地址递增):
以下是推栈的过程(划重点)
再回头来看之前的汇编:
main\f3\f4\strlen
从当前的sp开始,frame 0是strlen,这块没有开栈,因此上一级的调用函数仍然是x30,因此推导:frame1调用为f3
函数f3的起始入口汇编:
(gdb) x/2i f30x400600 <f3>: stp x29, x30, [sp,#-48]!0x400604 <f3+4>: mov x29, sp
可以看到,f3函数开辟的栈空间为48字节,因此,倒推frame2的栈顶为当前的sp + 48字节:0xfffffffff2c0
(gdb) x/gx 0xfffffffff2c0+8
0xfffffffff2c8: 0x000000000040065c
(gdb) x/i 0x000000000040065c0x40065c <f4+36>: mov w0, #0x0 // #0
frame2的函数为sp+8:0x000000000040065c -> <f4+36>
继续从sp = 0xfffffffff2c0倒推frame1的函数
函数f4的起始入口汇编为:
(gdb) x/2i f40x400638 <f4>: stp x29, x30, [sp,#-48]!0x40063c <f4+4>: mov x29, sp
可以看到,f4函数开辟的栈空间也是为48字节,因此,倒推frame3的栈顶为当前的0xfffffffff2c0 + 48字节:0xfffffffff2f0
frame2的函数为0xfffffffff2c0 + 8:0x000000000040065c -> <f4+36>
(gdb) x/gx 0xfffffffff2f0+8
0xfffffffff2f8: 0x0000000000400684
(gdb) x/i 0x00000000004006840x400684 <main+28>: mov w0, #0x0 // #0
因此frame3的函数为main函数,main函数对应的栈顶为0xfffffffff320
至此推导结束(有兴趣的同学可以继续推导,可以看到libc如何拉起main的过程)
总结:
推栈的关键:
- 当前的现场
- 熟悉cpu体系架构的开栈的方式
3、实战讲解
现场有如下的core:可以看到,所有的符号找不到,加载了符号表依然不好使,解析不出来实际的调用栈
(gdb) bt
#0 0x0000ffffaeb067bc in ?? () from /lib64/libc.so.6
#1 0x0000aaaad15cf000 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
先看info register,关注x29、x30、sp、pc四个寄存器的值
推导任务栈:
先将sp内容导出:
下图实际已先将结果标出,我们下面来详细描述如何推导
pc代表当前执行的函数指令,如果当前指令未开栈,一般情况x30代表上一级的frame调用当前函数的下一条指令,查看汇编,可以反解为如下函数
(gdb) x/i 0xaaaacd3de4fc0xaaaacd3de4fc <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+108>: mov x27, x0
找到栈顶函数后,查看该函数的栈操作:
(gdb) x/6i PGXCNodeConnStr0xaaaacd3de490 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)>: sub sp, sp, #0xd00xaaaacd3de494 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+4>: stp x29, x30, [sp,#80]0xaaaacd3de498 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+8>: add x29, sp, #0x50
可以看到,上一级的frame存在了当前的sp + 0xd0 - 0x80也就是0xfffec4cebd40 + 0xd0 - 0x80 = 0xfffec4cebd90的地方,而栈底在0xfffec4cebd40+ 0xd0 = 0xfffec4cebe10的地方
因此就找到了下一级的frame对应的栈顶和上一级的LR返回指令,反解,可以得到函数build_node_conn_str
(gdb) x/i 0x0000aaaacd414e080xaaaacd414e08 <build_node_conn_str(Oid, DatabasePool*)+224>: mov x21, x0
继续重复上述推导,可以看到这个函数build_node_conn_str开了176字节的栈,
(gdb) x/4i build_node_conn_str0xaaaacd414d28 <build_node_conn_str(Oid, DatabasePool*)>: stp x29, x30, [sp,#-176]!0xaaaacd414d2c <build_node_conn_str(Oid, DatabasePool*)+4>: mov x29, sp
因此继续用0xfffec4cebe10 + 176 = 0xfffec4cebec0
查看调用者0xfffec4cebe10+8为reload_database_pools
继续看reload_database_pools
(gdb) x/8i reload_database_pools0xaaaacd4225e8 <reload_database_pools(PoolAgent*)>: sub sp, sp, #0x1c00xaaaacd4225ec <reload_database_pools(PoolAgent*)+4>: adrp x5, 0xaaaad15cf0000xaaaacd4225f0 <reload_database_pools(PoolAgent*)+8>: adrp x3, 0xaaaacf0ed0000xaaaacd4225f4 <reload_database_pools(PoolAgent*)+12>: adrp x4, 0xaaaaceeed000 <_ZN4llvm18ConvertUTF8toUTF16EPPKhS1_PPtS3_NS_15ConversionFlagsE>0xaaaacd4225f8 <reload_database_pools(PoolAgent*)+16>: add x3, x3, #0x9e00xaaaacd4225fc <reload_database_pools(PoolAgent*)+20>: adrp x1, 0xaaaacf0ee000 <_ZZ25PoolManagerGetConnectionsP4ListS0_E8__func__+24>0xaaaacd422600 <reload_database_pools(PoolAgent*)+24>: stp x29, x30, [sp,#-96]!
实际开栈0x220字节,因此这一层frame的栈底为0xfffec4cebec0 + 0x220 = 0xfffec4cec0e0
因此得到基本的调用关系的结构如下
以上基本可以够用来分析问题了,因此不需要再继续推导
TIPS:arm架构下一般调用都会使用这种指令,
stp x29, x30, [sp,#immediate]! 有叹号或者无叹号
因此在每一层的frame都保存了上一层frame的栈顶地址和LR指令,通过准确找到底层的frame 0栈顶后,就可以快速推导出所有的调用关系(红色虚线圈出来的部分),函数的反解依赖符号表,只要原始的elf文件的symbol段没有strip掉,是都可以找到对应的函数符号(通过readelf -S查看即可)
找到Frame后,每一层frame里面的内容,结合汇编基本就可以用来推导过程变量了。
本文分享自华为云社区《代码 or 指令,浅析ARM架构下的函数的调用过程》,原文作者:K______。
点击关注,第一时间了解华为云新鲜技术~
代码 or 指令,浅析ARM架构下的函数的调用过程相关推荐
- 【Android 逆向】函数拦截 ( ARM 架构下的插桩拦截 | 完整代码示例 )
文章目录 一.ARM 架构下的插桩拦截 二.完整代码示例 一.ARM 架构下的插桩拦截 ARM 架构下的跳转指令 : 下面的二进制数都是十六进制数 ; 323232 位指令 ; 04 F0 1F E5 ...
- Arm 架构下的中断
中断的处理分为三个部分: 1.中断检测: arm架构下,中断监测部分的代码是需要用户自己开发的,可以参考ambaIntrCtl.c,需要实现函数xxxIntLvlVecChk.xxxIntLvlVec ...
- 史上最全!!!ARM架构下的NVIDIA Xavier安装ROS-Melodic以及使用速腾激光雷达+A-loam获取点云图
** 一.ARM架构下的NVIDIA Xavier 切换国内的源 ** 这里不要换成AMD架构的PC平台的软件源,需要换成配套ARM使用的源. 首先备份下之前的 source.list sudo cp ...
- ARM架构下部署docker
ARM架构下部署docker 1 环境要求 2 安装与配置docker 2.1? 下载Docker静态包 2.2 移值docker静态包以及组件 2.3 配置docker.service文件 2.4 ...
- 案例一: 使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程。 首先用文本编辑器写一个C++源程序名为StackFrame.cpp ,代码如下:
案例一: 使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程. 首先用文本编辑器写一个C++源程序名为StackFrame.cpp ,代码如下: 1 ...
- 第6.3章:ARM架构下手动编译StarRocks(拓展篇)
2023/4/17更新: 下文教程中CentOS 7下使用的centos-release-scl源里的devtoolset-10的gcc/g++将_GLIBCXX_USE_CXX11_ABI强制关闭了 ...
- 基于ARM架构下的PI数据库远程存储
随着信息技术的进步,计算机技术在工厂的控制层和管理层都得到了充分和广泛的应用.而当计算机技术的应用要求进一部深入和提升时,势必要求把生产控制层和管理层连接起来.实时数据库系统能够及时地把控制层 ...
- ARM架构下在qemu模拟器中汇编实现:统计输入字符串长度并且判断是否回文
参考文章: 在Ubuntu中安装Qemu模拟ARM架构 objdump(Linux)反汇编命令使用指南 如何在x64的Ubuntu系统下安装64bit的交叉编译工具aarch64-linux-gnu- ...
- ARM架构下使用NEON向量化指令集入门基础
一.NEON简介 ARM NEON技术是基于SIMD的理念而设计出的,它是一种64位和128位混合的SIMD技术,主要应用场景是音视频处理,图像视觉计算,信号处理应用等需要密集计算的场 ...
最新文章
- 窗体的ControlBox属性
- layui动态生成的下拉框被遮住
- 01.WPF中制作无边框窗体
- linux文件系统添加pcm,linux下用sox批量将pcm文件加wav头、批量修改采样率、切音频...
- SocialFish-kali下社会工程学钓鱼工具
- 小夕说,不了解动态空间增长的程序喵都是假喵(下)
- Python调用Java代码部署及初步使用
- informix多张表UPDATE时不能用别名
- python编写程序、计算1+3+5+7......+99-在python中实现求输出1-3+5-7+9-......101的和
- pycharm使用总结
- PAT 1045 快速排序(25)(STL-set+思路+测试点分析)
- 牛批!妹子一口气拿下BAT、美团、vivo、爱奇艺等公司Offer面经总结
- jQuery 的第一个例子
- windowskb2685811补丁_Win7/8.1 KB2685811、KB2685813和KB2670838蓝屏补丁下载汇总 (32位+64位)...
- 迅雷,暴风影音,QQ这些软件是什么工具和语言编的?
- 谷歌搜索没有相机图标_教您如何在Google上搜索图片
- 怎么查二手苹果手机价格
- 迅雷极速版阻止自动更新(亲自摸索出来,可用)
- BAT批处理如何去写Windows防火墙规则
- 关于数据库的递归查询
热门文章
- Bootstrap 插件的选项
- 获取网关_阿里二面问了这道题:如何设计一个微服务网关系统
- 大话西游2服务器维护,大话西游2:9.10维护解读:灵犀调整全服上线,去疾调整是好是坏?...
- oracle 其他用户表主键,Oracle中查看所有的表,用户表,列名,主键,外键
- Flask框架 - 初识
- docker build no such file or directory
- HDU 3564 Another LIS
- 单片机特殊功能寄存器
- 实现算法2.11、2.12的程序
- 浅析MSIL中间语言——基础篇