Home > OS >  ARM inline assembly, registers are read in an incorrect order
ARM inline assembly, registers are read in an incorrect order

Time:02-11

I am trying to read ARM registers via C using inline assembly but it doesn't follow the order of execution as it should.

volatile uint32_t var1 = 0;
volatile uint32_t var2 = 0;
volatile uint32_t var3 = 0;
volatile uint32_t var4 = 0;

__asm volatile(
    "mov r0, #5\n\t"
    "mov r1, #6\n\t"
    "mov r2, #7\n\t"
    "mov r3, #8\n\t"
            
    "mov %0, r0\n\t" 
    "mov %0, r1\n\t"
    "mov %0, r2\n\t" 
    "mov %0, r3\n\t"
            
    : "=r" (var1),
    "=r" (var2),
    "=r" (var3),
    "=r" (var4));

What happens is that the output is;

var1 = 8
var2 = 5
var3 = 6
var4 = 7

What I expect is;

var1 = 5
var2 = 6
var3 = 7
var4 = 8

It seems r0 is read last and it starts with r1. Why is that happening?

Note: Ignore the purpose of the code or how variables are defined, it's a copy/paste from a bigger application. It's just this specific register behavior in question.

CodePudding user response:

GCC-style assembly inserts are designed to insert a single instruction per asm("..."), and they require you to give accurate information about all of the registers involved. In this case, you have not notified the compiler that registers r0, r1, r2, and r3 are used internally, so it probably thinks it's OK to reuse some of those for var1 through var4. You have also reused %0 as the destination of all four of the final mov instructions, so all four of them are actually writing to var1.

Also, it may or may not be your immediate problem, but volatile doesn't do what you think it does and probably isn't accomplishing anything useful here.

How to fix it? Well, first off, there needs to be a really strong reason why you can't just write

uint32_t var1 = 5;
uint32_t var2 = 6;
uint32_t var3 = 7;
uint32_t var4 = 8;

Assuming there is such a reason, then you should instead try writing one instruction per asm, and not using any scratch registers at all...

asm ("mov %0, #5" : "=r" (var1));
asm ("mov %0, #6" : "=r" (var2));
asm ("mov %0, #7" : "=r" (var3));
asm ("mov %0, #8" : "=r" (var4));

If you really absolutely have to do a whole bunch of work in a single asm then the first thing you should consider is putting it in a separate .S file and making it conform to the ABI so that you can call it like a normal function:

    .text
    .globl do_the_thing
    .type do_the_thing, @function
_do_the_thing:
    ; all your actual code here
    bx r14
.size do_the_thing, .-do_the_thing

and then in your C

   rv = do_the_thing(arg1, arg2, ...);

If there's no way to make that work, then, and only then, should you sit down and read the entire "Extended Asm" chapter of the GCC manual and work out how to wedge the complete register-usage behavior of your insert into the constraints. If you need help with that, post a new question in which you show the real assembly language construct you need to insert, rather than a vague example, because every little detail matters.

CodePudding user response:

The arguments passed into the inline assembly need to be incremented;

"mov %0, r0\n\t" 
"mov %1, r1\n\t"
"mov %2, r2\n\t" 
"mov %3, r3\n\t"
  • Related