ARM 内联 asm:使用从内存中读取的值退出系统调用

ARM inline asm: exit system call with value read from memory

问题

我想在 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());

错误

invalid 'asm': operand number out of range

我无法解释为什么会出现此错误,因为我在上述代码段 (%0/var) 中使用了一个输入变量。另外,我尝试使用常规变量,但仍然出现相同的错误。

Extended-asm syntax 需要写入 %% 才能在 asm 输出中获得单个 %。例如对于 x86:

asm("inc %eax")                // bad: undeclared clobber
asm("inc %%eax" ::: "eax");    // safe but still useless :P

%r7r7 视为操作数。正如评论者所指出的,只需省略 %s,因为 ARM 不需要它们,即使使用 GNU as.


不幸的是,doesn't seem to be a way to request input operands in specific registers on ARM,您可以使用 x86 的方式。 (例如,"a" 约束具体表示 eax)。

您可以使用 register int var asm ("r7") 强制 var 使用特定的寄存器,然后使用 "r" 约束并假设它将在该寄存器中。我不确定这是否总是安全的,或者是一个好主意,但即使在内联之后它似乎也能工作。 @Jeremy 评论说这项技术是 GCC 团队推荐的。

我确实生成了一些高效的代码,这避免了在 reg-reg 移动上浪费指令:

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), "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.

由于您总是希望从内存中读取值,因此您可以使用 "m" 约束并在内联汇编中包含 ldr。那么你就不需要 register int var asm("r0") 技巧来避免为该操作数浪费 mov

可能并不总是需要 mov r7, #1,这也是我使用 register asm() 语法的原因。如果 gcc 想要在函数中其他地方的寄存器中使用 1 常量,它可以在 r7 中完成,因此它已经存在于 ASM_EXIT.


任何时候 GNU C 内联 asm 语句的第一条或最后一条指令是 mov 条指令,可能有一种方法可以通过更好的约束来删除它们。