以下链接说明了UNIX(BSD风格)和Linux的x86-32系统调用约定:


http://www.int80h.org/bsdasm/#system-calls
http://www.freebsd.org/doc/zh-CN/books/developers-handbook/x86-system-calls.html

但是UNIX和Linux上的x86-64系统调用约定是什么Linux?

评论

Unix调用约定没有“标准”。对于Linux,当然可以,但是我确定Solaris,OpenBSD,Linux和Minix可能具有至少至少略有不同的调用约定,并且它们都是unix。

这不是完全正确的-大多数机器类型都可以使用一组UNIX ABI,这使C编译器可以实现互操作性。 C ++编译器存在更大的问题。

你们俩都是正确的。我正在寻找FreeBSD和Linux。

如果答案包含有关在整个系统调用中保留哪些寄存器的信息,我将不胜感激。当然,堆栈指针是(除非在__NR_clone调用中以受控方式更改),但是它们的其他指针是吗?

@AlbertvanderHorst:是的,我刚刚用32位的详细信息更新了Wiki答案。 64位已经很准确了:由于sysret的工作方式,rcx和r11被销毁了,而rax被返回值所取代。所有其他寄存器都保留在amd64上。

#1 楼

进一步阅读此处的任何主题:《 Linux系统调用权威指南》

我在Linux上使用GNU汇编器(gas)进行了验证。
内核接口
x86-32 aka i386 Linux系统调用约定:
在x86-32中,使用寄存器传递Linux系统调用的参数。 syscall_number的%eax。 %ebx,%ecx,%edx,%esi,%edi,%ebp用于将6个参数传递给系统调用。
返回值在%eax中。所有其他寄存器(包括EFLAGS)都在int %ebxx80中保留。
我从Linux Assembly Tutorial中摘录了以下片段,但对此感到怀疑。如果可以显示一个示例,那就太好了。

如果有六个以上参数,
int 0x80必须包含内存
参数列表所在的位置
/>已存储-但不必担心
,因为您不太可能使用具有六个以上自变量的syscall。

示例和更多阅读内容,请参见http://www.int80h.org/bsdasm/#alternate-calling-convention。使用sysenter的i386 Linux的Hello World的另一个示例:您好,使用Linux系统调用的汇编语言世界?
有一种更快的方式进行32位系统调用:使用sysenter。内核将内存页面映射到每个进程(vDSO)中,int sysenterx80舞的用户空间端必须与内核合作才能找到返回地址。 Arg到寄存器的映射与sysenter相同。通常,您应该调用vDSO,而不是直接使用int syscallx80。 (有关链接和调用vDSO的信息,以及有关-errno的更多信息,以及与系统调用有关的其他信息,请参见《 Linux系统调用权威指南》。)
x86-32 [免费|打开|网络| DragonFly] BSD UNIX系统调用约定:
参数在堆栈上传递。将参数(最后一个参数先被压入)推入堆栈。然后再推送一个额外的32位虚拟数据(它实际上不是虚拟数据。有关更多信息,请参见下面的链接),然后给出系统调用指令int callx80
http://www.int80h.org/bsdasm/# default-calling-convention

x86-64 Linux系统调用约定:
(注意:x86-64 Mac OS X与Linux类似,但有所不同。TODO:检查* BSD的作用)
请参阅“系统V应用程序二进制接口AMD64体系结构处理器增补”中的“ A.2 AMD64 Linux内核约定”部分。可从ABI维护人员的存储库中的此页面链接找到最新版本的i386和x86-64 System V psABI。 (有关最新的ABI链接和有关x86 asm的许多其他知识,请参阅x86标签Wiki。)
这是此部分的摘录:


用户级应用程序用作整数寄存器,用于传递序列
%rdi,%rsi,%rdx,%rcx,
%r8和%r9。内核接口使用%rdi,%rsi,%rdx,%r10,%r8和%r9。

