18

我最近开始为 arm 内核进行汇编程序编程。我的第一个小演示,只有 .text 部分,运行没有任何问题。

作为一个逻辑扩展,我想将汇编代码构造成通常的部分:.text、.data、.bss。

所以我写了以下简单的程序:

 .globl _start

 .section .text

 _start:
     b   main
     b   .
     b   .
     b   .
     b   .
     b   .
     b   .
     b   .  


 main:
    ldr r0, x
    nop

 .section .data

 x:  .word  0xf0f0f0f0

 .end

  /opt/arm/bin/arm-as -ggdb -mcpu=arm7tdmi demo.s -o demo.o

退出并出现错误

 prog.s: Assembler messages:
 prog.s:17: Error: internal_relocation (type: OFFSET_IMM) not fixed up
 make: *** [prog.o] Error 1

我不知道为什么汇编器抱怨重定位,因为我认为这是链接器的任务。我可以想象我必须告诉汇编器我的 .data 部分不在汇编阶段的最终内存位置,但我找不到任何相关的东西。

虽然我找到了一种正确组装代码的方法,但通过替换

 .section .data

经过

 .org .

这不是一个令人满意的解决方案。尤其是考虑到气体文档突出了本节的意义。

也许你们中的某个专家可以帮助我获得一些智慧

4

3 回答 3

24

似乎唯一可以做到的方法是获取变量的地址并从该地址加载一个值。

ldr r1,=x    ; get address of x
ldr r0,[r1]  ; load from that address

在某种程度上,这也是有道理的。毕竟,如果 x 的地址(链接后)对于 PC 相对访问来说太远了怎么办?由于编译器(不进行链接)不知道数据部分与文本部分的距离有多远,所以它会拒绝编译该代码以防万一它无法访问。

通过使用这种间接访问变量的方式,可以保证该变量是可访问的(或者至少编译器可以确定该变量是否可访问)。

代码改编自http://www.zap.org.au/elec2041-cdrom/examples/intro/pseudo.s

于 2012-04-16T09:46:10.230 回答
5

我不打算将其作为例外答案,但它确实提供了更多洞察力,并且还为仅使用一条 ldr 指令提供了一种不方便的解决方案。

当使用这两个阶段的 ldr 方法时,汇编器实际上在您的代码之后添加了另外 4 个字节的数据!甚至在 .text 部分,这 4 个字节是 .data 变量的实际地址。然后第一条 ldr 指令实际指向该地址,然后您使用下一条 ldr 来使用实际地址。正如 tangrs 所讨论的那样,这个双指针可能是一种确保您的变量/常量可访问的方法,尤其是在 .data 部分距离更远的情况下(我上次运行时距离为 64k)。

查看一些正确方法的示例代码:

.text
.global _start
_start:
    ldr r0, =x
    ldr r0, [r0]
    mov r7, #1
    swi #0
    nop
.data
    x: .word 0xf0f0f0f0

汇编器实际上产生了这个:

00010074 <_start>:
   10074:   e59f000c    ldr r0, [pc, #12]   ; 10088 <_start+0x14>
   10078:   e5900000    ldr r0, [r0]
   1007c:   e3a07001    mov r7, #1
   10080:   ef000000    svc 0x00000000
   10084:   e1a00000    nop         ; (mov r0, r0)
   10088:   0002008c    andeq   r0, r2, ip, lsl #1

Disassembly of section .data:

0002008c <x>:
   2008c:   f0f0f0f0            ; <UNDEFINED> instruction: 0xf0f0f0f0

第一个 ldr 指向程序计数器之后的 12 个字节(考虑到当前指令 + 8 个以上)。这指向地址 0x10088(如 objdump 所述),它指向 andeq 指令(在此上下文中不是真正的指令)。它实际上是一个地址,0x0002008c,它指向我们在变量 x 的 .data 部分中的正确地址。现在我们在 r0 中有了变量的地址,我们可以在该地址上使用 ldr 来获取实际值。值得注意的是,尽管这两条 ldr 指令的源文件中的第二个操作数看起来非常不同,但机器编码是针对相同的 ldr 编码的;它们都是 LDR 立即数(虽然第一个 ldr 变体也被认为是 LDR 字面量,但它只是 LDR 立即数,将“Rn”硬编码为“1111”,无论如何这只是 pc 寄存器)。

考虑到所有这些,虽然不方便,但我们可以想办法只使用一次 LDR 立即数(文字)形式。我们所要做的就是确保获得与我们的真实数据相对应的正确立即值(偏移量)。做起来比说的容易:

.text
.global _start
_start:
    ldr r0, [pc, #8]
    mov r7, #1
    swi #0
    nop
x:  .word 0xf0f0f0f0

除了只需要使用一条 LDR 指令来获得相同的结果之外,此版本的源代码还有另一个细微差别:没有 .data 部分。这可以通过数据部分来完成,但它会将我们的数据放在更高的地址中,使我们的偏移量大得多,以至于我们可能不得不使用额外的指令来获得正确的偏移量。另一个注意事项是由于它位于 .text (rx) 部分中,默认情况下您不能在其上使用 str 。这是一个非常小的障碍,只需对 ld 使用 -N 选项,您的 .text 部分现在是 rwx。我敢肯定最后一个建议会激怒stackoverflow之神,来找我;)

于 2018-11-15T15:09:36.650 回答
2

这不适用于问题中的代码,但一般来说,此错误通常意味着您忘记定义要使用ldr指令加载的常量。

在应该可以正常编译的代码中,当项目在具有不同的汇编文件扩展名的不同工具链上编译时,通常会发生这种情况,因此.include指令可能包含错误的文件(如file.asm.s代替file.asm),从而导致缺少定义。

于 2019-09-15T14:56:45.757 回答