Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

internal relocation not fixed up

i recently started assembler programming for arm cores. My first little demos, only with the .text section, ran without any problems.

As a logical extension i wanted to structure the assembler code into the usual sections: .text, .data, .bss .

So i wrote the following simple program:

 .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

But

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

exits with the error

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

I have no clue why the assembler complains about relocation, because i thought that's the task of the linker. I could imagine that i have to tell the assembler that my .data section isn't located at the final memory postion at the assembling stage, but i can't find anything related.

Although i found a way to get the code assembled correctly, by replacing

 .section .data

by

 .org .

that is not a satisfying solution. Especially in view of the fact that the gas documentation highlight the sense of this section.

Maybe someone of you experts can help me to gain some wisdom

like image 463
user1146332 Avatar asked Apr 10 '12 18:04

user1146332


3 Answers

It seems the only way you can do it is by grabbing the address of the variable and load a value from that address.

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

In a way, this also kind of makes sense. After all, what if the address of x (after linking) is too far away for a PC relative access? Since the compiler (which doesn't do the linking) does not know how far away the data section may be from the text section, it would refuse to compile that code just in case it isn't reachable.

By using this indirect way of accessing a variable, it is guaranteed that the variable will be reachable (or at least the compiler can be sure whether the variable is reachable or not).

Code adapted from http://www.zap.org.au/elec2041-cdrom/examples/intro/pseudo.s

like image 167
tangrs Avatar answered Nov 14 '22 02:11

tangrs


I don't intend this to be the excepted answer, but it does offer some more insight and also offer an inconvenient solution to using just one ldr instruction.

When using this two stage ldr method, the assembler actually adds another 4 bytes of data after your code! In the .text section even, these 4 bytes are the actual address to your .data variable. The first ldr instruction then actually points to this address, you then use the next ldr to use the real address. As tangrs was discussing, this double pointer may be a way to make sure your variables/constants are reachable, especially with the .data section being farther away (64k away in my last run).

Looking at some sample code of the right way to do this:

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

The assembler ACTUALLY produces this:

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

The first ldr is pointing 12 bytes after the program counter (considered the current instruction + eight more). This points to address 0x10088 (as noted by objdump), which is pointing to the andeq instruction (not a real instruction in this context). It is really an address, 0x0002008c, which is pointing to our proper address in the .data section for our variable x. Now that we have the address of our variable in r0, we can use ldr on that address to get the real value. It is notable though, that even though the 2nd operand in the source file for both of these ldr instructions looks very different, the machine encoding is for the same ldr encoding; they are both LDR Immediate (though the first ldr variant is also considered LDR Literal, it is just LDR Immediate with 'Rn' hardcoded to '1111', which is just the pc register anyway).

With all of this in mind, though it is inconvenient, we can figure a way to just use the LDR Immediate(Literal) form once. All we have to do is make sure to get the correct immediate value (offset) that corresponds with our real data. Easier done than said:

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

Other than only having to use one LDR instruction to achieve the same result, there's one other subtle difference in this version of the source: there is no .data section. This can be done with a data section, but it would put our data in a much higher address, making our offset so much larger that we may have to use extra instructions just to get the offset correct. Another side note is due to this being in the .text (r-x) section, you can't use str on it by default. This is a very small barrier, just use the -N option for ld and your .text section is now rwx. I'm sure that last suggestion will anger the stackoverflow gods, come at me ;)

like image 20
XlogicX Avatar answered Nov 14 '22 02:11

XlogicX


This doesn't apply to the code in the question, but in general this error often means that you have forgotten to define a constant that you're loading with the ldr instruction.

In a code which is supposed to compile fine, this often happens when the project is compiled on a different toolchain with a different extension for assembler files, so that .include directives may include the wrong file (like file.asm.s instead of file.asm), leading to missing defines.

like image 27
Dmitry Grigoryev Avatar answered Nov 14 '22 01:11

Dmitry Grigoryev