以下链接说明了用于UNIX(BSD风格)和Linux的x86-32系统调用约定:
但是,UNIX和Linux上的x86-64系统调用约定是什么?
sysret
,并且rax被返回值替换。所有其他寄存器都保留在amd64上。
以下链接说明了用于UNIX(BSD风格)和Linux的x86-32系统调用约定:
但是,UNIX和Linux上的x86-64系统调用约定是什么?
sysret
,并且rax被返回值替换。所有其他寄存器都保留在amd64上。
Answers:
进一步阅读以下任何主题:Linux系统调用权威指南
我在Linux上使用GNU汇编器(气体)验证了这些。
x86-32又名i386 Linux系统调用约定:
在x86-32中,使用寄存器传递用于Linux系统调用的参数。%eax
用于syscall_number。%ebx,%ecx,%edx,%esi,%edi,%ebp用于将6个参数传递给系统调用。
返回值为in %eax
。所有其他寄存器(包括EFLAGS)都保留在内int $0x80
。
我从Linux Assembly Tutorial中摘录了以下片段,但对此感到怀疑。如果有人可以举一个例子,那就太好了。
如果有六个以上的参数,则
%ebx
必须包含参数列表存储的内存位置-但不必担心,因为您不太可能使用超过六个参数的syscall。
有关示例和更多内容,请参见http://www.int80h.org/bsdasm/#alternate-calling-convention。i386 Linux的Hello World的另一个示例使用int 0x80
:Hello,Linux系统调用中的汇编语言世界吗?
有一种进行32位系统调用的更快方法:使用sysenter
。内核将页面的内存映射到每个进程(vDSO)中,而舞动的用户空间端sysenter
则必须与内核合作才能找到返回地址。用于寄存器映射的Arg与for相同int $0x80
。通常,您应该调用vDSO,而不是sysenter
直接使用。(有关链接和调用vDSO的信息,以及有关的更多信息以及与系统调用有关的所有其他信息,请参阅《 Linux系统调用权威指南》sysenter
。)
x86-32 [Free | Open | Net | DragonFly] BSD UNIX系统调用约定:
参数在堆栈上传递。将参数(最后一个参数先被压入)推入堆栈。然后推送一个额外的32位虚拟数据(它实际上不是虚拟数据。有关更多信息,请参见下面的链接),然后给出系统调用指令int $0x80
http://www.int80h.org/bsdasm/#default-calling-convention
x86-64 Mac OS X相似但不同。TODO:检查* BSD的功能。
请参阅《System V应用程序二进制接口AMD64体系结构处理器补充》中的“ A.2 AMD64 Linux内核约定”部分。可从ABI维护人员的存储库中的此页面链接找到最新版本的i386和x86-64 System V psABI。。(另请参见x86 标记Wiki,以获取最新的ABI链接以及有关x86 asm的许多其他好东西。)
这是此部分的代码段:
- 用户级应用程序用作整数寄存器,以传递序列%rdi,%rsi,%rdx,%rcx,%r8和%r9。内核接口使用%rdi,%rsi,%rdx,%r10,%r8和%r9。
- 通过
syscall
指令进行系统调用。此副本%rcx和%r11以及%rax返回值,但保留了其他寄存器。- 系统调用的编号必须在寄存器%rax中传递。
- 系统调用仅限于六个参数,没有参数直接传递到堆栈上。
- 从系统调用返回,寄存器%rax包含系统调用的结果。-4095到-1之间的值表示错误,它是
-errno
。- 仅将类INTEGER或类MEMORY的值传递给内核。
请记住,这是从ABI特定于Linux的附录中获得的,即使对于Linux来说,它的信息内容也不是规范性的。(但实际上是准确的。)
此32位int $0x80
ABI 可用于64位代码(但强烈建议不要使用)。 如果以64位代码使用32位int 0x80 Linux ABI,会发生什么情况? 它仍然将输入截断为32位,因此不适合使用指针,并且将r8-r11归零。
x86-32函数调用约定:
在x86-32中,参数在堆栈上传递。将最后一个参数首先压入堆栈,直到完成所有参数,然后call
执行指令。这用于从程序集在Linux上调用C库(libc)函数。
i386 System V ABI的现代版本(在Linux上使用)要求%esp
a之前的16字节对齐call
,就像x86-64 System V ABI始终需要的一样。允许被调用者假定并使用SSE 16字节加载/存储在未对齐时发生故障。但是从历史上看,Linux只需要4字节堆栈对齐,因此即使8字节也要保留自然对齐的空间,这需要额外的工作。double
东西。
其他一些现代的32位系统仍然不需要超过4字节的堆栈对齐。
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的下一个可用寄存器
用来按顺序将整数/指针(即INTEGER类)参数传递给汇编中任何libc函数%rdi, %rsi, %rdx, %rcx, %r8 and %r9
的寄存器也是如此。%rdi用于第一个INTEGER参数。%rsi代表第二,%rdx代表第三,依此类推。然后call
应该给出指示。执行时,堆栈(%rsp
)必须对齐16B call
。
如果有6个以上INTEGER参数,则将第7个INTEGER参数及更高版本传递给堆栈。(弹出呼叫者,与x86-32相同。)
前8个浮点args在%xmm0-7中传递,随后在堆栈中传递。没有保留呼叫的向量寄存器。(一个包含FP和整数参数的函数的寄存器总数可以超过8个。)
可变参数函数(如printf
)始终需要%al
= FP寄存器args的数量。
对于何时将结构打包到寄存器(rdx:rax
返回时)与在内存中打包有一些规则。有关详细信息,请参见ABI,并检查编译器输出,以确保您的代码与编译器就如何传递/返回某些内容达成一致。
请注意,Windows x64函数调用约定与x86-64 System V有多个显着不同,例如必须由调用者保留的阴影空间(而不是红色区域)和保留了呼叫的xmm6-xmm15。arg进入哪个寄存器的规则也非常不同。
int 0x80
在64位代码中使用Linux的ABI,则会发生以下情况:stackoverflow.com/questions/46087730/…。它将r8-r11归零,并且工作原理与在32位进程中运行时完全相同。在该问答中,我有一个示例说明它正常工作,或者由于截断指针而失败。而且,我还深入研究了内核源代码,以显示其为何如此运行。
也许您正在寻找x86_64 ABI?
如果这不完全是您想要的,请在您首选的搜索引擎中使用“ x86_64 abi”查找替代参考。
调用约定定义了在调用或被其他程序调用时如何在寄存器中传递参数。这些约定的最佳来源是为每种硬件定义的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
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
该文档很好地概述了所有不同的约定:
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在处定义了x86_64 syscalls,sysdeps/unix/sysv/linux/x86_64/sysdep.h
其中包含一些有趣的代码,例如:
/* 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 : //zh.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions
快速提醒您:
cc
表示标志寄存器。但是彼得·科德斯(Peter Cordes)认为这里没有必要。memory
表示可以在汇编中传递指针并用于访问内存有关从头开始的最小的可运行示例,请参见以下答案:如何通过内联汇编中的sysenter调用系统调用?
手动在汇编中进行一些系统调用
不是很科学,但是很有趣:
x86_64.S
.text
.global _start
_start:
asm_main_after_prologue:
/* write */
mov $1, %rax /* syscall number */
mov $1, %rdi /* stdout */
mov $msg, %rsi /* buffer */
mov $len, %rdx /* len */
syscall
/* exit */
mov $60, %rax /* syscall number */
mov $0, %rdi /* exit status */
syscall
msg:
.ascii "hello\n"
len = . - msg
aarch64
我在以下位置显示了一个最小的可运行用户态示例:https: //reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grep内核代码应该很容易。
"cc"
撞是不必要的:Linux的系统调用保存/恢复RFLAGS(在syscall
/ sysret
指令做,使用R11,内核不修改保存R11 / RFLAGS不是通过其他ptrace
调试器的系统调用。)不,它曾经重要,因为"cc"
撞的对于GNU C扩展asm中的x86 / x86-64是隐式的,因此将其遗漏将无法获得任何收益。