跟涛哥一起学嵌入式 第08集:ARM跳转指令深度剖析

2023-05-16

 

跳转指令

顺序、选择、循环是构建程序的基本结构,任何一个逻辑复杂的程序基本上都可以由这三种程序结构组合而成。而跳转指令,则在子程序调用、选择、循环程序结构中被大量使用。程序的跳转是如何实现的呢?在了解这个机制之前,我们需要先了解一下程序计数器PC。

程序计数器PC,是CPU的寄存器列表中最重要的一个寄存器。它就像一杆枪,指哪打哪:你给PC指针赋值哪个地址,CPU就会到PC指针指向的这个地址去取指令、翻译指令、执行指令。一般情况下,当你没有给PC指针赋新地址时,CPU在PC指针指向的地址取完指令后,PC计数器会自动加一,指向下一条指令,程序可以自动执行下去。当我们需要跳转时,可以直接给PC指针赋一个新地址,于是CPU就会跳转到新地址去执行了。

在ARM中,常见的跳转指令有B、BL、MOV、LDR等。不同的指令,它们的使用条件、使用场合是不同的,今天就给大家总结一下它们的区别及各自使用的场合。

B跳转指令

B指令是ARM中最基本的跳转指令,它的使用方法如下:


   
B label

上面语句表示跳转到label的标号处去执行。B跳转指令是ARM中最简单的指令,只是单纯的跳转,而且是相对跳转。它可以跳到以当前位置PC为基址,前后32MB的地址空间范围,所以B指令只是在临近的代码块、标号之间跳转。

B指令跳转,大多数时候是单向的,跳过去就不再返回来了。但是我们可以通过添加一些标号来实现一些控制逻辑:比如循环、选择程序结构:

;循环结构示例
LOOP
    SUB R0,R0,#1
    ...
    CMP R0,#0
    BNE LOOP
;选择结构示例
    MOV R1,#10
    MOV R2,#20
    CMP R1,R2
    BEQ HERE
    ...
    B END 
HERE
    ...
END
    ...

在上面的程序中,我们使用B跳转指令实现了选择、循环这两种基本的程序结构。B指令像ARM的其它指令一样,可以根据CPSR状态寄存器的标志位,有条件的执行。这样,可以减少指令数目、提高代码密度和运行效率。如BNE、BEQ就是当结果相等、不相等时的条件跳转。

当前程序状态寄存器:

 

各种各样的条件码:

 

BL指令

BL指令跟B不同:在跳转之前,会先将当前指令的下一条指令地址保存到LR寄存器中,然后才跳转到标号执行。这样做的好处是:当我们想从标号地方返回时,可以直接将LR寄存器中的返回地址赋值给PC,程序就可以返回到原来的程序中继续执行了。

BL跳转指令一般用在子程序的调用中。无论是汇编语言子程序,还是C语言子程序,在跳转到子程序之前,都要将返回地址保存起来。当子程序执行完毕,将LR寄存器保存的返回地址,重新赋值给PC,处理器就可以返回到主程序继续执行了。

BEGIN
    MOV R0,#SRC
    MOV R1,#DST
    MOV R2,#100
    BL COPY
    NOP
    ...
 COPY
    SUB R2,R2,#1
    LDR R3,[R0],#1
    STR R3,[R1],#1
    CMP R2,#0
    BNE COPY
    MOV PC,LR

上面的汇编代码段,我们定义了一个汇编子程序COPY,实现了数据拷贝的功能。当我们使用BL指令调用这个子程序COPY时,CPU会首先将当前指令的下一条指令:NOP 的地址保存到LR寄存器中,然后才跳转到COPY子程序去执行。在COPY子程序中,处理完数据搬运后,通过


   
MOV PC,LR

这条语句,将保存在LR寄存器中的返回地址,重新赋值给PC,这样我们就可以返回到原来的程序中继续执行了。

