【问题标题】:Constraining r10 register in gcc inline x86_64 assembly在 gcc 内联 x86_64 程序集中约束 r10 寄存器
【发布时间】:2013-04-06 12:56:19
【问题描述】:

我正在尝试编写一个非常轻量级的 libc 替换库,以便更好地理解内核 - 应用程序接口。第一个任务显然是让一些系统调用包装器到位。我已经成功地让 1 到 3 个参数包装器工作,但我正在努力处理 4 个参数变量。这是我的出发点:

long _syscall4(long type, long a1, long a2, long a3, long a4)
{
    long ret;
    asm
    (
        "syscall"
        : "=a"(ret)    // return value
        : "a"(type), "D"(a1), "S"(a2), "d"(a3), "r10"(a4)
        : "c", "r11", "memory"  // some syscalls read or write memory
                   // the syscall insn instruction itself destroys RCX and R11
    );
    return ret;
}

(编者注:这是安全且可用的,也是一个很好的例子,在应用答案的方式来处理r10之后。MUSL libc 有一些类似的宏。)

编译器给我以下错误:

error: matching constraint references invalid operand number

我的 _syscall3 函数工作正常,但不使用 r10 或有一个 clobber 列表。

(编者注:没有clobber列表是不安全的:您需要告诉编译器RCX和R11被覆盖,并且“内存”应该在系统调用之前同步,可以读取或写入内存。如果您想为特定的系统调用编写特定的包装器,您可以选择性地省略"memory" clobbers,或者根据哪些参数是该系统调用的指针来使用虚拟内存操作数。

如果这个_syscall4 函数不能内联,那么寄存器和"memory" clobbers 在实践中不会造成任何问题,但是您应该使它们能够内联;与调用非内联包装函数相比,内联此系统调用在调用站点需要的代码更少。)

【问题讨论】:

  • 这应该在标题中(使用inline 关键字),以便它可以内联,从而节省调用站点将调用号放入 RDI 并将 args 放入其他 arg 传递的开销regs,然后被叫方在将索书号复制到 RAX 后将它们全部复制一份。我编辑添加了一个"memory" clobber 以确保安全。 (非内联函数调用已经隐含地成为优化器的内存屏障。)

标签: c gcc x86-64 system-calls inline-assembly


【解决方案1】:

寄存器没有限制:%r8 .. %15。然而,更新的(如 gcc-4.x)应该接受:

register long r10 asm("r10") = a4;

然后使用输入约束:"r" (r10) 作为您的 asm 语句。
https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html


请注意,强制为扩展 asm 选择 "r" 约束是 GCC 为 register-asm locals 保证的唯一行为。 register void *rsp asm("rsp");void *stack_pointer = rsp; 之类的东西有时确实有效,但保证并且不再推荐。


您将希望您的系统调用包装器 asm 语句为 volatile 并具有 "memory" clobber,除非您使用虚拟内存输入为特定系统调用编写特定包装器以了解哪些 args 是指针或输出(根据How can I indicate that the memory *pointed* to by an inline ASM argument may be used?

它需要volatile 因为执行write(1, buf, 16) 应该打印两次缓冲区,而不仅仅是CSE 返回值!系统调用通常不是其输入的纯函数,因此您需要volatile

(某些特定的系统调用包装器,例如 getpid 可能是非易失性的,因为它们每次都会返回相同的东西,除非您也使用 fork。但如果通过 VDSO 完成 getpid 效率更高,所以它不会如果您在 Linux 上,则不必首先进入内核,因此,如果您正在为 getpidclock_gettime 制作自定义包装器,您可能首先不想要 syscall。请参阅The Definitive Guide to Linux System Calls)

需要"memory" clobber,因为寄存器中的指针确实暗示指向的内存也是输入或输出。存储到仅由write 系统调用读取的缓冲区需要被优化为死存储。或者对于munmap,编译器最好在内存未映射之前完成所有加载/存储。一些系统调用不接受任何指针输入,也不需要"memory",但通用包装器必须做出最坏情况的假设。

register ... asm("r10")通常需要 asm volatile"memory" clobbers,但系统调用包装器需要。

【讨论】:

【解决方案2】:

大概是因为没有指令对r10 寄存器有特定要求,gcc 人员没有为它创建约束(假设约束主要用于机器描述)。如果您坚持使用内联 asm,我认为您没有比使用通用 "r"(或 "m")约束并自己移动到 r10(并将其添加到 clobber 列表)更好的方法了。

【讨论】:

    猜你喜欢
    • 2013-06-14
    • 2019-01-28
    • 2012-11-12
    • 2011-04-23
    • 2015-12-23
    • 2012-08-26
    • 1970-01-01
    • 2014-06-27
    • 1970-01-01
    相关资源
    最近更新 更多