Вопрос

The IAR compiler for ARM Cortex-M3 provides inline assembly. How can one store the address of a specific function to a location on the stack?

The C code would like this

void tick();

void bar()
{
    int x;
    // modify a value on stack
    (&x)[4] = &tick;
}

While this works in general it is optimized away by the compiler in release build. I have tried to code it with inline assembly

void bar()
{
    int x;
    asm("ldr r0,%0" : : "i" (&tick));
    asm("str r0,[sp+#0x10];
}

The ldr instruction is not accepted by the IAR compiler. The problem is that this instruction requires an addressing mode with a register and offset. The actual address of the function tick is store behind the function and the ldr instruction holds only the offset to the memory location the holds the actual address. The disassembly is similar like this:

    ldr r0,??tick_address
    str r0,[sp+#0x10]
    bx lr ; return
??tick_address dd tick

How do I get the address of tick immediately to a register to use it for the stack manipulation?

Это было полезно?

Решение

GNU GCC inline assembly can do mere assignments via pseudo-empty asm() statements, like:

asm("" : "=r"(foo) : "0"(tick));

This tells the compiler:

  1. The variable foo is to be taken from a register after the inline assembly block
  2. The variable tick is to be passed in - in the same register (argument zero)

The actual choice of which register is to be used is completely left to the compiler.

The trick here are the output and input constraints - we just alias the (one and only) input to the output, and the compiler will, on its own, choose a suitable register, and generate the instructions necessary to load / store the respective variables before / after the "actual" inline assembly code. You could even do:

asm("" : "=r"(foo1), "=r"(foo2) : "0"(tick1) , "1"(tick2));

to do two "assignments" in a single inline assembly statement.

This compiler-generated "set the inputs, retrieve the outputs" code generation happens even if the actual inline assembly is empty (as here).

Another example: Say you want to read the current program counter - the PC register. You can do that on ARM via two different inline assembly statements:

asm("" : "=pc"(foo));
asm("mov %0, PC" : "=r"(foo));

This is not 100% identical; in the second case, the compiler knows that whatever register it wants to see foo in after the asm, it'll find it there. In the former, the compiler knows that were it to use foo after the statement, it needs to retrieve it from PC. The difference between the two would be if you did:

uintptr_t *val;
uintptr_t foo;

asm("" : "=pc"(foo));
*val = foo;

In this case, the compiler can possibly identify that this can be turned into a single str [R...], PC because it knows foo is in pc after the asm. Were you to write this via

asm("mov %0, PC" : "=r"(foo));
*val = foo;

the compiler would be forced to create (assuming it chooses R0 / R1 for foo/val):

MOV R0, PC
STR [R1], R0

The documentation for this behaviour is largely in the "Extended ASM" section of the GCC manuals, see the example for the contrived combine instruction.

Другие советы

There is no assignment the variable x in your code, therefore it's value is undefined and setting foo to an undefined value isn't required to change foo.

You need to assign the value to the variable, not to some memory location you assume the compiler use to implement it.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top