在上面的汇编代码中,LR,即R14,连接寄存器,常用来存放程序的返回地址;PC,即R15,程序计数器,表示当前指令地址。LR和PC都是ARM汇编器为了方便程序员编程,预定义的一些宏。你在程序中使用这些助记符其实就是相当于操作R14和R15寄存器。除此之外,ARM中常用的助记符有:

  • FP:栈帧基址寄存器,即R12

  • SP:栈指针寄存器,即R13

  • LR:链接寄存器,即R14

  • PC:程序计数器,即R15

同样,在C语言调用子函数的过程中,在跳转子函数执行之前,CPU也会将当前指令的下一条指令地址保存到LR寄存器中,然后再跳转到子函数中执行。因为在子函数运行过程中,也有可能会用到ARM的一些寄存器,也有可能会调用其它的子函数,会覆盖掉保存在LR寄存器中的返回地址,所以,我们一般在运行子函数之前,会首先将LR寄存器压入子函数的栈帧,相当于将返回地址保存到了栈上。当子函数运行结束时,再通过出栈操作,将保存在栈中的返回地址弹出到PC指针中,这样程序就成功从子程序中返回了,直接返回到原来的函数中继续执行。

int main(void)
{
    func();
    printf("Hello!\n");
    return 0;
} 
;对应的汇编代码
main
    BL func
    BL printf
func
    PUSH LR
    ...
    pop pc 
;func子函数返回

MOV指令

通过上面的学习,我们可以看到,无论是B指令、还是BL指令,都是相对寻址。其本质都是以当前指令地址PC为基址,然后加上一个[0,32M]的偏移,达到修改PC的目的。

除此之外,我们也可以直接给PC指针赋值,达到跳转的目的。如上面的 func 子程序返回,就是直接通过


   
MOV PC,LR

这条指令,将LR寄存器中的返回地址,直接赋值给PC,直返回到原来的主函数去执行。

MOV指令主要用来在寄存器之间传输数据,或者将一个立即数传送到寄存器。但是MOV指令有一个硬伤,就是传递的立即数只能是8位数,有大小的限制。这是为什么呢?很简单,ARM是RISC架构,在一个32位的ARM中,指令通常都是32位的。而一个指令中,通常要包括操作码+操作数,如下图:

一条指令,总共有32个bit空间,MOV这个操作码要占几位吧,Rd寄存器编码要占据几位吧,剩下的留给立即数的空间就不多了,所以这也就限定了MOV指令能传递的立即数的大小了。而一般的32位程序中,无论是变量还是函数,它们的地址一般都是32位的,如果使用MOV指令,将他们的地址传送到PC,使用下面的形式:


   
MOV PC,#0x30008000

你会发现,立即数#0X30008000这个地址就已经32位了,在加上MOV指令这个操作码,已经超过32位了,编译器是无法翻译这个指令的,所以说,当一个变量或函数地址为32位时,使用MOV指令给PC直接赋值,行不通,那怎么办呢?

 

LDR伪指令

办法总是有的,比如,我们就可以通过伪指令LDR,直接将一个32位的立即数地址,传送到PC:


   
LDR PC,=0x30008000

LDR伪指令的功能和MOV一样,都可以将一个立即数传送到寄存器。唯一区别的就是,MOV指令只能传送8位的,而LDR可以传送一个32位的立即数或地址。

这里需要注意一下,立即数 0X30008000 的前面有一个等于号“=”,这表示前面的LDR指令是一个伪指令。除此之外,在ARM中,LDR还有另外一个意思,用来将内存中的数据加载到寄存器。我们知道,ARM是RISC架构,使用LDR/STR架构,不能直接修改内存中的数据。如果我们要修改内存中的一个变量,要首先使用LDR指令将内存中的变量加载到寄存器中,接着对寄存器进行操作,最后再使用STR指令将寄存器中的变量回写到内存中。所以,LDR可以看作是一个伪指令,也可以看做是普通的一个LDR指令,判定他们的区别就是看前面的等于号。

普通的LDR指令主要使用寄存器间接寻址,常用的使用方式如下:

LDR R0,[R1]
LDR R0,0x30008000

这里注意后面一句,是将地址0x30008000地址上的内容传动到寄存器R0,而不是直接将这个地址传送到R0,这里一定要注意其跟LDR伪指令的区别,这一点没有注意到,你在分析程序时就可能误入歧途了。

