Search code examples
assemblyarmarm64

How to initialize an array of 16-bit integers in ARM64?


I'm trying to learn some assembly, specifically ARM64.

I'm trying to initialize an array of 16-bit integers to some fixed value (123).

Here's what I have:

.global _main
.align 2

_main:
  mov x0, #0               ; start with a 0-byte offset
  mov x1, #123             ; the value to set each 16-bit element to
  lsl x1, x1, #48          ; shift this value to the upper 16-bits of the register

  loop:
    str x1, [sp, x0]       ; store the full 64-bit register at some byte offset
    add x0, x0, #2         ; advance by two bytes (16-bits)
    cmp x0, #10            ; loop until we've written five 16-bit integers
    b.ne loop

  ldr x2, [sp]             ; load the first four 16-bit integers into x2
  ubfm x0, x2, #48, #63    ; set the exit status to the leftmost 16-bit integer
  mov x16, #1              ; system exit
  svc 0                    ; supervisor call

I'm expecting the exit status to be 123 but it's 0. I don't understand why.

If I comment out the last two lines of the loop the exit status is 123 which is correct.

Please could someone explain what's going on? Is it an alignment problem?

Thanks


Solution

  • Assuming you are running your program on a little-endian Aaarch64 system, on a given loop iteration, you are overriding the bytes you had modified in the previous one:

    You are actually writing the bytes: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7b
    from: sp + x0 + 0
    to: sp + x0 + 7 at each iteration.

    Initial conditions:

    (gdb) p/x $sp
    $3 = 0x40010000
    (gdb) x/12xh 0x40010000
    0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
    0x40010010:     0x0000  0x0000  0x0000  0x0000
    
    # initializing some memory to 0xff so that we may see what is going on
    (gdb) set {unsigned long }(0x40010000) = 0xffffffffffffffff
    (gdb) set {unsigned long }(0x40010008) = 0xffffffffffffffff
    (gdb) set {unsigned long }(0x40010010) = 0xffffffffffffffff
    
    (gdb) x/12xh 0x40010000
    0x40010000:     0xffff  0xffff  0xffff  0xffff  0xffff  0xffff  0xffff  0xffff
    0x40010010:     0xffff  0xffff  0xffff  0xffff
    

    Before first loop:

    (gdb) p/x$x1
    $4 = 0x7b000000000000
    

    Looping:

    # pass #1, after str x1, [sp, x0]
    (gdb) x/12xh 0x40010000
    0x40010000:     0x0000  0x0000  0x0000  0x007b  0xffff  0xffff  0xffff  0xffff
    0x40010010:     0xffff  0xffff  0xffff  0xffff
    
    # pass #2, after str x1, [sp, x0]
    (gdb) x/12xh 0x40010000
    0x40010000:     0x0000  0x0000  0x0000  0x0000  0x007b  0xffff  0xffff  0xffff
    0x40010010:     0xffff  0xffff  0xffff  0xffff
    
    # pass #3, after str x1, [sp, x0]
    (gdb) x/12xh 0x40010000
    0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x007b  0xffff  0xffff
    0x40010010:     0xffff  0xffff  0xffff  0xffff
    
    # pass #4, after str x1, [sp, x0]
    0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x007b  0xffff
    0x40010010:     0xffff  0xffff  0xffff  0xffff
    
    # pass #5, after str x1, [sp, x0]
    (gdb) x/12xh 0x40010000
    0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x007b
    0x40010010:     0xffff  0xffff  0xffff  0xffff
    
    # after ldr x2, [sp]:
    (gdb) p/x $sp
    $2 = 0x40010000
    (gdb) p/x $x2
    $3 = 0x0
    (gdb) 
    

    Your program would work if you were not shifting the value in x1, by commenting-out lsl x1, x1, #48:

    # after ldr x2, [sp]
    (gdb) x/12xh 0x40010000
    0x40010000:     0x007b  0x007b  0x007b  0x007b  0x007b  0x0000  0x0000  0x0000
    0x40010010:     0x0000  0x0000  0x0000  0x0000
    (gdb) p/x $x2
    $1 = 0x7b007b007b007b
    (gdb) 
    

    This being said, this would probably better to use the strh instruction, so that you would avoid writing more bytes than you should, i.e. 16 instead of 2, at each iteration of your loop.

    Bottom line, on a little-endian system, the constant 0x0000000000007b will be stored in memory (ascending addresses) as 7b 00 00 00 00 00 00 00, and the constant 0x7b00000000000000 will be stored as 00 00 00 00 00 00 00 7b.

    Because of the shifting you do, you are storing 0x7b00000000000000, not 0x0000000000007b, into memory.