返回顶部
首页 > 资讯 > 服务器 >浅析ARM架构下的函数的调用过程
  • 325
分享到

浅析ARM架构下的函数的调用过程

linuxarmarm函数 2022-06-03 14:06:09 325人浏览 安东尼
摘要

目录1、背景知识1、ARM64寄存器介绍2、STP指令详解(ARMV8手册)2、一个例子3、实战讲解1、背景知识 1、ARM64寄存器介绍 2、STP指令详解(ARMV8手册) 我们先看一下指令格式(64bit)

目录
  • 1、背景知识
    • 1、ARM64寄存器介绍
    • 2、STP指令详解(ARMV8手册)
  • 2、一个例子
    • 3、实战讲解

      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() then
               SetNotTaGCheckedInstruction(!tag_checked);
      if wback && (t == n || t2 == n) && n != 31 then
          Constraint c = ConstrainUnpredictable();
          assert c IN {Constraint_NONE, Constraint_UNKNOWN, Constraint_UNDEF, Constraint_NOP};
          case c of
              when Constraint_NONE rt_unknown = FALSE; // value stored is pre-writeback
              when Constraint_UNKNOWN rt_unknown = TRUE; // value stored is UNKNOWN
              when Constraint_UNDEF UNDEFINED;
              when Constraint_NOP EndOfInstruction();
      if n == 31 then
       CheckSPAlignment();
          address = SP[];
      else
          address = X[n];
      if !postindex then
          address = address + offset;
      if rt_unknown && t == n then
          data1 = bits(datasize) UNKNOWN;
      else
          data1 = X[t];
      if rt_unknown && t2 == n then
          data2 = bits(datasize) UNKNOWN;
      else
          data2 = X[t2];
      Mem[address, dbytes, AccType_NORMAL] = data1;
      Mem[address+dbytes, dbytes, AccType_NORMAL] = data2;
      if wback then
        if postindex then
              address = address + offset;
          if n == 31 then
              SP[] = address;
          else
              X[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 f3
         0x400600 <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 0x000000000040065c
         0x40065c <f4+36>:    mov w0, #0x0                       // #0
      frame2的函数为sp+8:0x000000000040065c -> <f4+36>

      继续从sp = 0xfffffffff2c0倒推frame1的函数

      函数f4的起始入口汇编为:

      
      (gdb) x/2i f4
         0x400638 <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 0x0000000000400684
         0x400684 <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 0xaaaacd3De4fc
         0xaaaacd3de4fc <PGXCnodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)+108>: mov x27, x0

      找到栈顶函数后,查看该函数的栈操作:

      
      (gdb) x/6i PGXCNodeConnStr
         0xaaaacd3de490 <PGXCNodeConnStr(char const*, int, char const*, char const*, char const*, char const*, int, char const*)>: sub  sp, sp, #0xd0
         0xaaaacd3de494 <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 0x0000aaaacd414e08
         0xaaaacd414e08 <build_node_conn_str(Oid, DatabasePool*)+224>:     mov x21, x0

      继续重复上述推导,可以看到这个函数build_node_conn_str开了176字节的栈,

      
      (gdb) x/4i build_node_conn_str
         0xaaaacd414d28 <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_pools
         0xaaaacd4225e8 <reload_database_pools(PoolAgent*)>:       sub   sp, sp, #0x1c0
         0xaaaacd4225ec <reload_database_pools(PoolAgent*)+4>:  adrp x5, 0xaaaad15cf000
         0xaaaacd4225f0 <reload_database_pools(PoolAgent*)+8>:   adrp x3, 0xaaaacf0ed000
         0xaaaacd4225f4 <reload_database_pools(PoolAgent*)+12>: adrp x4, 0xaaaaceeed000 <_ZN4llvm18ConvertUTF8toUTF16EPPKhS1_PPtS3_NS_15ConversionFlagsE>
         0xaaaacd4225f8 <reload_database_pools(PoolAgent*)+16>: add  x3, x3, #0x9e0
         0xaaaacd4225fc <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里面的内容,结合汇编基本就可以用来推导过程变量了。

      以上就是浅析ARM架构下的函数的调用过程的详细内容,更多关于ARM架构下的函数的调用过程的资料请关注编程网其它相关文章!

      --结束END--

      本文标题: 浅析ARM架构下的函数的调用过程

      本文链接: https://lsjlt.com/news/11632.html(转载时请注明来源链接)

      有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

      猜你喜欢
      • 浅析ARM架构下的函数的调用过程
        目录1、背景知识1、ARM64寄存器介绍2、STP指令详解(ARMV8手册)2、一个例子3、实战讲解1、背景知识 1、ARM64寄存器介绍 2、STP指令详解(ARMV8手册) 我们先看一下指令格式(64bit)...
        99+
        2022-06-03
        linux arm arm 函数
      • MacOS(M1芯片arm架构)下安装PyTorch的详细过程
        目录1.创建pytorch虚拟环境2.切换到新的环境3.安装pytorch4.测试5.在pytorch环境下安装jupyter notebook6.让jupyter在pytorch环...
        99+
        2023-02-21
        MacOS M1安装PyTorch MacOS M1 arm安装PyTorch MacOS安装PyTorch
      • MacOS(M1芯片 arm架构)下安装tensorflow的详细过程
        目录导语下面将开始讲解在Mac(M1)上如何安装tensorflow1. 下载Miniforge3-MacOSX-arm64.sh脚本文件,并且运行2. 创建虚拟环境3. 安装ten...
        99+
        2023-02-21
        MacOS安装tensorflow M1芯片安装tensorflow M1芯片 arm安装tensorflow
      • C++浅析析构函数的特征
        目录定义特征编译器生成的默认析构函数定义 析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资...
        99+
        2024-04-02
      • function.procedure函数下的过程分析
        function.procedure函数下的过程分析,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。 问题:PKG_...
        99+
        2024-04-02
      • linux(ARM)架构下的mysql安装使用(完整版)
        目录 一、安装MYSQL之前要先换源 二、安装MYSQL 1、安装 2、安装完成 3、安装后无法登陆 3.1 原因 3.2 登陆后切换database 3.3 修改密码(注意这里账号和密码是双引号) 3.4查看一下用户 3.5 然后进行刷新...
        99+
        2023-08-31
        ubuntu mysql linux
      • 浅析php中函数调用函数的不同方式
        在PHP中,函数调用可能是程序中最常用的操作之一。但是,当你需要一个函数调用另一个函数的时候,你需要了解一些细节。在本文中,我们将讨论PHP中函数调用函数的不同方式,以帮助你更好地理解这个过程。直接调用最基本的方式调用函数是直接在代码中调用...
        99+
        2023-05-14
        php 函数
      • C++浅析构造函数的特性
        目录构造函数的概念构造函数的特性只能有一个构造函数构造函数的概念 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值...
        99+
        2024-04-02
      • ARM64架构下安装mysql5.7.22的全过程
        MySQL下载地址为: https://obs.cn-north-4.myhuaweicloud.com/obs-mirror-ftp4/database/mysql-5.7.27-...
        99+
        2024-04-02
      • MySQL调用存储过程和函数的示例分析
        这篇文章主要介绍了MySQL调用存储过程和函数的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。存储过程和函数有多种调用方法。存储过程...
        99+
        2024-04-02
      • 浅析 Golang 函数的性能调优技巧
        在 go 中,函数性能调优技巧包括:减少内存分配:复用变量、使用缓冲池、使用固定大小数组。优化数据结构:使用切片代替数组、使用 map 代替 switch 语句、选择正确的容器。避免不必...
        99+
        2024-04-19
        性能调优 函数优化 golang 垃圾回收器 冒泡排序
      • 浅析Java8的函数式编程
        前言本系列博客,介绍的是JDK8 的函数式编程,那么第一个问题就出现了,为什么要出现JDK8?  JAVA不是已经很好,很强大了吗,很多公司用的还是1.6,1.7呀,1.8有必要吗?更不要提即将问世的JDK9了,鲁迅...
        99+
        2023-05-31
        java8 函数式 编程
      • Socket函数调用过程是怎样的
        本篇内容介绍了“Socket函数调用过程是怎样的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一:简介Socket可以作插口或者插槽,可以想...
        99+
        2023-06-04
      • 浅析Android系统的架构以及程序项目的目录结构
        Android框架结构 直接上图: 由上图,我们可以看出Android系统架构由5部分组成, 分别是:Linux Kernel(linux内核)、Android Runti...
        99+
        2022-06-06
        程序 Android 架构
      • 深入浅析Python数据分析的过程记录
        目录一、需求介绍二、以第1、个为例进行数据分析1、获取一天的数据2、开始一天的数据的分析3、循环日期进行多天的数据分析:4、将数据写入Excel表格中三、完整的代码展示:总结一、需求...
        99+
        2024-04-02
      • golang函数在面向对象编程中的微服务架构下的应用
        在面向对象编程的微服务架构中,go 函数通过函数式编程特性增强了 oop,包括一等公民和闭包,可用于创建可重用、模块化和高性能的微服务。例如,在订单处理微服务中,函数可以用于创建订单对象...
        99+
        2024-05-03
        golang 微服务 作用域
      • Oracle 中 table 函数的应用浅析
        表函数可接受查询语句或游标作为输入参数,并可输出多行数据。该函数可以平行执行,并可持续输出数据流,被称作管道式输出。应用表函数可将数据转换分阶段处理,并省去中间结果的存储和缓冲表。 1. 用游标传递数据 利...
        99+
        2024-04-02
      • C++浅析内联函数的使用
        目录一. 概念二. 特性一. 概念 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。 在C++里...
        99+
        2024-04-02
      • C语言浅析函数的用法
        目录问题引入函数C语言中函数的语法形式问题例子函数的调用过程函数声明变量声明数组声明问题引入 有时候,我们经常需要在一个程序中,对一个数组进行 键盘输入,打印数组元素值。 有些代码块...
        99+
        2024-04-02
      • 深入浅出 PHP 函数的调用顺序
        php 函数调用顺序:解析参数:参数赋值给函数形参。查找变量:确定函数中使用的所有变量。执行代码:逐行执行函数主体。返回值:将返回值传回调用方。 深入浅出 PHP 函数的调用顺序 PH...
        99+
        2024-04-16
        php 函数调用 作用域
      软考高级职称资格查询
      编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
      • 官方手机版

      • 微信公众号

      • 商务合作