المتطلبات: يكون القاري على درايه بالـ stack frame و بعض الأسمبلي.
هذا المقال ينطبق فقط على cdecl calling convention
احيانا نحتاج كتابة دوال عدد معاملتها يختلف بإختلاف الإستدعاء مثل:
int sum(int count, ...);
دائما ما نحتاج لتمرير عدد المعاملات لنعرف كم معامل سيتم الحصول عليه بإستخدام va_list، المشكله التى كانت تواجهني تتمثل فى عدم وجود المعامل الأول حيث حينها سأحتاج لإيجاد وسيله للحصول على عدد المعاملات و كذلك عنوان اول معامل حتى استطيع الوصول للذي يليه و هكذا.
بإستخدام لغة السي أو السي++ لا ترهق نفسك لن تفعلها و لكن بوجود الأسمبلي تم حل المشكله، كود الدالتين:
__declspec(naked)
unsigned get_bytes()
{
__asm
{
mov ebx, ebp
add ebx, 4
mov ebx, [ebx]
mov ebx, [ebx]
mov eax, ebx
and ebx, 0xffff
cmp ebx, 0xc483
jne NOT_EQUAL
shr eax, 10h
and eax, 0xff
ret
NOT_EQUAL:
mov eax, 0
ret
}
}
__declspec(naked)
void* get_first_param()
{
__asm
{
call get_bytes
cmp eax, 0
jne GET_ADDR
ret
GET_ADDR:
mov eax, ebp
add eax, 8
ret
}
}نلاحظ فى تعريف الدالتين وجود __declspec(naked) هذا السطر هام جدا جدا حيث ان المترجم يقوم بشكل افتراضي ببناء stack frame للدوال و في حالتنا هذه فإننا نريد من الدالتين بالأعلي الوصول لمحتويات اخر داله تم انشاء stack frame لها للحصول على بيانات المعاملات منها و بهذا السطر فإننا نخبر المترجم انه لا داعي من وضع Epilog و الـ prolog لهذه الدوال و اننا نعلم جيدا ما نفعله.
الداله الأولي تقوم بالحصول على عدد المعاملات بشكل طريف نوعا ما لنفهم الكود السابق انظر معي الكود التالي:
int sum(...)
{
unsigned bytes = get_bytes();
return 0;
}
int main()
{
sum(3, 4, 5, 6, 7);
}الداله main تقوم بإستدعاء الداله sum و تقوم بتمرير 5 معاملات مساحة كل واحد منهم 4 بايت لذا فإن المحصله هي 20 بايت و حيث ان هذه المساحه تم حجزها من داخل الداله main فإن اول سطر بعد استدعاء الداله sum هو أمر بحذف 20 بايت من الـ stack، و حيث ان الداله sum تم إنشاء لها stack frame لذا كل ما افعله هو من داخل الداله sum أقوم بإستدعاء الداله get_bytes و التى لا يتم انشاء stack frame لها و بالتالي فإنه من خلالها استطيع التعامل مع الـ stack frame للداله sum و الذى من خلال قيمة المسجل ebp استطعت الوصول للـ return address داخل الداله main، الـ return address هو السطر الذى يحتوى على امر تفريغ الـ stack و الذى إذا كان موجود يأخذ الشكل التالي:
add esp, xxh
لذا ما افعله هو الحصول على الـ machine code و اقوم بإستخلاص القيمه التى يتم حذفها من الـ stack، هذه القيمه هي نفسها عدد البايت التى تم حجزها للمعاملات الممرره للداله sum.
إذا كان الـ machine code الموجود عند الـ return address لا يطابق التعليمه الخاصه بتحرير مساحة الـ stack حينها اعرف ان الداله لا تأخذ معاملات و بالتالي أقوم بإرجاع صفر.
الداله الثانيه تقوم بإرجاع عنوان المعامل الأول إن كان موجود و صفر غير ذلك.
كلتا الدلتين يعملوا مع أى داله بشرط ان يكون اسلوب استدعائها هو cdecl
و الله ولي التوفيق