8

I want to access local variable declared in C in inline arm Assembly. How do I do that?

Global variables can be accessed like this,

int temp = 0;
Function(){
    __asm(
       ".global temp\n\t"           
        "LDR R2, =temp\n\t"                                                     
        "LDR R2, [R2, #0]\n\t"
    );
}       

But how do I access local variables? I tried changing ".global" to ".local" for local variables, but it generated error (undefined reference to `temp'). The IDE I am using is KEIL.

Any thoughts? Thanks in Advance.

old_timer
  • 69,149
  • 8
  • 89
  • 168
Muzahir Hussain
  • 1,009
  • 2
  • 16
  • 35
  • 2
    Local variables either exist on the stack or in a register. It will be difficult to transfer them without some sort of compiler support. GCC inline assembler will put them in registers for you. I think [Keil is based on GCC](http://www2.keil.com/mdk5/compiler/6/). `.local` just means a 'global' with static linkage. If you declare `void foo(void) { static int bar;}` you can access bar this way. However, that is not a normal concept of 'local' to a 'C' programmer. It is a 'static'. – artless noise Nov 27 '17 at 17:04

2 Answers2

6

According to GCC docs: 6.45.2.3 Output Operands

You can pass the values like this:

#include <stdio.h>

int main(int argc, char *argv[]) {

  int src = 1;
  int dst;   

  asm ("mov %1, %0\n\t add $1, %0" : "=r" (dst) : "r" (src));

  printf("0x%X\n", dst);

  return 0;
}

After your asm code you put the ':' character and the values you want to pass like this: "(=|+)(r|m)" (variable). Use '=' when overriding the value and '+' when reading or overriding the value, then use the 'r' letter if the value resides in a register or 'm' if it resides in memory.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
ProNOOB
  • 504
  • 2
  • 14
  • 1
    That's x86 with AT&T syntax (`add src, dst`), but yes, extended ASM with constraints is the right answer. However, you should definitely not include the `mov` in your inline asm. Always use constraints and let the compiler generate a `mov` if it wants to save the old value of `src`. e.g. `asm("inc %0" : "=r"(dst) : "0" (src));` so `%0` starts as src and finishes as dst (matching constraint). Of course you can't get optimal code for all cases this way; you're still preventing the compiler from emitting `lea 1(%rax), %esi` to copy-and-add. https://gcc.gnu.org/wiki/DontUseInlineAsm – Peter Cordes Nov 28 '17 at 03:59
3

r minimal runnable example

main.c

#include <assert.h>
#include <inttypes.h>

int main(void) {
    uint64_t in0 = 1, in1 = 2, out;
    __asm__ (
        "add %[out], %[in0], %[in1];"
        : [out] "=r" (out)
        : [in0] "r"  (in0),
          [in1] "r"  (in1)
    );
    assert(in0 == 1);
    assert(in1 == 2);
    assert(out == 3);
}

GitHub upstream.

Compile and run:

sudo apt-get install qemu-user gcc-aarch64-linux-gnu
aarch64-linux-gnu-gcc -std=c99 -ggdb3 -march=armv8-a -pedantic -Wall -Wextra -o main.out main.c
qemu-aarch64 -L /usr/aarch64-linux-gnu -E LD_BIND_NOW=1 main.out

Disassembly:

gdb-multiarch -nh -batch -ex 'disassemble/rs main' add.out

Output excerpt:

Dump of assembler code for function main:
add.c:
6   int main(void) {
   0x0000000000000764 <+0>: fd 7b bd a9 stp x29, x30, [sp, #-48]!
   0x0000000000000768 <+4>: fd 03 00 91 mov x29, sp

7       uint64_t in0 = 1, in1 = 2, out;
   0x000000000000076c <+8>: 20 00 80 d2 mov x0, #0x1                    // #1
   0x0000000000000770 <+12>:    e0 0f 00 f9 str x0, [sp, #24]
   0x0000000000000774 <+16>:    40 00 80 d2 mov x0, #0x2                    // #2
   0x0000000000000778 <+20>:    e0 13 00 f9 str x0, [sp, #32]

8       __asm__ (
   0x000000000000077c <+24>:    e0 0f 40 f9 ldr x0, [sp, #24]
   0x0000000000000780 <+28>:    e1 13 40 f9 ldr x1, [sp, #32]
   0x0000000000000784 <+32>:    00 00 01 8b add x0, x0, x1
   0x0000000000000788 <+36>:    e0 17 00 f9 str x0, [sp, #40]

9           "add %[out], %[in0], %[in1];"
10          : [out] "=r" (out)
11          : [in0] "r"  (in0),
12            [in1] "r"  (in1)
13      );

So we see that r translated into stack sp relative str loads, which is where local variables live.

Tested in Ubuntu 18.10, GCC 8.2.0, QEMU 2.12.

Ciro Santilli
  • 3,693
  • 1
  • 18
  • 44
  • I don't think your `assert`s can truly test that `asm` didn't clobber input-only registers. If you compile without optimization, the values will be reloaded from the stack. If you compile *with* optimization, constant propagation will evaluate `in0 == 1` to true at compile time. You'd need something like an `escape` function that used `asm("" : "+r"(in0));` to make the compiler lose any knowledge about the *value* of the var while keeping it in a reg, before the `assert`. – Peter Cordes Feb 23 '19 at 22:47