Search code examples
arraysassemblyarmarmv8

ARM Assembly Arrays


I am trying to figure out how arrays work in ARM assembly, but I am just overwhelmed. I want to initialize an array of size 20 to 0, 1, 2 and so on.

A[0] = 0
A[1] = 1

I can't even figure out how to print what I have to see if I did it correctly. This is what I have so far:

.data
.balign 4       @ Memory location divisible by 4
        string: .asciz "a[%d] = %d\n"
        a:      .skip   80      @ allocates 20
.text
.global main
.extern printf

main:
        push    {ip, lr}        @ return address + dummy register
        ldr     r1, =a          @ set r1 to index point of array
        mov     r2, #0          @ index r2 = 0
loop:
        cmp     r2, #20         @ 20 elements?
        beq     end             @ Leave loop if 20 elements
        add     r3, r1, r2, LSL #2      @ r3 = r1 + (r2*4)
        str     r2, [r3]        @ r3 = r2
        add     r2, r2, #1      @ r2 = r2 + 1
        b       loop            @ branch to next loop iteration
print:
        push    {lr}            @ store return address
        ldr     r0, =string     @ format
        bl      printf          @ c printf
        pop     {pc}            @ return address

ARM confuses me enough as it is, I don't know what i'm doing wrong. If anyone could help me better understand how this works that would be much appreciated.


Solution

  • as mentioned your printf has problems, you can use the toolchain itself to see what the calling convention is, and then conform to that.

    #include <stdio.h>
    unsigned int a,b;
    void notmain ( void )
    {
      printf("a[%d] = %d\n",a,b);
    }
    

    giving

    00001008 <notmain>:
        1008:   e59f2010    ldr r2, [pc, #16]   ; 1020 <notmain+0x18>
        100c:   e59f3010    ldr r3, [pc, #16]   ; 1024 <notmain+0x1c>
        1010:   e5921000    ldr r1, [r2]
        1014:   e59f000c    ldr r0, [pc, #12]   ; 1028 <notmain+0x20>
        1018:   e5932000    ldr r2, [r3]
        101c:   eafffff8    b   1004 <printf>
        1020:   0000903c    andeq   r9, r0, ip, lsr r0
        1024:   00009038    andeq   r9, r0, r8, lsr r0
        1028:   0000102c    andeq   r1, r0, ip, lsr #32
    
    Disassembly of section .rodata:
    
    0000102c <.rodata>:
        102c:   64255b61    strtvs  r5, [r5], #-2913    ; 0xb61
        1030:   203d205d    eorscs  r2, sp, sp, asr r0
        1034:   000a6425    andeq   r6, sl, r5, lsr #8
    
    Disassembly of section .bss:
    
    00009038 <b>:
        9038:   00000000    andeq   r0, r0, r0
    
    0000903c <a>:
        903c:   
    

    the calling convention is generally first parameter in r0, second in r1, third in r2 up to r3 then use the stack. There are many exceptions to this, but we can see here that the compiler which normally works fine with a printf call, wants the address of the format string in r0. the value of a then the value of b in r1 and r2 respectively.

    Your printf has the string in r0, but a printf call with that format string needs three parameters.

    The code above used a tail optimization and branch to printf rather than called it and returned from. The arm convention these days prefers the stack to be aligned on 64 bit boundaries, so you can put some register, you dont necessarily care to preserve on the push/pop in order to keep that alignment

    push {r3,lr}
    ...
    pop {r3,pc}
    

    It certainly wont hurt you to do this, it may or may not hurt to not do it depending on what downstream assumes.

    Your setup and loop should function just fine assuming that r1 (label a) is a word aligned address. Which it may or may not be if you mess with your string, should put a first then the string or put another alignment statement before a to insure the array is aligned. There are instruction set features that can simply the code, but it appears functional as is.