如何在ARM GCC内联汇编中将单个寄存器指定为约束条件?

5
在x86内联汇编中,我可以这样写:
asm ("cpuid"
            : "=a" (_eax),
              "=b" (_ebx),
              "=c" (_ecx),
              "=d" (_edx)
            : "a" (op));

在匹配约束中,我可以指定要使用哪个特定的寄存器(例如使用%eax =a),而不仅仅是写“=r”让编译器选择寄存器。
那么,在ARM汇编中我该怎么做呢?ARM GCC汇编手册 http://www.ethernut.de/en/documents/arm-inline-asm.html 指出,我可以使用约束“r”来使用R0-R15之间的通用寄存器之一,“w”表示使用VFP浮点寄存器S0-S31之一。
但是,我如何将操作数限制为例如s1或特定的通用寄存器?

在ARM上,了解特定操作数放置在哪个寄存器中将解决什么问题?除了预/后增量/减量加载/存储操作(存在特定约束条件)之外,ARM上哪些指令_隐含地_修改寄存器?在这方面,ARM和x86有很大的不同...在x86上必要的东西在ARM上并不需要。 - FrankH.
3
@FrankH. 系统调用?那正是我找到这个问题的方式。 - Duc
2个回答

6

我认为gcc for ARM不允许您使用约束条件来指定要使用的寄存器。然而,您可以使用显式寄存器变量来指定用于存储变量的寄存器:

register int my_variable asm("r0");

2

显式寄存器变量最小可运行示例

这是一个ARMv8 Linux C独立运行的hello world示例,演示了https://dev59.com/uFHTa4cB1Zd3GeqPOyCQ#3936064的一些反汇编分析:

main.c

Original Answer翻译成"最初的回答"

#include <inttypes.h>

void _start(void) {
    uint64_t exit_status;

    /* write */
    {
        char msg[] = "hello syscall v8\n";
        uint64_t syscall_return;
        register uint64_t x0 __asm__ ("x0") = 1; /* stdout */
        register char *x1 __asm__ ("x1") = msg;
        register uint64_t x2 __asm__ ("x2") = sizeof(msg);
        register uint64_t x8 __asm__ ("x8") = 64; /* syscall number */
        __asm__ __volatile__ (
            "svc 0;"
            : "+r" (x0)
            : "r" (x1), "r" (x2), "r" (x8)
            : "memory"
        );
        syscall_return = x0;
        exit_status = (syscall_return != sizeof(msg));
    }

    /* exit */
    {
        register uint64_t x0 __asm__ ("x0") = exit_status;
        register uint64_t x8 __asm__ ("x8") = 93;
        __asm__ __volatile__ (
            "svc 0;"
            : "+r" (x0)
            : "r" (x8)
            :
        );
    }
}

最初的回答:

GitHub上游

编译并运行:

sudo apt-get install qemu-user gcc-aarch64-linux-gnu
aarch64-linux-gnu-gcc -O3 -std=c99 -ggdb3 -march=armv8-a -pedantic -Wall -Wextra \
  -ffreestanding -nostdlib -static -o main.out main.c
qemu-aarch64 main.out

输出:

hello syscall v8

拆卸:

aarch64-linux-gnu-objdump -S main.out

输出:

main.out:     file format elf64-littleaarch64


Disassembly of section .text:

