قراردادهای فراخوانی اکس۸۶
این مقاله توصیف قراردادهای فراخوانی (به انگلیسی: calling conventions) مورد استفاده، در هنگام برنامهنویسی میکروپروسسورهای معماری اکس۸۶ است.[۱]
قراردادهای فراخوانی، رابط کاربری کد فراخوانی شده را توصیف میکند:
- ترتیبی که به پارامترهای اتمی (اسکالر) یا بخشهای فردی از یک پارامتر پیچیده، اختصاص داده میشود
- پارامترها چگونه منتقل میشوند (گذاشته شدن بر روی پشته (به انگلیسی: stack)، قرار گرفتن در ثبّات (رجیستر)ها یا ترکیبی از هر دو)
- کدام ثبّاتهای تابع فراخوانی شده باید برای فراخوانیکننده حفظ شود (همچنین شناخته شدن به عنوان: ثبّاتهای ذخیره شده تابع صدا شده(callee-saved) یا غیر فرار (non-volatile)
- چگونه وظیفه آمادهسازی پشته و بازگرداندن بعد از یکبار صدا زدنِ تابع، بین فراخوانی کننده(caller) و فراخوانی شده(callee) تقسیم میشود
این دقیقاً با تخصیص دادنِ اندازهها و فرمتها به انواع زبان برنامهنویسی مرتبط است. یکی دیگر از موضوعهای خیلی مرتبط برانگیختگی نام (Name mangling) است، که تعیین میکند که چگونه نمادها در کد به نماد مورد استفاده در لینکر مرتبط و تبدیل شوند. قراردادهای فراخوانی، نمایندگیهای نوع، و برانگیختگی نام، همه بخشی از آنچه که به عنوان یک رابط دودویی نرمافزار شناخته میشود هستند.
اغلب تفاوتهای ظریفی در نحوه پیادهسازی این قراردادها در کامپایلرهای مختلف وجود دارد، بنابراین اغلب سخت است که کدی را که توسط کامپایلرهای مختلف کامپایل شدهاست نمایش دهیم. از سوی دیگر، قراردادهایی که به عنوان استاندارد API (مانند stdcall) مورد استفاده قرار میگیرند، بهطور خیلی یکسان پیادهسازی میشوند.
پیشینه تاریخی
[ویرایش]قبل از میکرو رایانهها، تولیدکننده دستگاه، بهطور کلی یک سیستم عامل و کامپایلرهایی برای چند زبان برنامهنویسی ارائه میداد. قرارداد (های) فراخوانی برای هر پلت فرم، توسط ابزار برنامه نویسیِ سازنده آن تعریف میشود.
میکرو رایانههای اولیه قبل از Commodore Pet و اپل II بهطور کلی بدون سیستم عامل یا کامپایلرها عرضه میشدند. رایانه شخصی آیبیام با سیستم عامل دیسک (DOS) (پیشگام مایکروسافت برای ویندوز) عرضه شد، اما بدون کامپایلر. تنها سختافزار استاندارد برای ماشینهای سازگار با رایانه شخصی IBM توسط پردازندههای اینتل (۸۰۸۶، ۸۰۳۸۶) تعریف شد که سختافزار تحت اللفظی IBM نامیده میشد. سپس فرمتهای سختافزاری و تمامی استانداردهای نرمافزاری (ذخیره برای قراردادهای فراخوانی BIOS) برای رقابت در بازار عرضه شد.[۲]
بسیاری از شرکتهای نرمافزارهای مستقل، سیستم عاملها و کامپایلرهایی برای بسیاری از زبانهای برنامهنویسی و برنامههای کاربردی ارائه میدهند. بسیاری از طرحهای مختلف فراخوانی کردن توسط شرکتها پیادهسازی شدهاست که اغلب متقابلاً منحصر به فرد، بر اساس الزامات مختلف، شیوههای تاریخی و خلاقیت برنامهنویس هستند.[۳]
پس از انعطافپذیری بازار IBM، سیستم عاملهای و ابزارهای برنامهنویسی مایکروسافت (با قراردادهای متفاوت) غالب بودند، در حالی که شرکتهای رده دوم مثل بورلند و نوول و پروژههای منبع بازمانند GCC هنوز استانداردهای خود را حفظ کردند. در نهایت مقررات مربوط به قابلیت همکاری بین فروشندگان و محصولات به تصویب رسید، ساده کردن مشکل انتخاب یک قرارداد قابل قبول بود.[۴]
پاک شدن توسط فراخوانی کننده
[ویرایش]در این قراردادها، فراخوانی کننده، آرگومانها را از پشته پاک میکند.
قرارداد cdecl
[ویرایش]cdecl (که بر پایه تعریف C است) یک قرارداد فراخوانی است که از زبان برنامهنویسی C سرچشمه میگیرد و توسط بسیاری از کامپایلرهای C برای معماری x86 استفاده میشود.[۴] در cdecl، آرگومانهای تابع به پشته منتقل میشوند. مقادیر اعداد صحیح و آدرسهای حافظه به وسیلهٔ ثبات (رجیستر) EAX و مقادیر اعشاری در ثبّات ST0 x87 بازگردانده میشوند. ثبّاتهای EAX, ECX، و EDX ذخیره شده توسط فراخوانیکننده (به انگلیسی: caller-saved) و بقیه ذخیره شده توسط فراخوانی شونده (به انگلیسی: callee-saved) هستند. ثبّاتهای اعشاری x87 T ST0 تا ST7، هنگام فراخوانی یک تابع جدید باید خالی (برداشته شده یا آزاد) باشند و ST1 تا ST7 باید در هنگام خروج از یک تابع خالی باشد. ST0 نیز باید زمانی خالی باشد که برای بازگشت مقدار مورد استفاده قرار نگیرد.
در چارچوب زبان برنامهنویسی C، آرگومانهای تابع بر روی پشته از راست به چپ قرار میگیرند، یعنی اول آخرین آرگومان قرار داده میشود. در لینوکس، GCC استانداردهای واقعی را برای قراردادهای فراخوانی تنظیم میکند. از زمان GCC نسخه ۴٫۵، هنگام فراخوانی یک تابع، پشته باید به یک مرز ۱۶ بایت برسد (نسخههای قبلی فقط یک تراز ۴ بایت نیاز داشتند) [۵]
قطعه کد مرجعِ C زیر را در نظر بگیرید:
int callee(int, int, int);
int caller(void)
{
return callee(1, 2, 3) + 5;
}
در x86، ممکن است کد اسمبلی (به روش نوشتاری اینتل) زیر تولید کند:
caller:
; make new call frame
; (some compilers may produce an 'enter' instruction instead)
push ebp ; save old call frame
mov ebp, esp ; initialize new call frame
; push call arguments, in reverse
; (some compilers may subtract the required space from the stack pointer,
; then write each argument directly, see below.
; The 'enter' instruction can also do something similar)
; sub esp, 12 : 'enter' instruction could do this for us
; mov [ebp-4], 3 : or mov [esp+8], 3
; mov [ebp-8], 2 : or mov [esp+4], 2
; mov [ebp-12], 1 : or mov [esp], 1
push 3
push 2
push 1
call callee ; call subroutine 'callee'
add eax, 5 ; modify subroutine result
; (eax is the return value of our callee,
; so we don't have to move it into a local variable)
; restore old call frame
; (some compilers may produce a 'leave' instruction instead)
; add esp, 12 ; remove arguments from frame, ebp - esp = 12.
; compilers will usually produce the following instead,
; which is just as fast, and, unlike the add instruction,
; also works for variable length arguments
; and variable length arrays allocated on the stack.
mov esp, ebp ; most calling conventions dictate ebp be callee-saved,
; i.e. it's preserved after calling the callee.
; it therefore still points to the start of our stack frame.
; we do need to make sure
; callee doesn't modify (or restores) ebp, though,
; so we need to make sure
; it uses a calling convention which does this
pop ebp ; restore old call frame
ret ; return
فراخوانیکننده پس از اتمام فراخوانی کردن تابع، پشته را پاک میکند.
برخی تغییرات در بیان cdecl,[۶] به ویژه در نحوه بازگشت مقادیر وجود دارد. در نتیجه، برنامههای کامپایل شده توسط x86 با سیستم عاملهای مختلف یا کامپایلرهای مختلف میتواند ناسازگار باشد، حتی اگر آنها هر دو از قراردادهای "cdecl" استفاده کنند و به محیط زیر وابسته نباشند. بعضی از کامپایلرها ساختار دادههای ساده را با طول ۲ ثبّات یا کمتر در جفت ثبّات EAX: EDX برمیگردانند، و ساختارهای بزرگتر و اشیاء کلاسها که نیاز به رفتار ویژه توسط رسیدگی کنندهٔ استثنا دارند (مانند سازندهٔ تعریف شده، تخریبکننده یا اختصاص دادن) به حافظه برگرداننده میشوند. برای انتقال "در حافظه"، فراخوانیکننده حافظه را اختصاص میدهد و یک اشاره گر به آن را به عنوان پارامتر پنهان میدهد؛ فراخوانی شده حافظه را پر میکند و اشاره گر را برمیگرداند (هنگام بازگشت اشاره گر پنهان برداشته میشود).
در لینوکس / GCC، مقادیر اعداد اعشاری هشت بایتی(double) / چهار بایتی(float) باید به وسیلهٔ شبه پشتهٔ x87 روی پشته قرار بگیرند. مثل این:
sub esp, 8 ; make room for the double
fld [ebp + x] ; load our double onto the floating point stack
fstp [esp] ; push our double onto the stack
call funct
add esp, 8
با استفاده از این روش تضمین میشود که این در پشته به فرم صحیح قرار داده شدهاست.
قراردادهای فراخوانی cdecl معمولاً قرارداد فراخوانی پیش فرض برای کامپایلرهای x86 C است، اگر چه بسیاری از کامپایلرها مجهز به گزینههایی برای تغییر خودکار قراردادهای فراخوانیِ استفاده شده هستند. برای تعریف کردن دستی تابع به عنوان cdecl، برخی نحو زیر را پشتیبانی میکند:
return_type _cdecl func_name();
اصلاحکننده cdecl_ باید در نمونه اولیه تابع (به انگلیسی: function prototype) و در تعریف تابع گنجانده شود تا هر تنظیمات دیگری را که ممکن است در جای خود باشد را لغو کند.
قرارداد syscall
[ویرایش]این مشابه cdecl است در آن آرگومانها از راست به چپ در استک قرارداده میشوند. ECX, EAX و EDX ذخیره نمیشوند. اندازه لیست پارامترها در doublewords به AL منتقل میشود.
Syscall قرارداد فراخوانی استاندارد برای OS / 2 API سی و دو بیت (۳۲ بیت) است.
قرارداد optlink
[ویرایش]آرگومانها از راست به چپ در پشته قرارداده میشوند. سه آرگومان اول (چپترین) در EAX, EDX و ECX منتقل میشود و بیشتر از چهار آرگومان اعشاری در ST0 از طریق ST3 منتقل میشود، اگرچه فضای آنها در لیست آرگومانها بر پشته رزرو میشود. نتایج در EAX یا ST0 برگردانده میشوند. ثبّاتهای EBP, EBX, ESI، و EDI حفظ میشوند.
Optlink توسط کامپایلر IBM VisualAge استفاده میشود.
پاک کردن توسط فراخوانی شده
[ویرایش]در این قراردادها، تابع فراخوانی شده، آرگومانها را از پشته پاک میکند. تشخیص توابعی ک این قراردادها را استفاده میکنند در کد ASM آسان است زیرا آنها پس از بازگشت، پشته را باز میکنند. دستور x86 ret به پارامتر ۱۶ بیتی اختیاری اجازه میدهد که بعد از بازگشت به فراخوانی کننده، تعداد بایتهای مشخصی از پشته را آزاد کند. نمونه ای از این کد به صورت زیر است:
ret 12
قراردادهایی به نام fastcall یا ثبّات استاندارد نشدهاند و پیادهسازی آن بسته به نوع فروشنده کامپایلر متفاوت است.[۴] بهطور معمول قراردادهای مبتنی بر ثبّات، یک یا چند آرگومان را به ثبّاتهایی که تعداد دسترسیهای حافظه مورد نیاز برای فراخوانی را کاهش میدهد و به این ترتیب آنها را سریع تر میکند، ارسال میکنند
پاسکال
[ویرایش]بر اساس قرارداد فراخوانی زبان برنامهنویسی Borland Pascal، پارامترها بر روی پشته از چپ به راست (مخالف cdecl) قرار میگیرند و فراخوانی شده مسئول حذف آنها از پشته است.
بازگشت نتیجه به شرح زیر است:
- مقادیر عادی در AL (مقادیر ۸ بیتی)، AX (مقادیر ۱۶ بیتی)، EAX (مقادیر ۳۲ بیتی) یا DX: AX (مقادیر ۳۲ بیتی در سیستمهای ۱۶ بیتی) بازگرداننده میشوند.
- مقادیر واقعی در DX:BX:AX بازگردانده میشوند.
- مقادیر اعداد اعشاری (۸۰۸۷) در ST0 بازگرداننده میشوند.
- اشاره گرها در EAX در سیستمهای ۳۲ بیتی و در AX در سیستمهای ۱۶ بیتی بازگرداننده میشوند..
- رشتهها در یک محل موقت با نماد Result@ بازگرداننده میشوند.
این قرارداد فراخوانی در APIهای ۱۶ بیتی زیر رایج بود:
OS / 2 1.x، مایکروسافت ویندوز 3.x و Borland Delphi نسخه 1.x. نسخههای مدرن ویندوز API از stdcall استفاده میکنند، که هنوز هم فراخوانی شده پشته را طبق قرارداد پاسکال را ذخیره میکند، اما اکنون پارامترها از راست به چپ قرارداده میشوند.
قرارداد stdcall
[ویرایش]قرارداد فراخوانی[۷] stdcall تغییری در قرارداد فراخوانی پاسکال است که در آن فراخوانی شده مسئول پاک کردن پشته است، اما پارامترها در پشته به ترتیب از راست به سمت چپ، همانطور که در قرارداد فراخوانی _cdecl قرار دارد، قرار میگیرند. ثبّاتهای EAX, ECX و EDX برای استفاده درون تابع تعیین میشوند. مقادیر بازگشتی در ثبّات EAX ذخیره میشوند.
stdcall استاندارد برای مایکروسافت Win32 API و Open Watcom CPP است.
قرارداد فراخوانیِ سریع مایکروسافت fastcall
[ویرایش]قرارداد مایکروسافت[۸] or GCC[۹] fastcall__ (معروف به msfastcall__) دو آرگومان اول (که از چپ به راست به دست میآید) را میدهد که در ECX و EDX قرار میگیرند. آرگومانهای باقی مانده راست به چپ در پشته قرار میگیرند. هنگامی که کامپایلر MS برای IA64 یا AMD64 کامپایل میشود، کلمه کلیدیfastcall__ را نادیده میگیرد و به جای آن یک قرارداد فراخوانی ۶۴ بیتی را استفاده میکند.
قرارداد فراخوانیِ برداریِ مایکروسافت
[ویرایش]در ویژوال استودیو ۲۰۱۳، مایکروسافت قرارداد فراخوانی vectorcall__ پاسخگو به نگرانیهای توسعه دهندگان کد در مورد کارآییِ بازیها، گرافیک، ویدئو / صدا است. برای کدهای IA-32 و x64، قرارداد vectorcall__[۱۰] شبیه fastcall__ و قرارداد فراخوانی اصلی x64 است، اما آنها را با پشتیبانی از پاس دادن آرگومانهای برداری با استفاده از ثبّاتهای SIMD گسترش میدهد. برای x64، زمانی که هر یک از شش آرگومان اول، از نوع برداری (float ,double __m128 , __m256 و غیره) باشند، از طریق ثبّاتهای XMM / YMM مربوط منتقل میشوند. بهطور مشابه برای IA-32، حداکثر تا ۶ ثبّاتها XMM / YMM به صورت تکراری برای آرگومانها از نوع بردار از چپ به راست بدون در نظر گرفتن موقعیت قرار میگیرد. علاوه بر این، vectorcall__ پشتیبانی کردن از انتقال مجموعه ای از مقادیر همگن بردار (HVA) را اضافه کرد، که از نوع ترکیبی است که شامل بیش از چهار نوع بردار یکسان است، با استفاده از شش ثبّات یکسان. هنگامی که ثبّاتها برای آرگومانهای برداری اختصاص داده میشوند، ثبّاتهای استفاده نشده به آرگومانهای HVA از چپ به راست بدون توجه به موقعیت اختصاص داده میشوند. نتیجه نوع برداری و مقادیر HVA با استفاده از چهار ثبّات اول XMM / YMM بازگرداننده میشود.[۱۱]
قرارداد ثبّاتهای Borland
[ویرایش]ارزیابی آرگومانها از چپ به راست، سه آرگومان را از طریق EAX, EDX, ECX منتقل میکند. آرگومانهای باقی مانده در پشته از چپ به راست قرارداده میشوند.[۱۲] این قرارداد فراخوانیِ پیش فرضِ کامپایلر ۳۲ بیتی دلفی است، جایی که به عنوان ثبّات شناخته میشود. GCC این قرارداد فراخوانی را با گزینه regparm = ۳ پشتیبانی می کند. این به وسیلهٔ هسته لینوکس روی i386 از نسخه ۲٫۶٫۲۰ (منتشر شده در فوریه ۲۰۰۷) استفاده میشود.[۱۳] این قرارداد فراخوانی نیز توسط C ++ Builder Embarcadero استفاده میشود، جایی که fastcall__ نامیده میشود.[۱۴] در این کامپایلر، fastcall مایکروسافت را میتوان به عنوان msfastcall__ استفاده کرد.[۱۵]
قرارداد ثبّاتهای Watcom
[ویرایش]Watcom از کلمه کلیدی fastcall__ پشتیبانی نمیکند مگر این که نام مستعار آن را null کند. قرارداد فراخوانی ثبّات ممکن است توسط سوئیچ خط فرمان انتخاب شود. (با این حال، IDA از fastcall__ برای یکنواخت کردن استفاده میکند)
تا ۴ ثبّات به ترتیب به آرگومانهای eax, edx, ebx, ecx اختصاص داده میشوند. آرگومانها از چپ به راست به ثبّاتها اختصاص داده میشوند. اگر هر آرگومان نتواند به یک ثبّات تخصیص داده شود (میگویند خیلی بزرگ است)، آن و تمام آرگومانهای بعدی به پشته اختصاص داده میشوند. آرگومانهایی که به پشته اختصاص داده میشوند از راست به چپ قرار میگیرد. نامها با اضافه کردن پسوند _ تغییر میکنند.
توابع متغیر بر اساس قرارداد فراخوانی به پشتهٔ Watcom سقوط میکنند.
کامپایلر Watcom C / C ++ نیز از دستور pragma aux#[۱۶] که به کاربر اجازه میدهد که خودشان قرارداد فراخوانی خود را مشخص کنند. همانطور که در کتابچه راهنمای آن آمدهاست: «تعداد کمی از کاربران احتمالاً به این روش نیاز دارند، اما اگر لازم باشد، میتواند یک نجات دهنده باشد».
قرارداد TopSpeed / Clarion / JPI
[ویرایش]اولین چهار پارامتر عدد صحیح در ثبّاتهای، ebx, ecx و edx منتقل میشوند. پارامترهای اعشاری به پشته اعشاری منتقل میشوند - ثبّاتهای st0، st1، st2، st3، st4، st5 و st6. پارامترهای ساختاری همیشه به پشته منتقل میشوند. پارامترهای اضافی پس از خروج ثبّاتها به پشته منتقل میشوند. مقادیر عدد صحیح در eax، نشانگرها در edx و انواع اعداد اعشاری در st0 بازگرداننده میشوند.
قرارداد فراخوانیِ امن
[ویرایش]در Delphi و Free Pascal روی ویندوز مایکروسافت، قرارداد فراخوانی امن رسیدگی خطای (COM (Component Object Model را بستهبندی میکند، به این ترتیب استثناها به فراخوانیکننده نفوذ نمیکنند، اما در مقدار بازگشت HRESULT گزارش میشوند، همانطور که COM / OLE درخواست دادهاست. در هنگام فراخوانی یک تابعِ safecall از کد دلفی، دلفی به صورت خودکار HRESULT را بررسی میکند و در صورت لزوم استثنا را مطرح میکند.
قرارداد فراخوانی امن با قرارداد فراخوانی stdcall یکسان است، به جز اینکه آن استثناها به فراخوانیکننده در EAX به عنوان یک HResult (به جای در [FS:[0 ) منتقل میشوند، در حالی که نتیجه تابع توسط مرجع در پشته( هرچند آن پارامتر نهایی «خارج» باشد ) منتقل میشود. هنگام فراخوانی یک تابع دلفی از دلفی، این فراخوانی درست مثل هر قرارداد فراخوانی دیگر ظاهر میشود، زیرا هرچند استثنائات در EAX منتقل میشوند، آنها بهطور خودکار به وسیلهٔ فراخوانیکننده به حالت استثناء مناسب تبدیل میشوند. هنگام استفاده از اشیاء COM که در زبانهای دیگر ایجاد شدهاست، HResults به صورت خودکار به عنوان استثنا مطرح میشود و نتیجه برای دریافت توابع در آن نتیجه است نه یک پارامتر. هنگام ایجاد اشیاء COM در دلفی با استفاده از safecall، نیازی به نگرانی در مورد HResults وجود ندارد، زیرا استثنائات میتوانند به عنوان نرمال مطرح شوند اما به عنوان HResults در سایر زبانها دیده میشود.
function function_name(a: DWORD): DWORD; safecall;
یک نتیجه را برمیگرداند و استثناها را مانند یک تابع معمولی دلفی مطرح میکند، اما مقادیر و استثنائات را همانطور که بود میدهد:
function function_name(a: DWORD; out Result: DWORD): HResult; stdcall;
پاک شدن توسط فراخوانی کننده یا فراخوانی شونده
[ویرایش]قرارداد thiscall
[ویرایش]این قرارداد فراخوانی برای فراخوانی توابع عضو غیر استاتیک C ++ استفاده میشود. دو نسخه اصلی از thiscall بسته به نوع کامپایلر استفاده میشود و اینکه آیا این تابع از یک متغیر عددی از آرگومانها، استفاده میکند یا خیر.
برای کامپایلر GCC، قرارداد thiscall تقریباً با cdecl یکسان است :تماس گیرنده پشته را پاک میکند، و پارامترها با ترتیب راست به چپ منتقل میشوند. تفاوت در اضافه کردن اشاره گر this است که آخرین چیزی است که به پشته منتقل میشود، به شرط اینکه اولین پارامتر در نمونه اولیه تابع(function prototype) باشد.
در کامپایلر مایکروسافت ویژوال سی + +، اشاره گر this در ECX منتقل میشود و آن فراخوانی شونده است که پشته را پاک میکند، که منعکس کردن قرارداد stdcall مورد استفاده در C برای این کامپایلر و در توابع API ویندوز است. وقتی توابع از یک متغیر عددی از آرگومانها استفاده میکنند، فراخوانیکننده است که پشته را پاک میکند ( cf. cdecl ).
قرارداد فراخوانی thiscall فقط میتواند در Microsoft Visual C ++ 2005 به صراحت مشخص شود. در هر کامپایلر دیگر thiscall یک کلمه کلیدی نیست. (با این حال، disassemblerها، مانند IDA، باید آن را مشخص کنند؛ بنابراین IDA از کلید واژه thiscall__ برای this استفاده میکند)
حفظ ثبّات
[ویرایش]بخشی دیگر از یک قرارداد فراخوانی این است که کدام یک از ثبّاتها تضمین میکنند که بعد از فراخوانی فرعی، مقادیر خود را حفظ کنند.
ثبّاتهای ذخیره شده توسط فراخوانی کننده (فرار)
[ویرایش]با توجه به اینتل ABI که اکثریت کامپایلرها با آن مطابقت دارد، EAX, EDX و ECX برای استفاده در یک فرایند یا تابع آزاد هستند و لازم نیست که حفظ شوند [نیازمند منبع]
همانطور که از نامش بر میآید، این ثبّاتهای همه منظوره معمولاً اطلاعات موقت (بیثبات) را نگه میدارد که میتواند توسط هر تابع رونویسی شود.
بنابراین، این مسئولیت تماس گیرنده است که هر یک از این ثبّاتها را بر روی پشته قرار دهد، اگر مایل به بازگرداندن مقادیر خود پس از فراخوانی فرعی باشد.
ثبّاتهای ذخیره شده توسط فراخوانی شده (غیرقابل تغییر)
[ویرایش]دیگر ثبّاتها برای نگهداری مقادیر طولانی مدت (غیرقابل تغییر) استفاده میشود که باید در فراخوانیها حفظ شود.
به عبارت دیگر، هنگامی که فراخوانیکننده روند فراخوانی را اجرا میکند، میتواند انتظار داشته باشد که آن ثبّاتها پس از بازگشت از فراخوانی شده، همان مقدار را نگه داشتهاند.
بنابراین، فراخوانی شده مسئول هر دو کارِ ذخیره (قراردادن در پشته در ابتدا) و بازیابی (برداشتن از پشته به ترتیب) آنها، قبل از بازگشت به تماس گیرنده، میشود. همانطور که در مورد قبلی، این تمرین فقط باید در ثبّاتی انجام شود که فراخوانی شده آن را تغییر میدهد.
قراردادهای فراخوانی x86-64
[ویرایش]قراردادهای فراخوانی x86-64 از فضای اضافی ثبّات برای انتقال آرگومانهای بیشتر در ثبّاتها بهره میبرد. همچنین تعداد قرادادهای فراخوانی ناسازگار کاهش یافتهاست. دو مورد استفادهٔ رایج وجود دارد.
قرادادهای فراخوانی مایکروسافت x64
[ویرایش]قرادادهای فراخوانی مایکروسافت x64[۱۷][۱۸] در Windows و pre-boot UEFI (برای حالت طولانی در x86-64) دنبال میشود. این با استفاده از رشتههای RCX, RDX, R8، R9 برای چهار آرگومان عدد صحیح یا اشاره گر اول (به همان ترتیب) است و XMM0، XMM1، XMM2، XMM3 برای آرگومانهای اعشاری استفاده میشود. آرگومانهای اضافی به پشته (از چپ به راست) اضافه میشوند. مقادیر بازگشتی از نوع عدد صحیح (شبیه به x86) در RAX اگر ۶۴ بیت یا کمتر برگرداننده میشوند. مقادیر بازگشتی اعشاری در XMM0 بازگردانده میشوند. پارامترهایی که کمتر از ۶۴ بیت طول دارند، با صفر بسط داده نشدهاند؛ بیتهای بالا صفر نیستند.
هنگام کامپایل کردن معماری x64 در یک زمینه ویندوز (چه با استفاده از ابزارهای مایکروسافت یا غیر مایکروسافت)، فقط یک قراداد فراخوانی وجود دارد - همانطور که در اینجا توضیح داده شدهاست، به طوری که stdcall, thiscall, cdecl, fastcall و … در حال حاضر همه یکی و یکسان هستند.
در قراداد فراخوانی مایکروسافت x64، مسئولیت تماس گیرنده است که دقیقاً قبل از فراخوانی تابع (صرف نظر از تعداد واقعی پارامترهای مورد استفاده) ۳۲ بایت از فضای سایه(shadow space) را در پشته اختصاص داده و از پشته را بعد از فراخوانی بردارد. فضای سایه برای ریختن RCX, RDX, R8، و R9، استفاده میشود[۱۹] اما باید تمام توابع، حتی آنهایی که با کمتر از چهار پارامتر هستند، را در دسترس بسازد.
ثبّاتهای RAX, RCX, RDX, R8، R9، R10، R11 فرار هستند (ذخیره شده توسط فراخوانی کننده).[۲۰]
ثبّاتهای RBX, RBP, RDI, RSI, RSP, R12، R13، R14 و R15 به عنوان غیر فرار (ذخیره شده توسط فراخوانی شده) محسوب میشوند.[۲۰]
به عنوان مثال، یک تابع که ۵ آرگومان عدد صحیح میگیرد، اولین تا چهارم را در ثبّاتها میگیرد، و پنجم در بالای فضای سایه قرار میدهد؛ بنابراین هنگامی که تابع فراخوانی شده وارد میشود، پشته از (به ترتیب صعودی) آدرس برگشتی، و سپس فضای سایه (۳۲ بایت) و پارامتر پنجم تشکیل میشود.
در x86-64 ویژوال استودیو ۲۰۰۸ اعداد اعشاری را در XMM6 و XMM7 (و همچنین XMM8 از طریق XMM15) ذخیره میکند. به این ترتیب، برای x86-64، تابع زبان اسمبلیِ نوشته شده توسط کاربر باید XMM6 و XMM7 را حفظ کند (در مقایسه با x86 که در آن نیازی نیست تابع زبان اسمبلیِ نوشته شده توسط کاربر XMM6 و XMM7 را حفظ کند). به عبارت دیگر، تابع زبان اسمبلیِ نوشته شده توسط کاربر باید برای ذخیره / بازگرداندن XMM6 و XMM7 قبل / بعد از تابع در هنگام حمل از x86 به x86-64 به روز شود.
مایکروسافت با شروع از ویژوال استودیو ۲۰۱۳، قرارداد فراخوانی vectorcall__ را معرفی کرد که قرارداد x64 را گسترش میدهد.
سیستم V AMD64 ABI
[ویرایش]قرارداد فراخوانی System V AMD64 ABI در Solaris، Linux، FreeBSD، macOS[۲۱] دنبال میشود و عملاً استانداردی در میان سیستم عاملهای شبه یونیکس و یونیکس است. اولین شش آرگومان عدد صحیح یا اشاره گر در RDI, RSI, RDX, RCX, R8، R9 ثبت میشوند (R10 به عنوان یک نشانگر زنجیره ای استاتیک در مورد توابع توزیع شده[۲۲] : 21 )، در حالی که XMM0، XMM1، XMM2، XMM3، XMM4، XMM5، XMM6 و XMM7 برای برخی از آرگومانهای اعشاری استفاده میشود.[۲۲] : 22 همانطور که در قرارداد فراخوانی مایکروسافت x64، آرگومانهای اضافی به پشته منتقل میشوند[۲۲] : 22 . مقادیر بازگشتی صحیح تا ۶۴ بیت در اندازه در RAX ذخیره میشوند در حالی که مقادیری تا ۱۲۸ بیت در RAX و RDX ذخیره میشوند. مقادیر بازگشتی اعشاری بهطور مشابه در XMM0 و XMM1 ذخیره میشود.[۲۲] : 25 .
اگر فراخوانی شده مایل به استفاده از ثبّاتهای RBX, RBP، و R12-R15 باشد، باید قبل از بازگشت کنترل به دست تماس گیرنده، مقادیر اصلی خود را بازگردانی کند. در صورت تمایل فراخوانیکننده به حفظ مقادیر خود، تمام ثبّاتهای دیگر باید توسط تماس گیرنده ذخیره شوند.[۲۲] : 16
برای توابع گره برگ (توابع که هیچ تابع دیگری را فراخوانی نمیکند)، یک فضای ۱۲۸ بایت فقط در زیر اشاره گر پشته تابع ذخیره میشود. فضا به نام منطقه قرمز نامیده میشود. این منطقه توسط هر سیگنال یا مدیریت کنندگان وقفه (interrupt handlers) خراب نخواهد شد. کامپایلرها میتوانند از این منطقه برای ذخیره متغیرهای محلی استفاده کنند. کامپایلرها ممکن است برخی از دستورالعملها را در شروع عملیات (تنظیم RSP, RBP) با استفاده از این منطقه حذف کنند. با این حال، سایر توابع میتوانند این منطقه را خراب کنند؛ بنابراین، این منطقه فقط باید برای توابع گره برگ استفاده شود. gcc
و clang
پرچمِ -mno-red-zone
را برای غیرفعال کردن بهینهسازی قرمز منطقه ارائه دادند.
اگر فراخوانی شده یک تابع varadic باشد، سپس تعداد آرگومانهای اعشاری که به تابع در ثبّاتهای برداری منتقل میشود، باید توسط فراخوانیکننده در ثبّات AL ارائه شود.[۲۲] : 55
برخلاف قرارداد فراخوانی مایکروسافت، فضای سایه ارائه نشد؛ در ورودی تابع، آدرس بازگشتی مجاورِ عدد صحیح هفتم در پشته است.
فهرست قراردادهای فراخوانی معماری x86
[ویرایش]این لیستی از قراردادهای فراخوانی x86 است.[۴] این قراردادها عمدتاً برای کامپایلرهای C / C ++ (به ویژه بخش ۶۴ بیتی در زیر) تعریف شدهاند و در نتیجه موارد بسیار خاصی در نظر گرفته شدهاست. زبانهای دیگر ممکن است از فرمتها و قراردادهای دیگر در پیادهسازی خود استفاده کنند.
معماری | اسم قراداد فراخوانی | کامپایلرِ سیستم عامل | پارامترها در ثبّات | دستور پارامتر در پشته | پشته پاک میشود بهوسیلهٔ | توضیحات |
---|---|---|---|---|---|---|
۸۰۸۶ | cdecl | (RTL (C | Caller | |||
Pascal | LTR ((Pascal | Callee | ||||
fastcall | Microsoft (non-member) | AX, DX, BX | LTR (Pascal) | Callee | Return pointer in BX. | |
fastcall | Microsoft (member function) | AX, DX | LTR (Pascal) | Callee | “this” on stack low address. Return pointer in AX. | |
fastcall | Turbo C[۲۳] | AX, DX, BX | LTR (Pascal) | Callee | “this” on stack low address. Return pointer on stack high address. | |
Watcom | AX, DX, BX, CX | (RTL (C | Callee | Return pointer in SI. | ||
IA-32 | cdecl | GCC | (RTL (C | Caller | When returning struct/class, the calling code allocates space and passes a pointer to this space via a hidden parameter on the stack. The called function writes the return value to this address. | |
cdecl | Microsoft | (RTL (C | Caller | When returning struct/class,
| ||
stdcall | Microsoft | (RTL (C | Callee | |||
GCC | (RTL (C | Hybrid | Stack aligned on 16 bytes boundary. | |||
fastcall | Microsoft | ECX, EDX | (RTL (C | Callee | Return pointer on stack if not member function. | |
fastcall | GCC | ECX, EDX | (RTL (C | Callee | ||
ثبّات | Delphi و Free Pascal | EAX, EDX, ECX | LTR (Pascal) | Callee | ||
thiscall | Windows (Microsoft Visual C++) | ECX | (RTL (C | Callee | Default for member functions. | |
vectorcall | Windows (Microsoft Visual C++) | (RTL (C | ||||
Watcom compiler | EAX, EDX, EBX, ECX | (RTL (C | Callee | Return pointer in ESI. | ||
x86-64 | Microsoft x64 calling convention[۱۷] | Windows (Microsoft Visual C++, GCC, Intel C++ Compiler, Delphi), UEFI | RCX/XMM0, RDX/XMM1, R8/XMM2, R9/XMM3 | (RTL (C | Caller | Stack aligned on 16 bytes. 32 bytes shadow space on stack. The specified 8 registers can only be used for parameters 1 through 4.
For C++ classes, the hidden "this" parameter is the first parameter, and is passed in RCX.[۲۴] |
vectorcall | Windows (Microsoft Visual C++) | RCX/XMM0, RDX/XMM1, R8/XMM2, R9/XMM3 + XMM0-XMM5/YMM0-YMM5 | (RTL (C | Caller | [۲۵] | |
System V AMD64 ABI[۲۲] | Solaris, Linux, BSD, OS X (GCC, Intel C++ Compiler) | RDI, RSI, RDX, RCX, R8, R9, XMM0–7 | (RTL (C | Caller | پشته در ۱۶ بایت محدوده شدهاست. ۱۲۸ بایت محدوده قرمز زیر پشته هستند. رابط هسته از RDI, RSI, RDX, R10، R8 و R9 استفاده میکند. |
منابع
[ویرایش]Milind Girkar, Hongjiu Lu, David Kreitzer, Vyacheslav Zakharin, eds. (2013-07-17). "System V Application Binary Interface: AMD64 Architecture Processor Supplement (With LP64 and ILP32 Programming Models)" (PDF). 1.0.CS1 maint: Uses editors parameter (link)</ref>[۴][۲۱]
- "SYSTEM V APPLICATION BINARY INTERFACE Intel386 Architecture Processor Supplement" (PDF) (4th ed.). The Santa Cruz Operation, Inc. 1997-03-19.
{{cite journal}}
: Cite journal requires|journal=
(help) - Nemanja Trifunovic (2001-07-22). Sean Ewington (ed.). "Calling Conventions Demystified". The Code Project.
- Stephen J. Friedl. "Intel x86 Function-call Conventions — Assembly View". Steve Friedl's Unixwiz.net Tech Tips.
- "Visual Studio 2010 — Visual C++ Calling Convention". MSDN Library. Microsoft. 2010.
- Andreas Jonsson (2005-02-13). "Calling conventions on the x86 platform".
- Raymond Chen (2004-01-02). "The history of calling conventions, part 1". The Old New Thing.
- Raymond Chen (2004-01-07). "The history of calling conventions, part 2". The Old New Thing.
- Raymond Chen (2004-01-08). "The history of calling conventions, part 3". The Old New Thing.
- Raymond Chen (2004-01-13). "The history of calling conventions, part 4: ia64". The Old New Thing.
- Raymond Chen (2004-01-14). "The history of calling conventions, part 5; amd64". The Old New Thing.
برای خواندن بیشتر
[ویرایش]- ↑ Pollard, Jonathan de Boyne (2010). "The gen on function calling conventions". Frequently Given Answers.
- ↑ "GCC Bugzilla – Bug 40838 - gcc shouldn't assume that the stack is aligned". 2009.
- ↑ "System V Application Binary Interface: Intel 386 Architecture Processor Supplement" (PDF) (4th ed.).
- ↑ ۴٫۰ ۴٫۱ ۴٫۲ ۴٫۳ ۴٫۴ Agner Fog (2010-02-16). "توافقنامه تماس برای کامپایلرهای مختلف C ++ و سیستم عامل" (PDF).
- ↑ "GCC Bugzilla – Bug 40838 - gcc shouldn't assume that the stack is aligned". 2009.
- ↑ de Boyne Pollard, Jonathan (2010). "The gen on function calling conventions". Frequently Given Answers.
- ↑
- ↑ "__fastcall". MSDN. Retrieved 2013-09-26.
- ↑ Ohse, Uwe. "gcc attribute overview: function fastcall". ohse.de. Retrieved 2010-09-27.
- ↑ "Introducing 'Vector Calling Convention'". MSDN. Retrieved 2014-12-31.
- ↑ "__vectorcall". MSDN. Retrieved 2014-12-31.
- ↑ "Program Control: Register Convention". docwiki.embarcadero.com. 2010-06-01. Retrieved 2010-09-27.
- ↑ "i386: always enable regparm".
- ↑ "_fastcall, __fastcall". docwiki.embarcadero.com.
- ↑ "__msfastcall". docwiki.embarcadero.com.
- ↑ "Calling_Conventions: Specifying_Calling_Conventions_the_Watcom_Way". openwatcom.org. 2010-04-27. Archived from the original on 8 March 2021. Retrieved 2018-08-31.
- ↑ ۱۷٫۰ ۱۷٫۱ "x64 Software Conventions: Calling Conventions". msdn.microsoft.com. 2010. Archived from the original on 4 October 2018. Retrieved 2010-09-27.
- ↑ "x64 Architecture". msdn.microsoft.com.
- ↑ "x64 Software Conventions - Stack Allocation". Microsoft. Retrieved 2010-03-31.
- ↑ ۲۰٫۰ ۲۰٫۱ "Caller/Callee Saved Registers". msdn.microsoft.com/en-us/library/6t169e9c.aspx. Microsoft. Archived from the original on 13 February 2019. Retrieved 30 January 2019.
- ↑ ۲۱٫۰ ۲۱٫۱ "x86-64 Code Model". Mac Developer Library. Apple Inc. Archived from the original on 2016-03-10. Retrieved 2016-04-06.
The x86-64 environment in OS X has only one code model for user-space code. It is most similar to the small PIC model defined by the x86-64 System V ABI.
- ↑ ۲۲٫۰ ۲۲٫۱ ۲۲٫۲ ۲۲٫۳ ۲۲٫۴ ۲۲٫۵ ۲۲٫۶ Michael Matz, Jan Hubička, Andreas Jaeger, Mark Mitchell, Milind Girkar, Hongjiu Lu, David Kreitzer, Vyacheslav Zakharin, eds. (2013-07-17). "System V Application Binary Interface: AMD64 Architecture Processor Supplement (With LP64 and ILP32 Programming Models)" (PDF). 1.0.CS1 maint: Uses editors parameter (link)
- ↑ Borland C/C++ version 3.1 User Guide (PDF). Borland. 1992. pp. 158, 189–191.
- ↑ "Register Usage". Microsoft Docs. Microsoft. Archived from the original on 15 September 2017. Retrieved 15 September 2017.
- ↑ https://msdn.microsoft.com/en-us/library/dn375768.aspx