07 May 2010

x86 calling Convention

หลายคนอาจจะไม่ทราบ ว่า keyword พวก __stdcall, __cdecl, __fastcall, __thiscall พวกนี้มันคืออะไรหรือแตกต่างกันยังไง ซึงมันก็คือ Calling Convention มันเป็นรูปแบบของการเรียกฟังชั่น ว่าจะส่ง parameter ไปแบบไหน และจะจัดการกับ stack ที่ใช้ส่ง parameter ยังไง สำหรับวิธีใช้งานมันนั้น จะใส่ไว้ระหว่างชื่อฟังชั่นและชนิดตัวแปรที่รีเทิน ยกตัวอย่างเช่นการใช้ __stdcall

void __stdcall foo()

__stdcall
อันนี้เป็นแบบ standard ของ Windows API การส่ง parameter จะส่งผ่านทาง stack โดยจะ push parameter จากขวามาซ้าย (จากหลังมาหน้านั่นละ) ตัวฟังชั่นจะเคลียร์ stack ที่ใช้ส่ง parameter เอง ดังนั้น จะไม่สามารถใช้ va_arg (ฟังชั่นที่สามารถส่ง parameter ได้ไม่จำกัด ยกตัวอย่างเช่น printf) กับฟังชั่นประเภทนี้ได้ เพราะตัวฟังชั่นต้องรู้จำนวนที่แน่นอนของ parameter ที่ส่งไป ข้อดีคือ ส่วนมากฟังชั่นประเภทนี้จะใช้ instruction ret ในการเคลียร์ stack หลังจากเรียกฟังชั่นแล้วไม่ต้องมาเสียเวลาเคลียร์ stack ตัวอย่างการเรียกฟังชั่นประเภทนี้

push param3
push param2
push param1
call function

__cdecl
อันนี้เป็นแบบ standard ของภาษา C/C++ การส่ง parameter จะเหมือนกันกับ __stdcall จะต่างกันตรงที่ฟังชั่นจะไม่เคลียร์ stack ที่ใช้ส่ง parameter จุดที่เรียกฟังชั่นต้องเคลียร์ stack เอง ข้อดีคือ สามารถทำให้ฟังชั่นใช้ va_arg ได้ ตัวอย่างการเรียกฟังชั่นประเภทนี้
push param3
push param2
push param1
call function
add esp, 12

__fastcall
การส่ง parameter จะคล้ายๆกับ __cdecl แต่จะต่างกันตรงที่ parameter แรกจะส่งผ่านทาง Register ECX กับ parameter สองจะส่งผ่านทาง Register EDX สำหรับ Borland C++ จะส่ง parameter ที่สามผ่านทาง Register EAX ส่วน parameter ที่เหลือจะส่งผ่านทาง stack เหมือนกันกับ __cdecl และ __stdcall ตัวฟังชั่นจะไม่เคลียร์ stack เหมือนกับ __cdecl ข้อดีคือ ทำงานได้เร็วหาก parameter ที่จะส่งไปให้ฟังชั่นมีแค่ 2 ตัว (3 ตัวสำหรับ Borland C++) ตัวอย่างการเรียกฟังชั่นประเภทนี้ของ Visual C++

push param3
mov edx, param2
mov ecx, param1
call function
add esp, 4

__thiscall
อันนี้จะเป็นชนิดพิเศษ เราไม่ต้องใส่ keyword อันนี้ เพราะเป็นรูปแบบการเรียก method ของ class ที่ไม่ใช่ static และสามารถรวมร่างกับ 3 แบบข้างบนได้! การส่ง parameter จะขึ้นอยู่กับว่าเราให้มันรวมร่างกับแบบไหน ตัว Object ที่เรียก method จะถูกส่งผ่านทาง Register ECX (ส่งไปแบบ pointer keyword this ใน method มันคือ ECX นี่ละ) หากเรารวมร่างมันเข้ากับแบบ __fastcall parameter แรกจะกลายเป็น EDX แทน (เพราะ ECX โดน Object เอาไปกิน) ส่วนการเคลียร์ stack จะขึ้นอยู่กับว่ารวมร่างกับแบบไหนเช่นกัน

ส่วนค่าที่รีเทินมาจากทุกแบบ หากเป็นจำนวนเต็ม จะผ่านทาง Register EAX หากค่าที่จะรีเทินเป็น 64 บิท จะรีเทินผ่าน Register EAX กับ EDX หากค่าที่รีเทินเป็นทศนิยมจะรีเทินผ่านทาง Floating Point Stack ST(0) แต่ละแบบจะมีข้อดีแตกต่างกันออกไป หากฟังชั่นไม่ได้ใช้ va_arg ควรจะใช้ __stdcall เนื่องจากมันน่าจะเร็วกว่า __cdecl ตอนเคลียร์ stack แต่หากฟังชั่นใช้ va_arg ควรจะใช้ __cdecl ถ้าหากฟังชั่นส่ง parameter ไป 1 หรือ 2 ตัว (หรือ 3 ตัวสำหรับ Borland C++) ควรจะใช้ __fastcall

ที่มา : http://thaikore.spaces.live.com/blog/cns!C2E9AE9782AA6173!201.entry

0 comments:

Post a Comment