• 说说ARM汇编的LDR伪指令


    我们知道ARM CPU中有一条被广泛使用的指令LDR,它主要是用来从存储器(确切地说是地址空间)中装载数据到通用寄存器。但不论是ARMASM还是GNU ARM AS,都提供了一条与之同名的伪指令LDR,而在实际中使用该伪指令的情况也较多,那他们有什么不同呢?下面我谈谈我的理解。

      由于我使用GNU工具链,所以以下的内容都以GNU AS的ARM语法为准。

      LDR伪指令的语法形式如下:

      LDR , =

      这个常量表达式中可以包含Label(在ARM汇编中Label会在连接时解释为一个常数),且其中的常数前不加#符号。

      范例demo.s:

    .equ STACK_BASE, 0×0c002000

    .equ STACK_SIZE, 0×00001000

    .text

    ldr sp, = STACK_BASE

    ldr sl, = STACK_BASE - STACK_SIZE

    ldr pc, = entry

      这是一个合法的汇编文件,它把堆栈基址设为0×0c002000,栈限设为0×0c001000,然后跳到entry所标识的C程序中执行。

      下面我们假设符号“entry”的地址为0×0c000000。

      我们如果把上面代码写成:

    .text

    mov sp, #0×0c002000

    mov sl, #0×0c001000

    mov pc, #0×0c000000

      汇编器会报错:

      demo.s: Assembler messages:

      demo.s:2: Error: invalid constant — `mov sp,#0×0c002000′

      demo.s:3: Error: invalid constant — `mov sl,#0×0c001000′

      说起这个错误的原因可就话长了,简而言之是因为RISC有一个重要的概念就是所有指令等长。在ARM指令集中,所有指令长度为4字节(Thumb指令是2字节)。那问题就来了,4字节是不可能同时存的下指令控制码和32位立即数的,那么我要把一个32位立即数(比如一个32位地址值)传送给寄存器该怎么办?

      RISC CPU提供一个通用的方法就是把地址值作为数据而不是代码,从存储器中相应的位置读入到寄存器中,待会我们会看到这样的例子。

      此外ARM还提供另一种方案。由于传送类指令的指令控制码部分(cond, opcode, S, Rd, Rn域)占去了20个字节,那能提供给立即数的就只剩12个位了。

      ARM并未使用这12个位来直接存一个12位立即数,而是使用了类似有效数字一样的概念,只存8个字节的有效位和一个4位的位偏移量(偏移单位为2)。这个东西在ARM被叫做术语immed_8,有兴趣的人可以找资料了解一下,到处都有介绍。

      可以看出ARM的这个方法能直接使用的立即数是相当有限的,像0xfffffff0这样的数显然无法支持。别着急,ARM的传送类指令中还有一个MVN指令可以解决该问题。显然0×0000000f是一个有效立即数,MVN会先将其取反再传送,这样有效立即数的范围又扩充了一倍。

      可就算如此仍有大量的32位立即数是无效的,比如上面那个例子中的0×0c002000和0×0c001000。面对这种问题一是使用RISC的通用方法,二是分次载入。

      比如可以这样载入0×0c002000:

    .text

    mov sp, #0×0c000000

    add sp, sp, #0×00002000 或者:

    .text

    mov sp, #0×0c000000

    orr sp, sp, #0×00002000

      感觉很狡猾是吧,呵呵。但是要注意它和方法一的一大区别:需要多条指令。那么在一些对指令数目有限制的场合就无法使用它,比如异常向量表处要做长跳转(超过±32MB)的话就只能用方法一;还有就是在同步事务中该操作不是原子的,因此可能需要加锁。

      扯了这么多再回到LDR伪指令上来。显然上面的内容是复杂繁琐的,如果然程序员在写程序的时候还要考虑某个数是不是immed_8一定超级麻烦,因此为了减轻程序员的负担才引入了LDR伪指令。

      你一定很好奇第一段代码demo.s被GNU AS变成了什么,好,让我们在Linux环境下执行下面的命令:

      arm-elf-as -o demo.o demo.s

      arm-elf-objdump -D demo.o

      结果:

    demo.o: file format elf32-littlearm

    Disassembly of section .text:

    00000000 <.text>:

    0: e59fd004 ldr sp, [pc, #4] ; c <.text+0xc>

    4: e59fa004 ldr sl, [pc, #4] ; 10 <.text+0×10>

    8: e59ff004 ldr pc, [pc, #4] ; 14 <.text+0×14>

    c: 0c002000 stceq 0, cr2, [r0]

    10: 0c001000 stceq 0, cr1, [r0]

    14: 00000000 andeq r0, r0, r0

    Disassembly of section .data:

      由于entry还没连上目标地址,objdump反汇编会认为是0,我们先不管它。另外两条LDR伪指令变成了实际的LDR指令!但目标很奇怪,都是[pc, #4]。那好我们看看[pc, #4]是什么。

      我们知道pc中存放的是当前指令的下下条指令的位置,也就是. + 8。那么上面的第一条指令ldr sp, [pc, #4]中的pc就是0×8,pc + 4就是0xc,而[0xc]的内容正是0×0c002000;同理,第二条ldr指令也是如此。显然这里LDR伪指令采用的是RISC通用的方法。

      另外要说的是,如果LDR的是一个immed_8或者immed_8的反码数,则会直接被解释成mov或mvn指令。如ldr pc, = 0×0c000000会被解释成mov pc, 0×0c000000。

      最后一点补充,我发现arm-elf-gcc通常都用累加法。如C语句中的i = 0×100ffc04;会变成类似于以下的语句:

      mov r0, #0×10000004

      add r0, r0, #0×000ff000

      add r0, r0, #0×00000c00

      …

      原因不详。

    原文:http://xcd.blog.techweb.com.cn/archives/185.html

  • 相关阅读:
    30 字符编码
    Xilinx Vivado的使用详细介绍(3):使用IP核
    Xilinx Vivado的使用详细介绍(1):创建工程、编写代码、行为仿真
    Vivado SDK ,调用math.h函数的时候出现 undefined reference to `xxx' ,解决方案
    Xilinx Vivado的使用详细介绍(4):Zedboard+vivado之流水灯(加SDK)
    xilinx Vivado的使用详细介绍(2):创建工程、添加文件、综合、实现、管脚约束、产生比特流文件、烧写程序、硬件验证
    no.4
    no.5
    关于贪心算法
    关于三角形把平面分成几块的问题
  • 原文地址:https://www.cnblogs.com/hicjiajia/p/2585457.html
Copyright © 2020-2023  润新知