我正在尝试编写一个重量很轻的libc替换库,以便可以更好地了解内核-应用程序接口(interface)。首要任务显然是准备好一些系统调用包装程序。我已经成功地使用了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或没有内容 list 。(编者注:没有破坏列表是不安全的:您需要告诉编译器RCX和R11被覆盖,并且“内存”应该在系统调用之前处于同步状态,这可能会读取或写入内存。如果您想为特定的系统调用编写特定的包装器,可以有选择地省略
"memory"
修饰符,或根据参数是该系统调用的指针使用虚拟内存操作数。如果此
_syscall4
函数不能内联,则register和"memory"
修饰符实际上不会引起任何问题,但是您应该使它们能够内联;与调用非内联包装函数相比,内联此系统调用将在调用站点上花费更少的代码。) 最佳答案
寄存器没有任何限制:%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本地用户的唯一行为。诸如register void *rsp asm("rsp");
和void *stack_pointer = rsp;
之类的东西有时可以工作,但不能保证,不建议再使用。您将希望您的syscall包装器
asm
语句为volatile
并具有"memory"
Clobber,除非您使用虚拟内存输入或输出(针对How can I indicate that the memory *pointed* to by an inline ASM argument may be used?)为特定的系统调用编写特定的包装器以知道哪些args是指针它需要
volatile
,因为执行write(1, buf, 16)
应该将缓冲区打印两次,而不仅仅是CSE返回值!系统调用通常不是其输入的Pure函数,因此您需要volatile
。(某些特定的系统调用包装器,例如
getpid
可能是非 volatile 的,因为它们每次都会返回相同的东西,除非您也使用fork。但是getpid
如果通过VDSO完成则效率更高,因此不必进入内核首先,如果您使用的是Linux,那么,如果要为getpid
和clock_gettime
创建自定义包装,则您可能首先不希望syscall
。请参见The Definitive Guide to Linux System Calls)之所以需要
"memory"
破坏器,是因为寄存器中的指针并不意味着指向的存储器也是输入或输出。只需要通过write
系统调用读取到缓冲区的存储就不需要像死存储一样进行优化。或者对于munmap
,编译器最好在取消映射内存之前完成所有加载/存储。一些系统调用不接受任何指针输入,也不需要"memory"
,但是通用包装必须做出最坏的假设。register ... asm("r10")
通常不需要asm volatile
或"memory"
修饰符,但是syscall包装器需要。关于在gcc内联x86_64程序集中约束r10寄存器,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15997759/