我想在 Linux Android 设备上使用内联汇编在 ARM 中执行退出系统调用,并且我希望从内存中的某个位置读取退出值。
如果不给出这个额外的参数,调用的宏看起来像:
#define ASM_EXIT() __asm__("mov %r0, #1\n\t" \
"mov %r7, #1\n\t" \
"swi #0")
这个效果很好。 为了接受一个论点,我将其调整为:
#define ASM_EXIT(var) __asm__("mov %r0, %0\n\t" \
"mov %r7, #1\n\t" \
"swi #0" \
: \
: "r"(var))
我用以下方式称呼它:
#define GET_STATUS() (*(int*)(some_address)) //gets an integer from an address
ASM_EXIT(GET_STATUS());
无效的“asm”:操作数超出范围
我无法解释为什么会出现此错误,因为我在上面的代码片段中使用了一个输入变量 (%0/var)。另外,我尝试过使用常规变量,但仍然遇到相同的错误。
扩展asm语法需要编写
%%
才能在asm输出中获得单个%
。 例如对于 x86:
asm("inc %eax") // bad: undeclared clobber
asm("inc %%eax" ::: "eax"); // safe but still useless :P
%r7
将 r7
视为操作数。 正如评论者所指出的,只需省略 %
,因为 ARM 不需要它们,即使使用 GNU as
。
ARM 没有像 x86 那样在特定寄存器中请求操作数时受到特定寄存器限制。 (例如,x86 的 "a"
constraint只允许编译器选择(的底部部分)
rax
)。您可以使用 register int var asm ("r7")
强制 var 使用特定寄存器,然后使用
"r"
约束并假设它将位于该寄存器中。 (这实际上是register ... asm("regname")
支持的使用:https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html)这生成了高效的代码,避免了在 reg-reg 移动上浪费指令。
在 Godbolt 编译器资源管理器上查看它:
__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
不会返回,但对于其他情况,编译器了解 reg 写入非常重要。 (它假设所有寄存器在
asm
语句中保持其值,除非约束另有指示。)
我们不能在"r0"
上声明一个 clobber,因为这会与编译器选择 "r0"
作为
status
的输入操作数发生冲突。 如果第一个参数的类型不是 int
或类似类型,则可以有两个 register ... asm("r0")
变量,其中一个用于返回值,用作 "=r"(retval)
输出,另一个作为 "r"(arg0)
输入.大多数 ISA 上的大多数操作系统(包括除 x86-64 之外的大多数 ISA 上的 Linux,其中 syscall
本身会破坏 RCX 和 R11)的系统调用 ABI 保留除返回值之外的所有寄存器。 所以只读输入约束对于R0以外的寄存器是正确的,不提及其他寄存器也是正确的。
由于您始终希望从内存中读取值,因此您可以使用
"m"
约束并在内联汇编中包含 ldr
。 那么您就不需要
register int var asm("r0")
技巧来避免该操作数浪费 mov
。 但一般来说,最好只是要求编译器将您的输入获取到您想要的寄存器中,并且仅在您的 svc
语句中包装一条特殊指令,例如 asm
。 (并告诉编译器输出是在哪里产生的。) mov r7, #1
也可能并不总是需要,这就是为什么我也使用 register asm()
语法。 如果 gcc 想要在函数中其他位置的寄存器中使用
1
常量,它可以在 r7
中完成,因此它已经存在于 ASM_EXIT 中。
任何时候 GNU C 内联汇编语句的第一个或最后一个指令是
mov
指令,可能有一种方法可以通过更好的约束来删除它们。