الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

المعاملات فى الدوال التى لا تحدد عددهم

بدأه C++er في 22 مايو 2011 · 1 رد · 739 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

المتطلبات: يكون القاري على درايه بالـ 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

و الله ولي التوفيق

تم تعديل هذه المشاركة بواسطة محمد علاء الدين في 22 مايو 2011 في 06:35

1

مدونتي: C++ Tips and Tricks

#2

بجد ولله فيك الخير انا كنت لسه ببحث علي الموضوع ده شكرا

وده مثال

بسيط اضافه لمثال محمد

typedef int (__cdecl * Chat_org)(char, char*, int); // real func

void KalClient::Chat(int color,char* mFormat,...)
{ 
	char* mText = new char[255];
	va_list args;
	va_start(args, mFormat);
	vsprintf_s(mText,255,mFormat,args);
	va_end(args);

	((Chat_org)Chat_add)(0,mText,color);
}

KalClient::Chat(lightblue," DWORD: %d - INT: %i - STRING: %s",25000,25,"STRING");

تم تعديل هذه المشاركة بواسطة Horus في 22 مايو 2011 في 14:10

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…