通过%esp指令进行系统调用。该副本%rcx和%r11以及%rax返回值,但保留了其他寄存器。
必须在%rax寄存器中传递syscall的编号。
系统调用仅限于六个参数,没有参数直接传递到堆栈上。
从系统调用返回,寄存器%rax包含系统调用的结果。范围在-4095到-1之间的值表示错误
,是call
仅将INTEGER类或MEMORY类的值传递给内核。


请记住,这是从ABI的特定于Linux的附录中获得的,即使对于Linux来说,它的信息内容也不是规范性的。 (但实际上是准确的。)
此32位double ABI可用于64位代码(但强烈建议不要使用)。如果以64位代码使用32位int 0x80 Linux ABI,会发生什么情况?它仍然将其输入截断为32位,因此不适合指针,并且将r8-r11归零。
用户界面:函数调用
x86-32函数调用约定:
在x86-在堆栈上传递了32个参数。将最后一个参数首先压入堆栈,直到完成所有参数,然后执行%rdi, %rsi, %rdx, %rcx, %r8 and %r9指令。这用于在Linux上从程序集调用C库(libc)函数。
现代版本的i386 System V ABI(在Linux上使用)要求call的16字节对齐在%rsp之前,例如x86-64系统V ABI始终要求。被调用者被允许假定并使用SSE 16字节加载/存储在未对齐时发生故障。但是从历史上看,Linux只需要4字节的堆栈对齐,因此即使对于8字节的call或类似的东西,它也需要花费额外的工作来保留自然对齐的空间。
其他一些现代的32位系统仍然不需要更多空间小于4字节堆栈对齐。

x86-64 System V用户空间函数调用约定:
x86-64 System V在寄存器中传递args,这比i386 System V的堆栈args效率更高。惯例。它避免了等待时间和将args存储到内存(高速缓存)然后再将它们重新加载到被调用方的额外指令。因为有更多可用的寄存器,所以这种方法行之有效,并且对于延迟和无序执行至关重要的现代高性能CPU更好。 (i386 ABI很旧)。
在这种新机制中:首先将参数划分为类。每个参数的类决定了将其传递给被调用函数的方式。
有关完整的信息,请参见:System V应用程序二进制接口AMD64体系结构处理器增刊中的“ 3.2函数调用序列”,部分内容为:

对参数进行分类后,将按以下顺序(按从左到右的顺序分配寄存器)以进行传递:

如果类为MEMORY,则在堆栈上传递参数。 />如果类是INTEGER,则使用序列
%rdi,%rsi,%rdx,%rcx,%r8和%r9的下一个可用寄存器


因此,printf是用于将整数/指针(即INTEGER类)参数传递给汇编中任何libc函数的寄存器。 %rdi用于第一个INTEGER参数。 %rsi代表第二,%rdx代表第三,依此类推。然后应给出%al指令。执行rdx:rax时,堆栈(q4312079q)必须对齐16B。
如果有6个以上INTEGER参数,则将第7个INTEGER参数及更高版本传递给堆栈。 (调用程序弹出窗口,与x86-32相同。)
前8个浮点参数在%xmm0-7中传递,随后在堆栈中传递。没有保留呼叫的向量寄存器。 (混合了FP和整数参数的函数最多可以有8个以上的寄存器参数。)
可变函数(例如q4312079q)始终需要q4312079q = FP寄存器args的数量。
有一些规则何时将结构打包到寄存器中(返回时为q4312079q)与内存中的大小。有关详细信息,请参见ABI,并检查编译器输出,以确保您的代码与编译器有关如何传递/返回某些内容的协议。

请注意,Windows x64函数调用约定与x86-具有多个重大区别。 64系统V,例如必须由调用方保留的阴影空间(而不是红色区域)和保留呼叫的xmm6-xmm15。 arg进入哪个寄存器的规则也非常不同。

评论


在linux 32中,“保留除ax bx cd dx si di bp之外的所有寄存器”。我想不到...

–阿尔伯特·范·德·霍斯特
16 Mar 16 '16 at 2:41

@Nicolás:调用方清理堆栈。我用有关函数调用约定的更多详细信息更新了答案。

