ARM 汇编回顾

这是一条为强者节省时间的提示

如果你觉得自己已经掌握了 ARM 汇编, 你可以跳过本小节.

我需要首先掌握哪些知识呢?

如果你想顺利地完成 LAB1 实验, 那么你至少需要:

  • 了解 Cortex-M0 编程模型 (实验指导书 3.2.3)
  • 了解 Cortex-M0 存储系统 (实验指导书 3.2.4)
  • 掌握 ARM 基础指令, 如 MOV, ADD, SUB, LDR, STR, CMP 和一些转移指令等 (实验指导书 3.6)
  • 掌握 ARM 基础伪指令, 如 AREA, DCD, SPACE, EQU, LDR 等 (实验指导书 4.2)
    如果你对自己不够自信, 没有关系, 继续往下做吧, 你可以先去试着阅读实验中用到的程序, 遇到不懂的地方再去查阅相关资料.

理解伪指令的意义

可能有的小伙伴会有这样的问题: 既然 CPU 仅依靠机器码运行, 而 "伪指令" 不能够被转换为机器码, 它的存在有什么意义呢?
伪指令其实有很多作用方式, 这里简要跟大家分享几种.

  • 假想我们手上只有指令, 如果我们期望实现对一个变量的反复读取, 处理 和 存储, 那么我们只能这样做: 先商定一个地址作为这个变量的存储地址, 之后的读写操作都在这个地址上进行, 这种方式看起来没什么问题, 但是当变量的个数很多时, 这种 "手动分配地址" 的方式貌似显得有些笨拙. 当多个文件中这样 "手动定义" 的变量之间存在相互引用关系时, 我们就变成了一个 "人工链接器", 这显然是不合理的, 我们还是需要将这一切交给真正的链接器去执行, 因此我们定义的变量必须要 "符号化", 于是我们希望通过一种方式使这些 "符号化" 的变量被自动分配地址, 而我们只需要关注这些变量的内容即可, 这种方式便是数据定义伪指令, 如 DCD, SPACE 等.
  • 有时我们希望代码尽可能简洁, 比如, 当我们想把其他文件中的某些代码拿到当前文件中使用时, 我们希望通过一条语句把它引用过来而不是笨拙地复制粘贴; 当我们写的一大段代码存在很强的规律性时, 我们希望用一种类似于 for 循环的语句去 "生成" 它们而不是把时间浪费在敲代码上; 当我们的程序在某几种应用情境下的版本只存在几条语句上的差别时, 我们希望首先通过一个情景指示符去选择使用哪几条语句, 选择好了再进行汇编.... 于是便有了汇编控制伪指令和符号定义伪指令, 这两种伪指令和 c 语言中以 "#" 开头的 "编译预处理指令" 作用很相似, 其本质是对指令代码的 "替换" 和 "生成".
  • 还有一种伪指令专门用于 "声明", "声明" 的对象不是开发者, 而是汇编器或链接器, 比如 CODE16, ALIGN 是对汇编器的声明, 告诉汇编器应该把指令翻译成多少位的机器码, 是否需要填字以保持对齐; IMPORT, EXPORT 是对链接器的声明, 告诉链接器这个符号是否是从别的文件引用过来的, IMPORT 类似于 c 语言中的 extern.

总之, 伪指令是为了方便开发者而诞生的, 我们应该对其心怀感激地去使用, 而不是把它们当成编程的负担.

迷惑众人的 LDR 伪指令

试想如果没有LDR伪指令, 我们在程序编写中会遇到这样两种问题:

  1. 使用 MOV 指令向寄存器加载立即数时, 一旦这个立即数超过了指定范围 (0-255), 就要手动定义一个数据, 然后把这个数据加载到寄存器中, 这种解决办法未免过于繁琐.
  2. 当使用 LDR 指令去读取一个变量时, 我们希望先把变量的地址读到某个寄存器中, 然后用这个寄存器去寻址内存, 然而怎样将一个 32 位的地址加载到一个寄存器中呢? 这时的情况又还原回了问题 1.

很庆幸我们有这样一条伪指令, 它也叫 LDR, 只是与 LDR 指令功能不同: 它可以用于加载任意 32 位立即数, 其操作数可以是 32 位立即数或变量标号.

  • 当操作数是立即数时, LDR 伪指令能够实现向寄存器直接加载 32 位立即数的功能, 如果这个立即数没有超出范围, 汇编器就将这条伪指令转换成一条 MOV 指令, 如果立即数超出了范围, 汇编器会自动地帮我们定义和加载数据, 这就解决了问题 1.
  • 问题 1 解决后, 问题 2 其实也就迎刃而解了: 由于变量标号代表的是变量的 32 位地址, 我们可以使用 LDR 伪指令把变量地址加载到寄存器中, 然后以这个寄存器作为索引去加载对应地址处的内存数据.
  • 另外, 当使用 LDR 伪指令时, 操作数前需要加上 "=" 以区分于 LDR 指令.
    下面给出了一个具体例子来展示汇编器对 LDR 伪指令做了哪些事:
    ;源代码
             LDR    R0,    =data1 ;通过 LDR 伪指令加载 data1 地址
             LDR    R1,    [R0] ;通过 LDR 指令加载 data1
    data1    DCD    0x11223344
    
    ;汇编得到的机器码
    ;地址 |  机器码  |   反汇编和注释
    0x48: 0x4807     ;LDR R0, [PC,#0x40] 加载 0x68 地址处的数据 (data1 的地址) 到 R0
    0x4A: 0x6807     ;LDR R1, [R0,#0] 加载 data1 到 R1
      ...
    0x4C: 0x11223344 ;data1, 用户分配的变量
      ...
    0x68: 0x0000004C ;data1 的地址, 汇编器自动分配的变量
    

伪指令的不同 "风格"

伪指令是 "不确定的", 不同的汇编器支持的伪指令一般是不同的, 常见的伪指令有 ADS 和 GNU 两种风格, 这两种风格的差别特别大. 如果你以后去从事 GNU 上的 ARM 开发, 千万切记不要对今天学到的东西 "生搬硬套" 哦!

results matching ""

    No results matching ""