在《C语言嵌入式Linux高级编程》第二期中,我们已经探讨了CPU、指令集、伪指令的基本概念,这里就不赘述了。简单来说,伪指令并不是真正的ARM指令,并不属于ARM指令集中的标准指令。它只是编译器为了方便我们程序员开发程序,定义的一些助记符。在编译时,这些伪指令还是会使用指令集中的标准指令来实现。

比如上面的LDR伪指令,程序在编译时,看到这个伪指令,会使用ARM指令集中标准的指令实现。如果LDR伪指令中的立即数小于8位,它就会转换为MOV指令来实现:

LDR R0,=200
MOV R0,#200

如果LDR伪指令中,立即数大于8bit表示的数据范围,比如说是一个32位的立即数或地址,那就不能使用MOV指令来实现了,可以采用文字池的形式,先将这个地址常量单独存放在存储单元中,然后使用相对寻址,曲线救国,完成这个32位地址或立即数与寄存器之间的传输,这些细节在教程视频中都有讲到,就不再赘述了。

 

小结

通过上面的学习,我们基本上理清了ARM系统中常见的几种跳转指令,以及它们的区别。只有彻底理解他们的底层机制及实现细节,才有可能在使用反汇编分析程序时,达到事半功倍的效果,从而大大提高我们的工作效率。否则,这些基本的细节和概念搞不清,将会永远成为你学习和工作上的障碍。嵌入式学习群:475504428,微博/微信公众号:宅学部落(armlinuxfun)。王老师淘宝店:wanglitao.taobao.com,王老师CSDN学院课程信息:https://edu.csdn.net/lecturer/775

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

