Search code examples
assemblyarmarmv7

armv7-m bare metal ldr/str symbolic memory


so I know questions for ldr/str on arm are countless. Perhaps this is another twist (not probable) or I'm just missing something (more probable.)

So this is bare metal and I want to load/store some variable in memory. And because I insist I want to give it a name. Naively I could write:

.section .bss
var: .word 0

.section .text
str r0, var 

(having a custom linker script which puts .bss in ram and .text in flash)

Of cause this doesn't work because instructions are 32bit and only have place for some smaller immediate. And the instructions I'm talking about live in flash which is 0x8000000+x and the variable is to be stored in memory which is somewhere in the 0x20000000+y.

Manually I know quite some ways to solve this:

  • storing the variables address in a constant (varaddr: .word 0x2001234; ldr r1, [pc,#varaddr]; str r0, [r1])
  • loading ram-base in a register and addressing it relative (ldr r1, #0x20000000; str r0, [r1,#varoffset])
  • construct the address by arithmetic (mov r1, #0x2000000; add r1, #offset / orr / movw / movt something)
  • certainly quite some more

Every of these variants work but neither of these variants let me use the label which I really want to use.

So what am I missing here. Is my idea for the linker script and labels bogus? Is there some assembler feature I didn't see? Something completely different?


Solution

  • One way you could use symbolic names for variables in static storage is to define a structure for your variables. This allows you to load the base address of your structure into a register and then access structure members using symbolic names relative to the base address. For example, you could do:

            .struct 0          @ start a new structure
    foo:    .skip 4            @ length of foo
    bar:    .skip 4            @ length of bar
    baz:    .skip 4            @ length of baz
    len:                       @ total length of the structure
    
            .section .bss      @ switch to the BSS (uninitialised data) section
            .balign 4          @ align to 4 bytes
    variables:
            .space len         @ reserve space for your variables
    
            .section .text     @ switch to the text (code) section
    
            ...
            ldr r0, =variables @ load r0 with the base address of your variables
            ldr r1, [r0+#foo]  @ access foo
            str r2, [r0+#bar]  @ access bar
            ldr r3, [r0+#baz]  @ access baz
    

    This is pretty much the closest you can get to symbolic names for variables in static storage. If the variables are on the stack, you can use a similar approach using the frame pointer (or stack pointer) as the base address. The operand to .struct is the base address of the structure for which you can chose any value you like.

    As for movw and movt. These offer a tiny performance advantage on some microarchitectures over ldr ..., =... as they do not require data fetches to the text section. This makes no difference on armv7-m targets as far as I know; also, movw and movt consume two extra bytes versus ldr with an = operand. I thus recommend you to stick with ldr and an = operand. The usage of movw and movt is like this:

            movw r0, :lower16:foo  @ load lower 16 bit of foo's address into r0
            movt r0, :upper16:foo  @ or higher 16 bit of foo's address into r0
    

    These two have to be issued in this specific order as movw clears the upper 16 bit. The prefixes :lower16: and :upper16: select appropriate relocation types referring to just the lower and upper 16 bit of the symbol's address. You can make a macro to make this easier to type:

            .macro addr reg, sym
            movw \reg, :lower16:\sym
            movt \reg, :upper16:\sym
            .endm
    

    This allows you to write

            addr r0, foo
    

    to generate the aforementioned movw and movt pair.