– Peter Cordes
17年3月3日,下午3:05

如果您在64位代码中使用Linux的int 0x80 ABI,则会发生以下情况:stackoverflow.com/questions/46087730/…。它将r8-r11归零,并且工作原理与在32位进程中运行时完全相同。在该问答中,我有一个示例说明它正常工作,或者由于截断指针而失败。而且,我还深入研究了内核源代码,以显示其为何如此运行。

– Peter Cordes
17年7月7日在4:38

@EvanCarroll:该代码段(带引号的文本)在“ Linux汇编教程”中特定于4.3节的链接处

– Michael Petch
18-10-26在0:50

@ r0ei与64位寄存器相同。它是ax而不是rax,它是bx而不是rbx,依此类推。除非您有16位调用约定,否则还有其他传递参数的方法。

– JCWasmx86
9月15日上午11:05

#2 楼

也许您正在寻找x86_64 ABI?



www.x86-64.org/documentation/abi.pdf(404 at 2018-11-24)

www.x86-64.org/documentation/abi.pdf(通过Wayback Machine,在2018-11-24)

x86-64 System V ABI记录在哪里? -https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI保持最新状态(由ABI维护者之一的HJ Lu提供),其中包含指向当前最新版本PDF的链接。

如果这不完全是您想要的,请在您喜欢的搜索引擎中使用'x86_64 abi'查找替代引用。

评论


实际上,我只想要系统调用约定。 esp for UNIX(FreeBSD)

–爪
2010-3-29在8:25

@claws:系统调用约定是ABI的一部分。

–乔纳森·莱弗勒(Jonathan Leffler)
2010-3-29在13:23

是的我已经进入每个操作系统的内核开发中心,并询问了他们。他们告诉我调查源并找出原因。我不了解没有记录的东西,他们如何才能开始开发?因此,我从收集到的信息中添加了一个答案,希望其他人填写其余详细信息。

–爪
10年3月29日在13:43



@JonathanLeffler链接现在似乎不起作用。如果您在访问链接时也遇到问题,可以更新一下吗?

–阿杰(Ajay Brahmakshatriya)
18年11月25日在6:46

@AjayBrahmakshatriya:感谢大家的注意;我已经添加了指向Wayback Machine记录的链接。整个x86-64.org网站没有任何数据响应。

–乔纳森·莱弗勒(Jonathan Leffler)
18年11月25日在7:56

#3 楼

调用约定定义了在调用或被其他程序调用时如何在寄存器中传递参数。这些约定的最佳来源是为每种硬件定义的ABI标准的形式。为了便于编译,用户空间和内核程序也使用相同的ABI。 Linux / Freebsd对于x86-64遵循相同的ABI,对于32位遵循另一组。但是Windows的x86-64 ABI与Linux / FreeBSD不同。通常,ABI不会区分系统调用与普通的“函数调用”。
即,这是x86_64调用约定的一个特定示例,对于Linux用户空间和内核都是相同的:http://eli.thegreenplace。 net / 2011/09/06 / stack-frame-layout-on-x86-64 /(请注​​意参数的顺序a,b,c,d,e,f):



性能是这些ABI的原因之一(例如,通过寄存器传递参数而不是保存到内存堆栈中)

对于ARM,有各种ABI:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.swdev.abi/index.html

https:// developer。 apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/iPhoneOSABIReference.pdf

ARM64约定:

http://infocenter.arm.com/help/ topic / com.arm.doc.ihi0055b / IHI0055B_aapcs64.pdf

对于PowerPC上的Linux:

http://refspecs.freestandards.org/elf/elfspec_ppc.pdf

http://www.0x04.net/ doc / elf / psABI-ppc64.pdf

对于嵌入式,有PPC EABI:

http://www.freescale.com/files/32bit/doc/ app_note / PPCEABI.pdf

本文档很好地概述了所有不同的约定:

http://www.agner.org/optimize/calling_conventions.pdf

评论


