cla*_*aws 132 unix linux assembly x86-64 abi
以下链接解释了UNIX(BSD风格)和Linux的x86-32系统调用约定:
但是UNIX和Linux上的x86-64系统调用约定是什么?
cla*_*aws 207
我在Linux上使用GNU Assembler(gas)验证了这些.
x86-32 Linux系统调用约定:
在x86-32中,Linux系统调用的参数使用寄存器传递.%eax
for syscall_number.%ebx,%ecx,%edx,%esi,%edi,%ebp用于将6个参数传递给系统调用.
返回值是%eax
.所有其他寄存器(包括EFLAGS)都保留在int $0x80
.
我从Linux Assembly Tutorial中获取了以下片段,但我对此表示怀疑.如果有人可以展示一个例子,那就太好了.
如果有超过六个参数,则
%ebx
必须包含存储参数列表的内存位置 - 但不要担心这一点,因为您不太可能使用超过六个参数的系统调用.
有关示例和更多内容,请参阅http://www.int80h.org/bsdasm/#alternate-calling-convention
有更快的方式来进行32位系统调用:使用int 0x80
.内核将一页内存映射到每个进程(vdso),其中包含用户空间端sysenter
,它必须与内核合作才能找到返回地址.arg to register mapping与for相同sysenter
,但代码应该调用vdso中的函数.(TODO:使用链接和/或特定信息更新此内容).
x86-32 [免费|打开|网络| DragonFly] BSD UNIX系统调用约定:
参数在堆栈上传递.将参数(最后一个参数按下)推入堆栈.然后推送额外的32位虚拟数据(它实际上不是虚拟数据.请参阅以下链接以获取更多信息)然后提供系统调用指令int $0x80
http://www.int80h.org/bsdasm/#default-calling-convention
x86-64 Linux系统调用约定:
x86-64 Mac OS X类似但不同.TODO:检查*BSD的作用.
请参阅System V应用程序二进制接口AMD64架构处理器补充资料的 "A.2 AMD64 Linux内核约定" 一节.最新版本的i386和x86-64 System V psABI可以在ABI维护者仓库的这个页面中找到.(另请参阅x86标记wiki以获取最新的ABI链接以及许多关于x86 asm的其他好东西.)
以下是本节的摘录:
- 用户级应用程序使用整数寄存器来传递序列%rdi,%rsi,%rdx,%rcx,%r8和%r9.内核接口使用%rdi,%rsi,%rdx,%r10,%r8和%r9.
- 系统调用通过
sysenter
指令完成.这个clobbers%rcx和%r11,以及%rax,但保留了其他寄存器.- 必须在寄存器%rax中传递系统调用的编号.
- 系统调用仅限于六个参数,不会直接在堆栈上传递参数.
- 从系统调用返回,寄存器%rax包含系统调用的结果.介于-4095和-1之间的值表示错误,它是
sysenter
.- 只有类INTEGER或类MEMORY的值传递给内核.
请记住,这是来自ABI的特定于Linux的附录,甚至对于Linux来说,它的信息量也不是规范性的.(但实际上它是准确的.)
x86-32函数调用约定:
在x86-32中,参数在堆栈上传递.最后一个参数首先被压入堆栈,直到完成所有参数然后int $0x80
执行指令.这用于从汇编中调用Linux上的C库(libc)函数.
x86-64函数调用约定:
x86-64在寄存器中传递args,这比i386 System V的堆栈args约定更有效.它避免了将args存储到内存(缓存)然后在被调用者中再次加载它们的延迟和额外指令.这很有效,因为有更多的寄存器可用,并且对于延迟和无序执行很重要的现代高性能CPU更好.(i386 ABI很老了).
在这个新机制中:首先将参数分为几类.每个参数的类确定它传递给被调用函数的方式.
有关完整信息,请参阅:System V应用程序二进制接口AMD64架构处理器补充资料的"3.2函数调用序列",其中部分内容如下:
一旦参数被分类,寄存器就会被分配(按从左到右的顺序)传递,如下所示:
- 如果类是MEMORY,则在堆栈上传递参数.
- 如果类是INTEGER,则使用序列%rdi,%rsi,%rdx,%rcx,%r8和%r9的下一个可用寄存器
所以syscall
是寄存器,以便用于整数/指针(即INTEGER类)参数传递到从组件任何libc函数.%rdi用于第一个INTEGER参数.%rsi代表第二名,%rdx代表第三名,依此类推.然后-errno
应该给出指示.执行时,stack(int $0x80
)必须是16B对齐的call
.
如果有超过6个INTEGER参数,则第7个INTEGER参数及更高版本参数将在堆栈上传递.(来电者弹出,与x86-32相同.)
前8个浮点args在%xmm0-7中传递,稍后在堆栈中传递.没有调用保留的向量寄存器.(混合使用FP和整数参数的函数可以有超过8个寄存器参数.)
变量函数(如%esp
)总是需要call
= FP寄存器args的数量.
有关何时将结构打包到寄存器(double
返回时)与内存中的规则.有关详细信息,请参阅ABI,并检查编译器输出以确保您的代码与编译器一致,了解应如何传递/返回内容.
Jon*_*ler 15
也许您正在寻找x86_64 ABI?
如果这不是您所追求的,请在您首选的搜索引擎中使用"x86_64 abi"来查找替代参考.
Pet*_*eoh 11
调用约定定义了在调用或被其他程序调用时如何在寄存器中传递参数.这些约定的最佳来源是为每种硬件定义的ABI标准.为了便于编译,用户空间和内核程序也使用相同的ABI.Linux/Freebsd遵循相同的ABI for x86-64和另一套32-bit.但x86-64 ABI for 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
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
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.
*/
Run Code Online (Sandbox Code Playgroud)
对于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
*/
Run Code Online (Sandbox Code Playgroud)
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. */
Run Code Online (Sandbox Code Playgroud)
和:
/* 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; \
})
Run Code Online (Sandbox Code Playgroud)
我觉得这很容易解释。请注意,这似乎是如何设计为与常规System V AMD64 ABI函数的调用约定完全匹配的:https : //en.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
Run Code Online (Sandbox Code Playgroud)
aarch64
我在以下位置展示了一个最小的可运行用户态示例:https: //reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grep内核代码应该很容易。
归档时间: |
|
查看次数: |
99701 次 |
最近记录: |