跟涛哥一起学嵌入式 第08集:ARM跳转指令深度剖析 的相关文章

  • 是否有专门设计的 C 函数或宏,用于以跨平台方式与汇编指令进行一对一编译以进行位操作?

    我有一个涉及仿真的项目 如果您查看我的帖子历史记录 您会看到我已经走了多远 并且我希望使用 C 进行伪二进制翻译并使用优化器和 或编译器使用 C 代码将我的 switch 语句内容编译为单个汇编指令 主要用于非常标准的指令 例如movs a
  • 使用 QEMU 模拟 Big Endian ARM 系统

    是否可以编译一些 Linux 内核并通过 QEMU 运行它 模拟一些 Big Endian ARM 处理器 如果 QEMU 无法做到这一点 我很想知道其他可以做到这一点的系统模拟器 我的基本目标是在尽可能多的本机环境中运行和调试专用的 Bi
  • Pandaboard 交叉编译 Qt

    我花了几周的时间尝试为我的 Panda 板交叉编译 Qt 但没办法 我无法通过 configure 如果有人能给我帮助 我将不胜感激 我的主机系统是Ubuntu 13 04 86 64位 在Virtualbox中运行 我的目标系统是 Pan
  • 抢占和上下文切换的区别

    一点介绍 我目前正在编写一个小型 读微型 RTOS 内核 它应该与内核中的大多数内容是一体的 然而 我找不到关于下面列出的一些事情的太多信息 这会很有帮助 除此之外 它实际上不是某种大学项目 而是我按照自己的意愿做的事情 回答所有问题的一个
  • armv8 NEON if 条件

    我想了解armv8 NEON内联汇编代码中的if条件 在armv7中 这可以通过检查溢出位来实现 如下所示 VMRS r4 FPSCR BIC r4 r4 1 lt lt 27 VMSR FPSCR r4 vtst 16 d30 d30 d
  • 为什么 ARM 使用两条指令来屏蔽一个值?

    对于以下功能 uint16 t swap const uint16 t value return value lt lt 8 value gt gt 8 为什么带 O2 的 ARM gcc 6 3 0 会产生以下程序集 swap unsig
  • ARM NEON:如何实现 256 字节查找表

    我正在使用内联汇编将我编写的一些代码移植到 NEON 我需要的一件事是将范围 0 128 的字节值转换为表中采用完整范围 0 255 的其他字节值 该表很短 但其背后的数学并不容易 因此我认为不值得每次 即时 计算它 所以我想尝试查找表 我
  • 如何创建具有自定义外设和内存映射的 QEMU ARM 机器?

    我正在为 Cortex M3 cpu 编写代码 并且正在使用以下命令执行单元测试qemu arm二进制 现在一切都很好 但我想知道我是否能够使用测试整个系统qemu system arm 我的意思是 我想为 qemu 编写自定义 机器 我将
  • 理解这部分手臂的汇编代码

    syntax unified thumb cpu cortex m4 arch armv7e m fpu fpv4 sp d16 Changes from unprivileged to privileged mode thumb func
  • 使用 ARM NEON 内在函数添加 alpha 和排列

    我正在开发一个 iOS 应用程序 需要相当快地将图像从 RGB gt BGRA 转换 如果可能的话 我想使用 NEON 内在函数 有没有比简单分配组件更快的方法 void neonPermuteRGBtoBGRA unsigned char
  • 如何使用 gcc 编译代码和 ARM Cortex A8 目标进行调用图分析?

    我对这个已经咬牙切齿了 我需要在 ARM 板上进行分析并需要查看调用图 我尝试使用 OProfile Kernel perf 和 Google 性能工具 一切正常 但不输出任何调用图信息 这使我得出结论 我没有正确编译代码 我在编译 C 代
  • RAM 存储二进制数和汇编语言的冒泡排序

    我必须使用 ARM v7 执行一个例程 在 RAM 内存中存储 10 个二进制数 然后使用冒泡排序对这些数字从高到低进行排序 我应该如何开始 func bubbleSortAscendingU32 ldr r3 r0 4 mov r1 9
  • 将 GCC 内联汇编与采用立即值的指令结合使用

    问题 我正在为 ARM Cortex M3 处理器开发定制操作系统 为了与我的内核交互 用户线程必须生成 SuperVisor Call SVC 指令 以前称为 SWI 用于软件中断 该指令在ARM ARM中的定义是 这意味着该指令需要即时
  • M1 MacBook Pro 上的 Android Studio 无法使用 ABI armeabi-v7a 模拟系统映像

    我的 M1 Macbook Pro 上的 Android Studio 可以很好地模拟 ABI arm64 v8a 的所有系统映像 API 24 29 30 31 但是 它无法使用 ABI armeabi v7a 运行所有映像 例如 API
  • 在linux x86平台上学习ARM所需的工具[关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我有一个 x86 linux 机器 在阅读一些关于 ARM 的各种信息时 我很好奇 现在我想花一些时间学
  • 是否可以将 SpaCy 安装到 Raspberry Pi 4 Raspbian Buster

    我一整天都在安装 SpaCy sudo pip install U spacy Looking in indexes https pypi org simple https www piwheels org simple Collectin
  • Beaglebone Black 的 U-boot 无法构建 - 目标 CPU 不支持 THUMB 指令

    我正在尝试按照 Chris Simmonds 的 掌握嵌入式 Linux 编程 中的说明为 Beagle Bone Black 构建 u boot 我已经构建了交叉工具链 现在正在尝试使用该工具链构建 Das U boot 但由于不支持 T
  • 直接在 ARM 目标上调试单声道应用程序

    我最近在 BeagleBone 嵌入式 ARM 设备上安装了 Mono 希望通过 USB 连接 Kinnect 传感器并使用 C Mono 控制它 我想知道 Mono 我正在使用 MonoDevelop 但我想这个问题也适用于 VS 是否允
  • 什么是遗留中断?

    我正在开发一个项目 试图弄清楚 ARM 架构的全局中断控制器中如何处理中断 我正在使用 pl390 中断控制器 我看到有一条线被称为传统中断 它绕过了分配器逻辑 假设有 2 个中断可以被编程为传统中断 任何人都可以帮助解释一下什么是遗留中断
  • GCC C++ (ARM) 和指向结构体字段的 const 指针

    假设有一个简单的测试代码 typedef struct int first int second int third type t define ADDRESS 0x12345678 define REGISTER type t ADDRE

随机推荐