ما هي اتفاقيات الاتصال لنظام Unix & Linux على i386 و x86-64
سؤال
اشرح الروابط التالية اتفاقيات استدعاء النظام x86-32 لكل من UNIX (نكهة BSD) و Linux:
ولكن ما هي اتفاقيات استدعاء نظام X86-64 على كل من UNIX & Linux؟
المحلول
مزيد من القراءة لأي من الموضوعات هنا: الدليل النهائي لمكالمات نظام Linux
لقد تحقق من هذه باستخدام GNU Assembler (GAS) على Linux.
واجهة النواة
x86-32 AKA I386 Linux System Call Convention:
في x86-32 يتم تمرير المعلمات لاستدعاء نظام Linux باستخدام السجلات. %eax
ل syscall_number. ٪ EBX ، ٪ ECX ، ٪ EDX ، ٪ ESI ، ٪ EDI ، ٪ EBP تستخدم لتمرير 6 معلمات لمكالمات النظام.
قيمة الإرجاع في %eax
. يتم الحفاظ على جميع السجلات الأخرى (بما في ذلك EFLAGs) عبر int $0x80
.
أخذت المقتطف التالي من برنامج Linux Assembly Tutorial لكنني أشك في هذا. إذا كان بإمكان أي شخص إظهار مثال ، فسيكون ذلك رائعًا.
إذا كان هناك أكثر من ست حجج ،
%ebx
يجب أن تحتوي على موقع الذاكرة حيث يتم تخزين قائمة الوسائط - ولكن لا تقلق بشأن هذا لأنه من غير المحتمل أن تستخدم syscall مع أكثر من ستة حجج.
للحصول على مثال ومزيد من القراءة ، راجع إلى http://www.int80h.org/bsdasm/#alternate-calling-convention. مثال آخر على عالم مرحبا ل I386 Linux باستخدام int 0x80
: ما هي أجزاء من رمز تجميع HelloWorld هذا ضروري إذا كنت سأكتب البرنامج في التجميع؟
هناك طريقة أسرع لإجراء مكالمات نظام 32 بت: باستخدام sysenter
. يقوم kernel بتخطيط صفحة من الذاكرة في كل عملية (VDSO) ، مع الجانب الآخر من المستخدم sysenter
الرقص ، الذي يجب أن يتعاون مع kernel حتى تتمكن من العثور على عنوان العودة. ARG لتسجيل تعيين هو نفسه بالنسبة int $0x80
. يجب عليك عادة الاتصال في VDSO بدلاً من الاستخدام sysenter
مباشرة. (يرى الدليل النهائي لمكالمات نظام Linux للحصول على معلومات حول الارتباط والاتصال بـ VDSO ، ولمزيد من المعلومات حول sysenter
, ، وكل شيء آخر يتعلق بمكالمات النظام.)
x86-32 [مجاني | افتح | net | Dragonfly] اتفاقية استدعاء نظام BSD UNIX:
يتم تمرير المعلمات على المكدس. ادفع المعلمات (تم دفع المعلمة الأخيرة أولاً) إلى المكدس. ثم ادفع 32 بت من البيانات الوهمية (ليست بياناتها الوهمية في الواقع. راجع الرابط التالي لمزيد من المعلومات) ثم أعط تعليمات استدعاء النظام int $0x80
http://www.int80h.org/bsdasm/#default-calling-convention
x86-64 اتفاقية مكالمة نظام نظام Linux:
x86-64 Mac OS X متشابهة ولكنها مختلفة. TODO: تحقق من ما يفعله *BSD.
الرجوع إلى القسم: "A.2 AMD64 لينكس اتفاقيات النواة "من نظام V Application Binary Interface AMD64 ملحق معالج العمارة. يمكن العثور على أحدث إصدارات من نظام i386 و x86-64 v psabis مرتبط من هذه الصفحة في ريبو ABI Punainer. (انظر أيضا x86 علامة wiki للحصول على روابط ABI محدثة والكثير من الأشياء الجيدة الأخرى حول x86 asm.)
هنا هو المقتطف من هذا القسم:
- تستخدم تطبيقات مستوى المستخدم كمسجلات عدد صحيح لتمرير Sequence ٪ RDI ، ٪ RSI ، ٪ RDX ، ٪ RCX ، ٪ R8 و ٪ R9. تستخدم واجهة kernel ٪ RDI ، ٪ RSI ، ٪ RDX ، ٪ R10 ، ٪ R8 و ٪ R9.
- يتم استدعاء النظام عبر
syscall
تعليمات. هذه Clobbers ٪ RCX و ٪ R11 بالإضافة إلى قيمة إرجاع ٪ Rax ، ولكن يتم الحفاظ على السجلات الأخرى.- يجب أن يتم تمرير عدد Syscall في Record ٪ Rax.
- تقتصر مكالمات النظام على ست حجج ، لا يتم تمرير أي حجة مباشرة على المكدس.
- بالعودة من Syscall ، يحتوي Record ٪ Rax على نتيجة استدعاء النظام. تشير قيمة في النطاق بين -4095 و -1
-errno
.- يتم تمرير قيم عدد صحيح من الفئة فقط أو ذاكرة الفئة إلى kernel.
تذكر أن هذا من الملحق الخاص بـ Linux إلى ABI ، وحتى بالنسبة إلى Linux ، فهو غير معياري. (لكنها في الواقع دقيقة.)
هذا 32 بت int $0x80
أبي هو قابلة للاستخدام في رمز 64 بت (ولكن غير موصى به للغاية). ماذا يحدث إذا كنت تستخدم 32 بت 0x80 Linux ABI في رمز 64 بت؟ لا يزال يقطع مدخلاته إلى 32 بت ، لذلك فهو غير مناسب للمؤشرات ، و Zeros R8-R11.
واجهة المستخدم: استدعاء الوظيفة
x86-32 اتفاقية استدعاء الوظيفة:
في x86-32 تم تمرير المعلمات على المكدس. تم دفع المعلمة الأخيرة أولاً إلى المكدس حتى تتم جميع المعلمات ثم call
تم تنفيذ التعليمات. يتم استخدام هذا لاتصال وظائف مكتبة C (LIBC) على Linux من التجميع.
تتطلب الإصدارات الحديثة من نظام I386 V ABI (المستخدم على Linux) محاذاة 16 بايت من %esp
قبل call
, ، مثل X86-64 System V ABI دائمًا. يُسمح لـ Callees بافتراض ذلك واستخدام الأحمال/المتاجر SSE 16 بايت التي لا تخضع للاختلاف. لكن تاريخياً ، تتطلب Linux محاذاة مكدس 4 بايت فقط ، لذلك استغرق الأمر عملًا إضافيًا لحجز مساحة محاذاة بشكل طبيعي حتى بالنسبة إلى 8 بايت double
أو شيء ما.
لا تزال بعض أنظمة 32 بت حديثة أخرى لا تتطلب أكثر من 4 محاذاة كومة بايت.
X86-64 SYSTEM V وظيفة استدعاء وظيفة المستخدم:
X86-64 System V يمرر args في السجلات ، وهو أكثر كفاءة من اتفاقية args System V System V. إنه يتجنب الكمون والتعليمات الإضافية لتخزين ARGS للذاكرة (ذاكرة التخزين المؤقت) ثم تحميلها مرة أخرى في Callee. هذا يعمل بشكل جيد لأن هناك المزيد من السجلات المتاحة ، وهو أفضل لمنظمة المعالجة المركزية العالية الأداء الحديثة حيث يهم الكمون والتنفيذ خارج الطلب. (I386 ABI قديم جدًا).
في هذا الجديد الآلية: أولاً يتم تقسيم المعلمات إلى فصول. تحدد فئة كل معلمة الطريقة التي يتم بها تمريرها إلى الوظيفة المدعومة.
للحصول على معلومات كاملة ، يرجى الرجوع إلى: "3.2 استدعاء الوظيفة" من نظام V Application Binary Interface AMD64 ملحق معالج العمارة الذي يقرأ ، جزئيا:
بمجرد تصنيف الوسائط ، يتم تعيين السجلات (بالترتيب من اليسار إلى اليمين) للمرور على النحو التالي:
- إذا كان الفصل هو الذاكرة ، فمرر الوسيطة على المكدس.
- إذا كان الفصل صحيحًا ، يتم استخدام السجل التالي المتاح للتسلسل ٪ RDI و ٪ RSI و ٪ RDX و ٪ RCX و ٪ R8 و ٪ R9
لذا %rdi, %rsi, %rdx, %rcx, %r8 and %r9
هي السجلات مرتب تستخدم لتمرير المعلمات عدد صحيح/مؤشر (IE Integer) إلى أي وظيفة LIBC من التجميع. يتم استخدام ٪ RDI لمعلمة عدد صحيح الأول. ٪ RSI لـ 2nd ، ٪ RDX للثالث وما إلى ذلك. ثم call
يجب إعطاء التعليمات. المدخنة (%rsp
) يجب أن يكون 16 ب محاذاة عندما call
ينفذ.
إذا كان هناك أكثر من 6 معلمات عدد صحيح ، يتم تمرير المعلمة عدد صحيح 7 وبعد ذلك على المكدس. (Caller Pops ، مثل x86-32.)
يتم تمرير أول 8 نقطة عائمة في ٪ xmm0-7 ، في وقت لاحق على المكدس. لا توجد سجلات ناقلات الحفاظ عليها. (يمكن أن يكون للدالة مع مزيج من وسيطات FP و integer أكثر من 8 وسيطات تسجيل إجمالية.)
وظائف variadic (مثل printf
) دائما تحتاج %al
= عدد FP Record Args.
هناك قواعد لموعد حزم الهياكل في السجلات (rdx:rax
عند العودة) مقابل الذاكرة. راجع ABI للحصول على التفاصيل ، وتحقق من إخراج المترجم للتأكد من أن الكود الخاص بك يتفق مع المترجمين حول كيفية إرجاع/إرجاع شيء ما.
لاحظ أن اتفاقية استدعاء وظيفة Windows X64 له اختلافات متعددة كبيرة من نظام X86-64 ، مثل مساحة الظل التي يجب يتم حجزها بواسطة المتصل (بدلاً من منطقة حمراء) ، ويتم الحفاظ عليها من XMM6-XMM15. وقواعد مختلفة تمامًا التي يذهب فيها ARG إلى أي تسجيل.
نصائح أخرى
ربما كنت تبحث عن X86_64 ABI؟
- www.x86-64.org/documentation/abi.pdf (404 في 2018-11-24)
- www.x86-64.org/documentation/abi.pdf (عبر آلة Wayback في 2018-11-24)
- أين يتم توثيق نظام X86-64 V ABI؟ - https://github.com/hjl-tools/x86-psabi/wiki/x86-psabi يتم تحديثه (بواسطة HJ LU ، أحد محلات ABI) مع روابط إلى PDFs من الإصدار الحالي الرسمي.
إذا لم يكن هذا بالضبط ما تتبعه ، فاستخدم "x86_64 ABI" في محرك البحث المفضل لديك للعثور على مراجع بديلة.
تحدد اتفاقيات الاتصال كيف يتم تمرير المعلمات في السجلات عند الاتصال أو الاتصال من قبل البرنامج الآخر. وأفضل مصدر لهذه الاتفاقية هو في شكل معايير ABI المحددة لكل هذه الأجهزة. لسهولة التجميع ، يتم استخدام نفس ABI من قبل برنامج المستخدمين وبرنامج kernel. Linux/FreeBSD اتبع نفس ABI لـ X86-64 ومجموعة أخرى لـ 32 بت. لكن X86-64 ABI لنظام التشغيل Windows يختلف عن Linux/FreeBSD. وعمومًا ، لا يفرق ABI استدعاء النظام مقابل "مكالمات الوظائف" العادية. أي ، إليك مثال خاص على اتفاقيات الاتصال x86_64 وهو نفسه بالنسبة لكل من Linux Usserpace و kernel: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ (لاحظ التسلسل A ، B ، C ، D ، E ، F من المعلمات):
الأداء هو أحد أسباب هذه ABI (على سبيل المثال ، تمرير المعلمات عبر السجلات بدلاً من حفظها في مداخن الذاكرة)
للذراع هناك مختلف 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
لـ 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
هذه الوثيقة هي نظرة عامة جيدة على جميع الاتفاقيات المختلفة:
Linux kernel 5.0 تعليقات المصدر
كنت أعرف أن تفاصيل x86 تحت arch/x86
, ، وهذه الاشياء syscall تتحول arch/x86/entry
. لذلك سريع git grep rdi
في هذا الدليل يقودني إلى ARCH/X86/ENTROM/ENTROM_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 بت في القوس/x86/إدخال/إدخال_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 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://en.wikipedia.org/wiki/x86_calling_conventions#list_of_x86_calling_conventions
تذكير سريع لل clobbers:
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
AARCH64
لقد عرضت الحد الأدنى من مثال Userland على: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 Todo Grep kernel Code هنا ، يجب أن يكون سهلاً.