完全不重要。如果问题的发布者与一般的ABI转换相同,则不会要求Linux中的64位syscall调用约定。

–阿尔伯特·范·德·霍斯特
16年1月22日在13:04

#4 楼

Linux内核5.0源代码注释
我知道x86细节在arch/x86下,而syscall东西在arch/x86/entry下。因此,该目录中的快速git grep rdi会将我引导至arch / x86 / entry / entry_64.S:
/*
 * 64-bit SYSCALL instruction entry. Up to 6 arguments in registers.
 *
 * This is the only entry point used for 64-bit system calls.  The
 * hardware interface is reasonably well designed and the register to
 * argument mapping Linux uses fits well with the registers that are
 * available when SYSCALL is used.
 *
 * SYSCALL instructions can be found inlined in libc implementations as
 * well as some other programs and libraries.  There are also a handful
 * of SYSCALL instructions in the vDSO used, for example, as a
 * clock_gettimeofday fallback.
 *
 * 64-bit SYSCALL saves rip to rcx, clears rflags.RF, then saves rflags to r11,
 * then loads new ss, cs, and rip from previously programmed MSRs.
 * rflags gets masked by a value from another MSR (so CLD and CLAC
 * are not needed). SYSCALL does not save anything on the stack
 * and does not change rsp.
 *
 * Registers on entry:
 * rax  system call number
 * rcx  return address
 * r11  saved rflags (note: r11 is callee-clobbered register in C ABI)
 * rdi  arg0
 * rsi  arg1
 * rdx  arg2
 * r10  arg3 (needs to be moved to rcx to conform to C ABI)
 * r8   arg4
 * r9   arg5
 * (note: r12-r15, rbp, rbx are callee-preserved in C ABI)
 *
 * Only called from user space.
 *
 * When user can change pt_regs->foo always force IRET. That is because
 * it deals with uncanonical addresses better. SYSRET has trouble
 * with them due to bugs in both AMD and Intel CPUs.
 */

,对于arch / x86 / entry / entry_32.S,则为32位:
/*
 * 32-bit SYSENTER entry.
 *
 * 32-bit system calls through the vDSO's __kernel_vsyscall enter here
 * if X86_FEATURE_SEP is available.  This is the preferred system call
 * entry on 32-bit systems.
 *
 * The SYSENTER instruction, in principle, should *only* occur in the
 * vDSO.  In practice, a small number of Android devices were shipped
 * with a copy of Bionic that inlined a SYSENTER instruction.  This
 * never happened in any of Google's Bionic versions -- it only happened
 * in a narrow range of Intel-provided versions.
 *
 * SYSENTER loads SS, ESP, CS, and EIP from previously programmed MSRs.
 * IF and VM in RFLAGS are cleared (IOW: interrupts are off).
 * SYSENTER does not save anything on the stack,
 * and does not save old EIP (!!!), ESP, or EFLAGS.
 *
 * To avoid losing track of EFLAGS.VM (and thus potentially corrupting
 * user and/or vm86 state), we explicitly disable the SYSENTER
 * instruction in vm86 mode by reprogramming the MSRs.
 *
 * Arguments:
 * eax  system call number
 * ebx  arg1
 * ecx  arg2
 * edx  arg3
 * esi  arg4
 * edi  arg5
 * ebp  user stack
 * 0(%ebp) arg6
 */

glibc 2.29 Linux x86_64系统调用实现
现在让我们通过查看主要的libc实现并看看它们在做什么来作弊。
有什么比研究我正在使用的glibc更好的了?现在当我写这个答案? :-)
glibc 2.29在sysdeps/unix/sysv/linux/x86_64/sysdep.h处定义了x86_64系统调用,其中包含一些有趣的代码,例如:
/* The Linux/x86-64 kernel expects the system call parameters in
   registers according to the following table:

    syscall number  rax
    arg 1       rdi
    arg 2       rsi
    arg 3       rdx
    arg 4       r10
    arg 5       r8
    arg 6       r9

    The Linux kernel uses and destroys internally these registers:
    return address from
    syscall     rcx
    eflags from syscall r11

    Normal function call, including calls to the system call stub
    functions in the libc, get the first six parameters passed in
    registers and the seventh parameter and later on the stack.  The
    register use is as follows:

     system call number in the DO_CALL macro
     arg 1      rdi
     arg 2      rsi
     arg 3      rdx
     arg 4      rcx
     arg 5      r8
     arg 6      r9

    We have to take care that the stack is aligned to 16 bytes.  When
    called the stack is not aligned since the return address has just
    been pushed.


    Syscalls of more than 6 arguments are not supported.  */

