Mat*_*ira 7 operating-system x86-64 system-calls abi linux-kernel
我正在用C编写一个独立程序,它只依赖于Linux内核.
我研究了相关手册 页,得知X86-64上的Linux系统调用入口点接收通过七个寄存器的系统调用号和六个参数rax,rdi,rsi,rdx,r10,r8,和r9.
这是否意味着 每个系统调用都接受六个参数?
我研究了几个libc实现的源代码,以了解它们如何执行系统调用.有趣的是,musl包含两种不同的系统调用方法:
此汇编源文件定义了一个__syscall函数,该函数将系统调用号和正好六个参数移动到ABI中定义的寄存器.该函数的通用名称暗示它可以与任何系统调用一起使用,尽管它总是将六个参数传递给内核.
此C头文件定义了七个单独的__syscallN函数,并N指定了它们的arity.这表明只传递系统调用所需的确切数量的参数的好处超过了拥有和维护七个几乎相同的函数的成本.
所以我自己尝试了一下:
long
system_call(long number,
long _1, long _2, long _3, long _4, long _5, long _6)
{
long value;
register long r10 __asm__ ("r10") = _4;
register long r8 __asm__ ("r8") = _5;
register long r9 __asm__ ("r9") = _6;
__asm__ volatile ( "syscall"
: "=a" (value)
: "a" (number), "D" (_1), "S" (_2), "d" (_3), "r" (r10), "r" (r8), "r" (r9)
: "rcx", "r11", "cc", "memory");
return value;
}
int main(void) {
static const char message[] = "It works!" "\n";
/* system_call(write, standard_output, ...); */
system_call(1, 1, message, sizeof message, 0, 0, 0);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我运行了这个程序并验证它是否写入It works!\n标准输出.这给我留下了以下问题:
0好吗?系统调用接受最多6个参数,在寄存器中传递(与SysV x64 C ABI几乎相同的寄存器,r10替换rcx但在系统调用情况下它们被保存),并且"额外"参数被简单地忽略.
以下是您的问题的一些具体答案.
这src/internal/x86_64/syscall.s只是一个"thunk",它将所有参数都转移到了正确的位置.也就是说,它从一个带有系统调用号和另外6个参数的C-ABI函数转换为具有相同6个参数和系统调用号的"系统调用ABI"函数rax.它对任意数量的参数都"正常" - 如果不使用这些参数,系统调用将简单地忽略额外的寄存器移动.
由于在C-ABI所有的参数寄存器被认为是从头开始(即调用者保存),重挫,如果你以为这是他们无害的__syscall方法是从C.称为其实内核使有关破坏的寄存器更强的保证,只有重挫rcx并r11因此假设C调用约定是安全的但是悲观.特别是,__syscall这里实现的代码调用将根据C ABI不必要地保存任何参数和临时寄存器,尽管内核承诺保留它们.
该arch/x86_64/syscall_arch.h文件几乎相同,但在C头文件中.在这里,您需要所有七个版本(对于零到六个参数),因为如果您使用错误数量的参数调用函数,现代C编译器将发出警告或错误.因此,在装配案例中没有"一个函数来统治它们"的实际选择.这样做的优点是可以减少少于6个参数的系统调用.
您列出的问题,已回答:
因为调用约定主要是基于寄存器和调用者清理.在这种情况下(包括在C ABI中)总是可以传递更多的参数,而被调用者只会忽略其他参数.由于该syscall机制在C和.asm级别是通用的,因此编译器无法确保传递正确数量的参数 - 您需要传递正确的系统调用ID 和正确数量的参数.如果你传递的更少,内核将看到垃圾,如果你传递更多,它们将被忽略.
是的,确定 - 因为整个syscall机制是进入内核的"通用门".99%的时间你不打算使用它:glibc在C ABI包装器中使用正确的签名包装绝大多数有趣的系统调用,这样你就不用担心了.这些是系统调用访问安全发生的方式.
你没有把它们设置成任何东西.如果您使用C原型arch/x86_64/syscall_arch.h,编译器会为您处理它(它不会将它们设置为任何东西),如果您正在编写自己的asm,则不要将它们设置为任何东西(并且您应该假设它们被破坏了)在系统调用之后).
它是免费使用它想要的所有寄存器,但会坚持到内核调用约定这是X86-64上比其他所有的寄存器rax,rcx并且r11被保留(这就是为什么你看到的rcx和r11在在C内联汇编的名单乱码).
是的,但差异非常小,因为reg-reg mov指令通常具有零延迟并且在最近的英特尔架构上具有高吞吐量(最多4个/周期).因此,移动额外的6个寄存器可能需要1.5个周期来进行系统调用,即使它什么也不做,通常需要至少50个周期.所以影响很小,但可能是可测量的(如果你非常仔细地测量!).
我不确定你的意思是什么,但其他寄存器可以像所有GP寄存器一样使用,如果内核想要保留它们的值(例如,通过将push它们放在堆栈上,然后再将pop它们放在后面).
| 归档时间: |
|
| 查看次数: |
833 次 |
| 最近记录: |