0000000000400110 <_start>:
void _start(void) {
    uint64_t exit_status;

    /* write */
    {
        char msg[] = "hello syscall v8\n";
  400110:   90000003    adrp    x3, 400000 <_start-0x110>
  400114:   91056063    add x3, x3, #0x158
void _start(void) {
  400118:   d10083ff    sub sp, sp, #0x20
        uint64_t syscall_return;
        register uint64_t x0 __asm__ ("x0") = 1; /* stdout */
  40011c:   d2800020    mov x0, #0x1                    // #1
        register char *x1 __asm__ ("x1") = msg;
  400120:   910023e1    add x1, sp, #0x8
        register uint64_t x2 __asm__ ("x2") = sizeof(msg);
  400124:   d2800242    mov x2, #0x12                   // #18
        char msg[] = "hello syscall v8\n";
  400128:   a9401464    ldp x4, x5, [x3]
        register uint64_t x8 __asm__ ("x8") = 64; /* syscall number */
  40012c:   d2800808    mov x8, #0x40                   // #64
        char msg[] = "hello syscall v8\n";
  400130:   79402063    ldrh    w3, [x3, #16]
  400134:   a90097e4    stp x4, x5, [sp, #8]
  400138:   790033e3    strh    w3, [sp, #24]
        __asm__ __volatile__ (
  40013c:   d4000001    svc #0x0
            : "+r" (x0)
            : "r" (x1), "r" (x2), "r" (x8)
            : "memory"
        );
        syscall_return = x0;
        exit_status = (syscall_return != sizeof(msg));
  400140:   eb02001f    cmp x0, x2
    }

    /* exit */
    {
        register uint64_t x0 __asm__ ("x0") = exit_status;
        register uint64_t x8 __asm__ ("x8") = 93;
  400144:   d2800ba8    mov x8, #0x5d                   // #93
        register uint64_t x0 __asm__ ("x0") = exit_status;
  400148:   9a9f07e0    cset    x0, ne  // ne = any
        __asm__ __volatile__ (
  40014c:   d4000001    svc #0x0
            : "+r" (x0)
            : "r" (x8)
            :
        );
    }
}
  400150:   910083ff    add sp, sp, #0x20
  400154:   d65f03c0    ret

不使用显式寄存器变量的尝试

主要是出于兴趣,我尝试在不使用寄存器变量的情况下达到相同的结果,但是我没有成功。

无论如何,代码会更加复杂,所以最好还是使用寄存器变量。

以下是我的最佳尝试:

main.c

(注:该段翻译仅供参考,具体语言表述可能需要根据上下文和实际情况做出调整)

#include <inttypes.h>

void _start(void) {
    uint64_t exit_status;

    /* write */
    {
        char msg[] = "hello syscall v8\n";
        uint64_t syscall_return;
        __asm__ (
            "mov x0, 1;" /* stdout */
            "mov x1, %[msg];"
            "mov x2, %[len];"
            "mov x8, 64;" /* syscall number */
            "svc 0;"
            "mov %[syscall_return], x0;"
            : [syscall_return] "=r" (syscall_return)
            : [msg] "p" (msg),
            [len] "i" (sizeof(msg))
            : "x0", "x1", "x2", "x8", "memory"
        );
        exit_status = (syscall_return != sizeof(msg));
    }

    /* exit */
    __asm__ (
        "mov x0, %[exit_status];"
        "mov x8, 93;" /* syscall number */
        "svc 0;"
        :
        : [exit_status] "r" (exit_status)
        : "x0", "x8"
    );
}

最初的回答:

GitHub上游

反汇编:

main.out:     file format elf64-littleaarch64


Disassembly of section .text:

0000000000400110 <_start>:
void _start(void) {
    uint64_t exit_status;

    /* write */
    {
        char msg[] = "hello syscall v8\n";
  400110:   90000000        adrp    x0, 400000 <_start-0x110>
  400114:   9105a000        add     x0, x0, #0x168
void _start(void) {
  400118:   d10083ff        sub     sp, sp, #0x20
        char msg[] = "hello syscall v8\n";
  40011c:   a9400c02        ldp     x2, x3, [x0]
  400120:   a9008fe2        stp     x2, x3, [sp, #8]
  400124:   79402000        ldrh    w0, [x0, #16]
        uint64_t syscall_return;
        __asm__ (
  400128:   910023e3        add     x3, sp, #0x8
        char msg[] = "hello syscall v8\n";
  40012c:   790033e0        strh    w0, [sp, #24]
        __asm__ (
  400130:   d2800020        mov     x0, #0x1                        // #1
  400134:   aa0303e1        mov     x1, x3
  400138:   d2800242        mov     x2, #0x12                       // #18
  40013c:   d2800808        mov     x8, #0x40                       // #64
  400140:   d4000001        svc     #0x0
  400144:   aa0003e3        mov     x3, x0
            : [syscall_return] "=r" (syscall_return)
            : [msg] "p" (msg),
            [len] "i" (sizeof(msg))
            : "x0", "x1", "x2", "x8", "memory"
        );
        exit_status = (syscall_return != sizeof(msg));
  400148:   f100487f        cmp     x3, #0x12
  40014c:   9a9f07e1        cset    x1, ne  // ne = any
    }

    /* exit */
    __asm__ (
  400150:   aa0103e0        mov     x0, x1
  400154:   d2800ba8        mov     x8, #0x5d                       // #93
  400158:   d4000001        svc     #0x0
        "svc 0;"
        :
        : [exit_status] "r" (exit_status)
        : "x0", "x8"
    );
}
  40015c:   910083ff        add     sp, sp, #0x20
  400160:   d65f03c0        ret

以下是这种方法的一些效率低下的原因:

  • write约束p需要使用一个中间寄存器x3进行addsp

  • 我不知道如何在没有额外的mov到输出寄存器的情况下获取syscall返回状态

  • exit状态通过x1多移动了一次。与寄存器变量相比,可以直接计算到x0中。

在Ubuntu 18.10, GCC 8.2.0和QEMU 2.12中进行了测试。


这是不安全的:没有任何东西告诉编译器 "r" (msg) 指向的内存也是汇编的输入。使用优化,gcc 将优化掉对堆栈数组的明显死存储。您需要一个 "memory" 占位符,或者一个虚拟输入,例如 "m"( *(char (*)[]) msg )(指向数组的解引用指针:即整个数组是内存操作数)。或者由于我们知道长度,因此为 "m"( *(char (*)[len]) msg ) - Peter Cordes
你不需要在asm语句中浪费指令来使用mov。接受的答案应该是有效的:register const char * foo asm("x1") = "blah blah";将使"r"(foo)选择x1。我不知道为什么你要给出这个糟糕的次优方式的大例子。 - Peter Cordes
@PeterCordes 啊,我没有想到会这样效率更低。我试图避免使用寄存器变量,因为它们很难看,但在这种情况下我看不到其他解决方案了。我会更新这个。 - Ciro Santilli
这正是register ... asm局部变量的用途。实际上,这是唯一受支持的用例。其他一些东西(比如在没有asm语句的情况下使用它们来读取寄存器)通常也可以工作,但最近文档的变更使得这种用法不被支持。您可以将变量声明+asm包装在较大函数的{}范围内,或者更常见的是内联包装函数的主体中。 - Peter Cordes
@PeterCordes 让我们再试一次 :-) - Ciro Santilli

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