以下链接解释的x86-32系统调用约定双方UNIX(BSD味)和Linux:
http://www.int80h.org/bsdasm/#system-calls
http://www.freebsd.org/doc/en/books/developers-handbook/x86-system-calls.html
但什么是在UNIX和Linux的系统x86-64的调用约定?
以下链接解释的x86-32系统调用约定双方UNIX(BSD味)和Linux:
http://www.int80h.org/bsdasm/#system-calls
http://www.freebsd.org/doc/en/books/developers-handbook/x86-system-calls.html
但什么是在UNIX和Linux的系统x86-64的调用约定?
进一步阅读任何这里的主题: 权威指南到Linux系统调用
我验证了在Linux上使用这些GNU汇编(气体)。
X86-32又名i386的Linux系统调用约定:
在对Linux系统调用的x86-32参数可通过寄存器传递。 %eax
的syscall_number。 %EBX,%ecx中,%EDX,%ESI,%EDI,%EBP用于传递6个参数的系统调用。
返回值是在%eax
。 所有其它寄存器(包括EFLAGS)在各个保留int $0x80
。
我把下面的代码片段从Linux的大会教程 ,但我怀疑这一点。 如果任何一个可以显示一个例子,这将是巨大的。
如果有超过六个参数,
%ebx
必须包含其中存储的参数列表中的存储位置-但不要担心这一点,因为这是不可能的,你将使用系统调用超过六个参数。
举一个例子,多一点阅读,请参阅http://www.int80h.org/bsdasm/#alternate-calling-convention 。 使用Linux的I386一个Hello World的另一个例子int 0x80
: 哪些部分的这个HelloWorld的汇编代码,如果我写汇编程序是必不可少的?
有一种更快的方式,使32位系统呼叫:使用sysenter
。 内核的内存页映射到每一个进程(VDSO),与用户空间侧sysenter
舞,这与内核合作,它能够找到返回地址。 精氨酸注册映射是一样的int $0x80
。 通常应调入VDSO而是采用sysenter
直接。 (见权威指南到Linux系统调用的信息对链接和调用到VDSO,以及有关的详细信息sysenter
,一切做系统调用)。
X86-32 [免费|打开|网络|蜻蜓] BSD UNIX系统调用约定:
参数传递到堆栈。 按参数(最后一个参数推送的第一)到堆栈。 然后推虚拟数据的另外32位(它不是实际的虚拟数据。请参考以下链接了解更多信息),然后给一个系统调用指令int $0x80
http://www.int80h.org/bsdasm/#default-calling-convention
X86-64 Mac OS X上类似,但不同的 。 TODO:检查什么* BSD做。
请参见:的“A.2 AMD64 Linux内核约定” System V应用程序二进制接口AMD64架构处理器补充 。 在i386和x86-64系统V psABIs的最新版本可以发现,从这个页面中ABI维护者的回购链接 。 (参见86达最新ABI链接和大量的约86 ASM其他好东西标签的wiki)。
下面是本节中的片段:
- 用户级应用程序作为整数寄存器用于使序列%RDI,%RSI,%RDX,RCX%,%R 8和R 9%。 内核接口使用%RDI,RSI%,%RDX,R10%,8%和%R9。
- 一个系统调用是通过完成
syscall
指令 。 这则会覆盖%RCX和R11% ,以及在%RAX返回值,但其它寄存器被保留。- 系统调用的数量在寄存器%RAX传递。
- 系统通话仅限于六个参数,无参数,直接在栈中传递。
- 从系统调用返回,注册%RAX包含系统调用的结果。 在-4095和-1之间的范围中的值表示的误差,这是
-errno
。- Integer类或类的内存只有值传递给内核。
记住,这是从Linux特有的附录,ABI,甚至对Linux它的信息不规范。 (但它实际上是准确的。)
此32位int $0x80
(但高度不推荐)ABI 是在64位代码可用。 如果你在64位代码中使用32位int 0x80的Linux的ABI会发生什么? 它仍然截断其输入为32位,所以它是不适合的指针,它归零R8-R11。
X86-32函数调用约定:
在X86-32参数传递栈。 最后一个参数是第一次被推到堆栈中,直到所有的参数都做了,然后call
指令被执行。 这是用于从组件调用C库(libc)函数在Linux。
在i386的System V ABI(在Linux上使用)的现代版本要求的16字节对齐%esp
,前后call
,如X86-64 System V的ABI总是必需的。 被调用者被允许假设,并使用SSE 16字节加载/存储上未对齐的故障。 但在历史上,仅适用于Linux需要4个字节堆栈对齐,所以它采取额外的工作,以保留自然对齐的空间即使是8字节double
什么的。
其他一些现代的32位系统仍然不需要超过4字节堆栈对齐。
X86-64系统V通过在寄存器中指定参数时,其比I386系统V的堆栈ARGS惯例更有效。 它避免了延迟和存储参数传递给内存(缓存),然后在被叫再次加载它们的额外的指令。 这种运作良好,因为有更多可用的寄存器,是现代高性能CPU好不到哪延迟和乱序执行的问题。 (此i386 ABI是很老)。
在这个新的机制:首先,参数分为若干类。 类中每个参数的确定在其中它被传递给被调用的函数的方式。
有关完整的信息是指:的“3.2功能调用序列” System V应用程序二进制接口AMD64架构处理器补充读取,部分:
一旦参数进行分类,寄存器会被分配(左到右的顺序)通过如下:
- 如果类是记忆,在栈上传递的参数。
- 如果该类是INTEGER,序列%RDI,%RSI,%RDX,RCX%,%R 8和R 9%的下一个可用的寄存器用于
所以%rdi, %rsi, %rdx, %rcx, %r8 and %r9
是用于整数/指针(即INTEGER类)参数传递到从组件任何libc函数,以便在寄存器中。 %RDI用于第一整数参数。 对于第2%RSI,RDX%为第三等。 然后call
指令应给予。 堆栈( %rsp
)必须在16B对齐call
执行。
如果有超过6个整型参数,第7 INTEGER参数及以后的在栈中传递。 (来电弹出,相同的x86-32)。
前8个浮点ARGS被传递以%xmm0-7,稍后堆栈。 有没有呼叫保存向量寄存器。 (带的FP和整数参数的混合可以具有多于8个总寄存器参数的函数。)
可变参数的函数( 像printf
)总是需要%al
= FP的数量寄存器ARGS。
有关于何时包结构到寄存器(规则rdx:rax
在返回)与在内存中。 见ABI的详细信息,并检查编译器的输出,以确保你的代码的东西应该如何通过编译器同意/返回。
需要注意的是在Windows 64位函数调用约定具有x86-64的System V的多显著差异,比如必须由主叫方(而不是红色区)预留空间的影子,和呼叫保持xmm6-XMM15。 而对于在arg去在非常不同的规则登记。
也许你正在寻找的x86_64的ABI?
如果这不是正是你以后,使用“x86_64的ABI”在您的首选搜索引擎来寻找替代引用。
调用约定定义打电话或通过其他程序调用时参数的传递在寄存器中。 而这些公约的最佳来源是在为这些硬件定义ABI标准的形式。 为了便于编辑,同样ABI也使用空间和内核程序。 Linux的/ Freebsd下遵循x86-64的相同ABI另一组为32位。 但X86-64 ABI的Windows是在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
对于Linux在PowerPC:
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
Linux内核5.0源代码注释
我知道86的细节正在arch/x86
,并根据该系统调用的东西去arch/x86/entry
。 因此,一个快速git grep rdi
在该目录使我在arch / 86 /项/ 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.
*/
并在32位拱/ 86 /进入/ entry_32.S :
/*
* 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的系统调用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://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions
该则会覆盖的快速提醒:
cc
是指标志寄存器。 但彼得科德斯评论认为这是不必要在这里。 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
GitHub的上游 。
aarch64
我在所示的最小运行的用户态例如: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grep的内核代码在这里,应该很容易。