正如该问题的标题中所述,当我出于临时原因修改asm语句中的某些寄存器时,在clobber和虚拟输出之间哪个选项更好?
例如,我在link中实现了交换功能的两个版本,发现两个版本生成相同数量的输出指令。
我应该使用哪个版本?我是否应该将其与虚拟输出一起使用,以允许编译器选择可以尽可能优化整个功能的寄存器?
如果答案是肯定的,那我什么时候应该使用清除清单?当一条指令要求您将其操作数加载到特定寄存器时,是否可以使用Clobber列表?如syscall指令要求其参数应位于寄存器rdi rsi rdx r10 r8 r9?
答案 0 :(得分:2)
您通常应该让编译器使用带有任何必需约束 1 的早期漏洞虚拟输出来为您选择寄存器。这样可以灵活地为该功能进行寄存器分配。
1 例如您可以使用=&Q
获取具有AH / BH / CH / DH的RAX / RBX / RCX / RDX:寄存器之一。如果您想使用movzbl %h[input], %[high_byte]
解压缩8位字段,请
; movzbl %b[input], %[low_byte]
; shr $16, %[input]
,您需要一个寄存器,该寄存器具有别名为高8寄存器的第二个8位块。
出于好奇,当我们考虑amd64的调用约定时,可以在函数内部自由使用一些寄存器。我们可以仅使用asm语句中的那些寄存器来实现某些功能。为什么允许编译器选择要使用的寄存器比上面提到的更好?
由于函数可以内联,可能插入调用其他函数的循环中,因此编译器希望将其输入保留在调用保留的寄存器中。如果您要编写一个独立的函数,编译器始终必须调用,从内联asm而不是独立调用中获得的全部是编译器处理调用约定差异和C ++名称处理。
或者周围的代码使用了一些需要固定寄存器的指令,例如cl
用于移位计数或RDX:RAX用于div
。
什么时候应该使用清单清单? ... 例如syscall指令要求其参数应该位于寄存器rdi rsi rdx r10 r8 r9 ??
通常,您将改用输入约束,因此,仅syscall
指令本身位于内联汇编中。但是syscall
(指令本身)掩盖了RCX和R11,因此使用它进行的系统调用不可避免地会破坏用户空间的RCX和R11。除非使用返回地址(RCX)或RFLAGS(R11),否则没有必要使用虚拟输出。所以是的,在这里,clippers很有用。
// the compiler will emit all the necessary MOV instructions
#include <stddef.h>
#include <asm/unistd.h>
// the compiler will emit all the necessary MOV instructions
//static inline
size_t sys_write(int fd, const char *buf, size_t len) {
size_t retval;
asm volatile("syscall"
: "=a"(retval) // EDI RSI RDX
: "a"(__NR_write), "D"(fd), "S"(buf), "d"(len)
, "m"(*(char (*)[len]) buf) // dummy memory input: the asm statement reads this memory
: "rcx", "r11" // clobbered by syscall
// , "memory" // would be needed if we didn't use a dummy memory input
);
return retval;
}
此非内联版本的编译如下(使用gcc -O3
on the Godbolt compiler explorer),因为函数调用约定几乎与系统调用约定匹配:
sys_write(int, char const*, unsigned long):
movl $1, %eax
syscall
ret
在任何输入寄存器上使用Clobber并将mov
放在asm内真是很愚蠢:
size_t dumb_sys_write(int fd, const char *buf, size_t len) {
size_t retval;
asm volatile(
"mov %[fd], %%edi\n\t"
"mov %[buf], %%rsi\n\t"
"mov %[len], %%rdx\n\t"
"syscall"
: "=a"(retval) // EDI RSI RDX
: "a"(__NR_write), [fd]"r"(fd), [buf]"r"(buf), [len]"r"(len)
, "m"(*(char (*)[len]) buf) // dummy memory input: the asm statement reads this memory
: "rdi", "rsi", "rdx", "rcx", "r11"
// , "memory" // would be needed if we didn't use a dummy memory input
);
// if(retval > -4096ULL) errno = -retval;
return retval;
}
dumb_sys_write(int, char const*, unsigned long):
movl %edi, %r9d
movq %rsi, %r8
movq %rdx, %r10
movl $1, %eax # compiler generated before this
# from inline asm
mov %r9d, %edi
mov %r8, %rsi
mov %r10, %rdx
syscall
# end of inline asm
ret
此外,您不会让编译器利用syscall
不会破坏其任何输入寄存器这一事实。编译器可能仍希望在寄存器中使用len
,并且使用纯输入约束条件使它知道之后该值仍然存在。
如果您正在使用任何隐式使用某些寄存器的指令,则也可能使用clobbers,但是这些指令的输入或输出都不是asm语句的直接输入或输出。但是,除非您在嵌入式asm中编写整个循环或大量代码,否则这种情况很少见。
或者,如果您要包装call
指令,也可以。 (很难安全地执行此操作,尤其是由于存在红色区域,但是人们确实会尝试这样做)。您不必选择要注册哪个代码伪造者,因此只需将其告知编译器即可。