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

3

问题

我想在Linux Android设备上使用内联汇编执行ARM中的exit系统调用,并且希望从内存中的位置读取退出值。

示例

如果不提供这个额外的参数,调用的宏看起来像:

#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)。而且,我已经尝试使用常规变量,但仍然出现了相同的错误。


GET_STATUS 确实有 // 吗?还是你添加了它来澄清问题?如果将其删除会发生什么?另外,尝试使用 ASM_EXIT(1) 怎么样?最后,使用 -E 编译可能会很有用。 - David Wohlferd
1
“%r7” - 好吧,你没有八个操作数用于扩展汇编语句,对吧?在简单汇编语句的情况下(编译器不尝试解析内容的情况下),似乎汇编程序会忽略虚假的语法并组装出你认为正确的结果。 - Notlikethat
1
对于GCC ARM汇编,您不需要在寄存器名称前加“%”前缀。 - Jeremy
@DavidWohlferd 不,它不会。为了澄清。 - Paschalis
这是一个使用寄存器变量的最小工作示例:https://dev59.com/uFHTa4cB1Zd3GeqPOyCQ#54845046 - Ciro Santilli
1个回答

5

Extended-asm syntax需要在汇编输出中写入%%以获取单个%。例如,对于x86:

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

%r7被视为操作数编号。正如评论者所指出的,只需省略%,因为在ARM中不需要使用它们,即使使用GNU as也是如此。


ARM在请求特定寄存器的操作数时,没有像x86那样有特定的寄存器约束。例如,x86的" a "约束只允许编译器选择rax的底部部分。
您可以使用" register int var asm ("r7") "来强制变量使用特定的寄存器,然后使用" r "约束并假设它将在该寄存器中。(事实上,这是唯一支持的" register ... asm("regname") "的用法:https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html)
这生成了高效的代码,避免了在寄存器间进行不必要的传输指令。
在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), "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内联汇编语句的第一条或最后一条指令是mov指令时,可能有更好的约束条件可以将其移除。


1
据我所知,在ARM上,您可以使用“register int var asm(“r7”)”来强制变量使用特定的寄存器,这是唯一的方法(无需额外的mov)。这个方法是由GCC团队推荐给我的。我不知道为什么你不能像在x86上那样直接给出寄存器名称。 - Jeremy
1
如果存在可写内存映射文件或进程是多线程的,则可能需要使用“memory”破坏来确保编译器不会将内存写入重新排序到退出系统调用之后。 - Timothy Baldwin
@TimothyBaldwin:谢谢,非常好的建议。已经修复。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接