和:
/* Registers clobbered by syscall.  */
# define REGISTERS_CLOBBERED_BY_SYSCALL "cc", "r11", "cx"

#undef internal_syscall6
#define internal_syscall6(number, err, arg1, arg2, arg3, arg4, arg5, arg6) \
({                                  \
    unsigned long int resultvar;                    \
    TYPEFY (arg6, __arg6) = ARGIFY (arg6);              \
    TYPEFY (arg5, __arg5) = ARGIFY (arg5);              \
    TYPEFY (arg4, __arg4) = ARGIFY (arg4);              \
    TYPEFY (arg3, __arg3) = ARGIFY (arg3);              \
    TYPEFY (arg2, __arg2) = ARGIFY (arg2);              \
    TYPEFY (arg1, __arg1) = ARGIFY (arg1);              \
    register TYPEFY (arg6, _a6) asm ("r9") = __arg6;            \
    register TYPEFY (arg5, _a5) asm ("r8") = __arg5;            \
    register TYPEFY (arg4, _a4) asm ("r10") = __arg4;           \
    register TYPEFY (arg3, _a3) asm ("rdx") = __arg3;           \
    register TYPEFY (arg2, _a2) asm ("rsi") = __arg2;           \
    register TYPEFY (arg1, _a1) asm ("rdi") = __arg1;           \
    asm volatile (                          \
    "syscall\n\t"                           \
    : "=a" (resultvar)                          \
    : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4),     \
      "r" (_a5), "r" (_a6)                      \
    : "memory", REGISTERS_CLOBBERED_BY_SYSCALL);            \
    (long int) resultvar;                       \
})

,我觉得这很容易解释。 。请注意,这似乎是如何设计成与常规System V AMD64 ABI函数的调用约定完全匹配的:https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions
快速提醒一下Clobbers:


cc表示标志寄存器。但是Peter Cordes认为这里不需要这样做。

memory意味着可以在汇编中传递指针并用于访问内存

有关从头开始的显式最小可运行示例,请参见这个答案:如何在内联汇编中通过syscall或sysenter调用系统调用?
在汇编中手动进行一些syscalls
不是很科学,但是很有趣:


x86_64.S
.text
.global _start
_start:
asm_main_after_prologue:
    /* write */
    mov , %rax    /* syscall number */
    mov , %rdi    /* stdout */
    mov $msg, %rsi  /* buffer */
    mov $len, %rdx  /* len */
    syscall

    /* exit */
    mov , %rax   /* syscall number */
    mov q4312078q, %rdi    /* exit status */
    syscall
msg:
    .ascii "hello\n"
len = . - msg

GitHub上游。


从C进行系统调用
这是一个具有寄存器约束的示例:如何通过调用系统调用内联汇编中是syscall还是sysenter?
aarch64
我在以下位置显示了一个最小的可运行用户态示例:https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834这里的TODO grep内核代码应该很容易。

评论


“ cc”垃圾邮件是不必要的:Linux syscalls保存/恢复RFLAGS(syscall / sysret指令使用R11来做到这一点,并且除了通过ptrace调试器系统调用之外,内核不会修改已保存的R11 / RFLAGS。)很重要,因为在GNU C Extended asm中x86 / x86-64隐含了一个“ cc”遮盖符,因此将其遗漏将无法获得任何好处。

– Peter Cordes
19 Mar 2 '19 at 18:21