ARM 内联汇编:退出系统调用并从内存中读取值

问题描述 投票:0回答:1

问题

我想在 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)。另外,我尝试过使用常规变量,但仍然遇到相同的错误。

linux assembly arm system-calls inline-assembly
1个回答
5
投票

扩展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

指令,可能有一种方法可以通过更好的约束来删除它们。


最新问题
© www.soinside.com 2019 - 2025. All rights reserved.