ما هي اتفاقيات الاتصال لنظام Unix & Linux على i386 و x86-64

StackOverflow https://stackoverflow.com/questions/2535989

سؤال

اشرح الروابط التالية اتفاقيات استدعاء النظام 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. (انظر أيضا علامة wiki للحصول على روابط ABI محدثة والكثير من الأشياء الجيدة الأخرى حول x86 asm.)

هنا هو المقتطف من هذا القسم:

  1. تستخدم تطبيقات مستوى المستخدم كمسجلات عدد صحيح لتمرير Sequence ٪ RDI ، ٪ RSI ، ٪ RDX ، ٪ RCX ، ٪ R8 و ٪ R9. تستخدم واجهة kernel ٪ RDI ، ٪ RSI ، ٪ RDX ، ٪ R10 ، ٪ R8 و ٪ R9.
  2. يتم استدعاء النظام عبر syscall تعليمات. هذه Clobbers ٪ RCX و ٪ R11 بالإضافة إلى قيمة إرجاع ٪ Rax ، ولكن يتم الحفاظ على السجلات الأخرى.
  3. يجب أن يتم تمرير عدد Syscall في Record ٪ Rax.
  4. تقتصر مكالمات النظام على ست حجج ، لا يتم تمرير أي حجة مباشرة على المكدس.
  5. بالعودة من Syscall ، يحتوي Record ٪ Rax على نتيجة استدعاء النظام. تشير قيمة في النطاق بين -4095 و -1 -errno.
  6. يتم تمرير قيم عدد صحيح من الفئة فقط أو ذاكرة الفئة إلى 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 ملحق معالج العمارة الذي يقرأ ، جزئيا:

بمجرد تصنيف الوسائط ، يتم تعيين السجلات (بالترتيب من اليسار إلى اليمين) للمرور على النحو التالي:

  1. إذا كان الفصل هو الذاكرة ، فمرر الوسيطة على المكدس.
  2. إذا كان الفصل صحيحًا ، يتم استخدام السجل التالي المتاح للتسلسل ٪ 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؟

إذا لم يكن هذا بالضبط ما تتبعه ، فاستخدم "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 من المعلمات):

A good rendering of calling conventions vs registers usage

الأداء هو أحد أسباب هذه ABI (على سبيل المثال ، تمرير المعلمات عبر السجلات بدلاً من حفظها في مداخن الذاكرة)

للذراع هناك مختلف 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 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
    

    Github upstream.

AARCH64

لقد عرضت الحد الأدنى من مثال Userland على: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 Todo Grep kernel Code هنا ، يجب أن يكون سهلاً.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top