I want to execute the exit system call in ARM using inline assembly on a Linux Android device, and I want the exit value to be read from a location in memory.
Without giving this extra argument, a macro for the call looks like:
#define ASM_EXIT() __asm__("mov %r0, #1\n\t" \
"mov %r7, #1\n\t" \
"swi #0")
This works well. To accept an argument, I adjust it to:
#define ASM_EXIT(var) __asm__("mov %r0, %0\n\t" \
"mov %r7, #1\n\t" \
"swi #0" \
: \
: "r"(var))
and I call it using:
#define GET_STATUS() (*(int*)(some_address)) //gets an integer from an address
ASM_EXIT(GET_STATUS());
invalid 'asm': operand number out of range
I can't explain why I get this error, as I use one input variable in the above snippet (%0/var). Also, I have tried with a regular variable, and still got the same error.
Extended-asm syntax requires writing %%
to get a single %
in the asm output. e.g. for x86:
asm("inc %eax") // bad: undeclared clobber
asm("inc %%eax" ::: "eax"); // safe but still useless :P
%r7
is treating r7
as an operand number. As commenters have pointed out, just omit the %
s, because you don't need them for ARM, even with GNU as
.
There aren't specific-register constraints for ARM to request operands in specific registers like there are for x86. (e.g. x86's "a"
constraint only lets the compiler pick (the bottom part of of) rax
).
You can use register int var asm ("r7")
to force a var to use a specific register, and then use an "r"
constraint and assume it will be in that register. (This is in fact the only supported use of register ... asm("regname")
: https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html)
This generated efficient code which avoids wasting an instruction on a reg-reg move.
See it on the Godbolt Compiler Explorer:
__attribute__((noreturn)) static inline void ASM_EXIT(int status)
{
register int status_r0 asm ("r0") = status;
register int callno_r7 asm ("r7") = 1;
asm volatile("swi #0\n"
: // "=r"(status_r0) // exit doesn't return; other syscalls write r0
: "r" (status_r0), "r" (callno_r7)
: "memory" // any side-effects on shared memory need to be done before this, not delayed until after
);
// __builtin_unreachable(); // optionally let GCC know the inline asm doesn't "return"
}
#define GET_STATUS() (*(int*)(some_address)) //gets an integer from an address
void foo(void) { ASM_EXIT(12); }
push {r7} @ # gcc is still saving r7 before use, even though it sees the "noreturn" and doesn't generate a return
movs r0, #12 @ stat_r0,
movs r7, #1 @ callno,
swi #0
# yes, it literally ends here, after the inlined noreturn
void bar(int status) { ASM_EXIT(status); }
push {r7} @
movs r7, #1 @ callno,
swi #0 # doesn't touch r0: already there as bar()'s first arg.
exit
doesn't return, but for other cases it's important that the compiler know about reg writes. (It assumes that all registers keep their values across asm
statements unless the constraints indicate otherwise.)
We can't declare a clobber on "r0"
because that would conflict with the compiler picking "r0"
as the input operand for status
. You can have two register ... asm("r0")
variables, one of them for the return value which you use as the "=r"(retval)
output, the other as the "r"(arg0)
input. See gcc arm optimizes away parameters before System Call for an example of this.
The system-call ABI for most OSes on most ISAs (including Linux on most ISAs other than x86-64 where syscall
itself clobbers RCX and R11) preserves all registers except the return value. So read-only input constraints are correct for registers other than R0, and not mentioning other registers is also correct.
Since you always want the value read from memory, you could use an "m"
constraint and include a ldr
in your inline asm. Then you wouldn't need the register int var asm("r0")
trick to avoid a wasted mov
for that operand. But generally it's best to just ask the compiler to get your inputs into the registers you want them, and only wrap a single special instruction like svc
in your asm
statement. (And tell the compiler about where outputs are produced.)
The mov r7, #1
might not always be needed either, which is why I used the register asm()
syntax for it, too. If gcc wants a 1
constant in a register somewhere else in a function, it can do it in r7
so it's already there for the ASM_EXIT.
Any time the first or last instructions of a GNU C inline asm statement are mov
instructions, there's probably a way to remove them with better constraints.