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

مشكلة النسخ من اليمين لليسار

بدأه C++er في 12 مارس 2011 · 14 رد · 1,283 مشاهدة · في أرشيف قسم الـــ Assembly
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

الدوال التاليه للنسخ من اليسار لليمين و لا توجد مشاكل بها، أول نسخه تقوم بالنسخ byte-by-byte و الأخري تقوم بنسخ عدد 4 بايت مره واحده و الباقى - أقل من 4 بايت - يتم نسخهم byte-by-byte:

void memcpy_ltr(void* dst, const void* src, size_t cnt)
{
   __asm
   {
      mov  esi, src
      mov  edi, dst
      mov  ecx, cnt
      cld
      rep movsb
   }
}


void memcpy2_ltr(void* dst, const void* src, size_t cnt)
{
   __asm
   {
      mov edi, dst
      mov esi, src
      mov ecx, cnt

      mov eax, ecx

      shr ecx, 2
      shl ecx, 2
      sub eax, ecx
      shr ecx, 2

      cld
      jz  REMAINS
      rep movsd

REMAINS:
      mov ecx, eax
      rep movsb
   }
}

لجعل النسخ يتم من اليمين لليسار لابد من جعل موقع مؤشر المصدر و الهدف عند أخر موقع لهما و كذلك جعل DF بواحد و ذلك لأن ESI و EDI سيتم طرح منهم قيمة البات التى تم نسخها، الكود التالي يقوم بذلك مع وجود مشكله سيتم ذكرها لاحقا:

void memcpy_rtl(void* dst, const void* src, size_t cnt)
{
   __asm
   {
      mov esi, src
      mov edi, dst
      mov ecx, cnt
      add esi, cnt
      add edi, cnt
      std
      rep movsb
   }
}

void memcpy2_rtl(void* dst, const void* src, size_t cnt)
{
   __asm
   {
      mov edi, dst
      mov esi, src
      mov ecx, cnt
      add esi, cnt
      add edi, cnt

      mov eax, ecx
      shr ecx, 2
      shl ecx, 2
      sub eax, ecx
      shr ecx, 2

      std
      jz  REMAINS
      rep movsd

REMAINS:
      mov ecx, eax
      rep movsb
   }
}

المشكله التى تحدث هي أن عملية النسخ من اليمين لليسار دائما ما تتخطي أخر مجموعه بمعنى إذا كان عدد البايت التى سيتم نسخها هو 11 و سيتم إستخدام الداله memcpy_rtl فسيتم نسخ أخر 10 بايت و أول بايت لن يتم نسخه، أما إذا تم إستخدام الداله memcpy2_rtl حينها تصبح الأحدى عشر بايت عباره عن مجموعتين بمساحة 4 بايت لكل واحده و يتبقى 3 بايت، بالنسبه للمجموعتين سيتم نسخ ثانيمها و تجاهل اولهما بالنسبه للـ 3 بايت المتبقيين سيتم نسخ أخر إثنان و تجاهل الاول.

أيضا إستخدام دوال النسخ من اليمين لليسار تسبب تلف الـ stack مع العلم ان الدوال مكتوبه بلغة السى و المترجم بشكل تلقائي يقوم بإضافة prolog and epilog لضمان عدم حدوث مثل هذا الأمر

لا اعرف لماذا تعمل هذه الدوال بهذا الشكل و اتمنى ان يستطيع احد ان يفسر لى هذا الأمر.

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

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

1

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

#2

فلنفترض انك تريد نسخ من 0x1000 الى 0x2000 بطول 4 بايت

بالنسبة لكودك فسيتم الاتي :

ESI = 0x1004
EDI = 0x2004
ECX = 4
Move bytes -> decrement ecx -> check if ecx = 0 ?
-----------------------------
ESI = 0x1003
EDI = 0x2003
ECX = 3
Move bytes -> decrement ecx -> check if ecx = 0 ?
-----------------------------
ESI = 0x1002
EDI = 0x2002
ECX = 2
Move bytes -> decrement ecx -> check if ecx = 0 ?
-----------------------------
ESI = 0x1001
EDI = 0x2001
ECX = 1
Move bytes -> decrement ecx -> check if ecx = 0 ?
ECX = 0
Stop cycling

كما ترى لم يتم نسخ اي شيء في 0x2000 لأن العداد توقف، وعوضا عن ذلك قمت بالكتابة في مكان لا تريد الكتابه به 0x2004 وستحدث عملية corruption .

انت تريد ان تكتب في 0x2000/0x2001/0x2002/0x2003 فقط وهي الاربع بايتات المحددة .

عليك بزيادة ESI/EDI بقيمة ECX - 1

تم تعديل هذه المشاركة بواسطة GamingMasteR في 13 مارس 2011 في 02:59

2

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#3

السلام عليكم،

الدالة memcpy_rtl:

تقوم هذه الدالة بنسخ عدد البايتات المحددة في المسجل ECX و المشار إليها بالمسجل ESI إلى الحجرة التي يشير إليها المسجل EDI من اليمين إلى اليسار. لذلك يجب الإشارة إلى آخر بايت نريد نقله و نشير إلى آخر بايت في الحجرة التي نريد النسخ فيها مع رفع راية الإتجاه (STD). المشكلة هنا أنه يبدأ النسخ من بعد آخر بايت من المصدر إلى ثاني بايت. لنأخذ المثال التالي لنفهم أكثر:

00403000 |  41 72 61 62 54 65 61 6D 00 00 00 00 00 00 00 00 |  ArabTeam........ 
pointer ->  0  1  2  3  4  5  6   7

حجم الكلمة "ArabTeam" هو 8. لاحظ أنّ مؤشر آخر حرف هو 7 يعني: مؤشر آخر حرف = عدد حروف الكلمة - 1

ما ينقس الكود الذي كتبته هو أن تنقس 1 من المسجلين ESI و EDI.

الدالة memcpy2_rtl:

تقوم التعليمة movsd بنسخ 4 بايتات إبتداءا من العنوان المشار إليه بالمسجل ESI إلى العنوان الموجد في EDI. تكون عملية النسخ بالتوازي يعني:

[EDI]<--[ESI]
[EDI+1]<--[ESI+1]
[EDI+2]<--[ESI+2]
[EDI+3]<--[ESI+3]

إذا كنا ننسخ من اليسار إلى اليمين بعد النسخ تقوم هذه التعليمة بإنقاص 4 من كلتا المسجلين (ESI و EDI)، ثم تنقص 1 من المسجل ECX -وذلك عند إستعمال هذه التعليمة مع تعليمة التكرير REP- و إذا أصبح صفر لا تكمل النسخ. يتم تكرار تنفيذ هذه التعليمة (movsd) حسب العدد المحدد في المسجل ECX. و بعد تنفيذ هاتين التعليمتين، يشير المسجل ESI إلى عنوان حجرة المصدر ناقص 4، و المسجل EDI إلى عنوان الحجرة المستهدفة ناقص 4 -و ذلك لأنها تتثبت من المسجل ECX بعد تجهيز المسجلين ESI و EDI-. مثال:

نريد نسخ 8 بايتات من الحجرة التي تبدأ في العنوان 403008h إلى الحجرة التي تبدأ في العنوان 403018. شكل الذاكرة:

00403000  00 00 00 00 00 00 00 00 41 72 61 62 54 65 61 6D  ........ArabTeam
00403010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

كود النسخ:

      mov ecx, 2 ; عدد البايتات=2*4=8
      mov esi, 403008h
      mov edi, 403018h
      add esi, 4 ; نشير إلى آخر 4 بايتات نريد نقلهم
      add edi, 4 ; نشير إلى آخر 4 بايتات لتستقبل
      std
      rep movsd

عند تنفيذ آخر تعليمة (rep movsd) يقوم المعالج بنقل 4 بايتات من العنوان 40300Ch و هي الكلمة "Team" إلى العنوان 40301Ch. ثم يطرح 4 من كلتا المسجلين ESI و EDI ثم ينقص 1 من ECX لتصبح 1. و يعود لنقل 4 بايتات من العنوان 403008h وهي الكلمة "Arab" إلى العنوان 403018. و تنقس 4 من المسجلين ESI و EDI ليصبح ESI=403004h و EDI=403014h.

بعد فهم عمل التعليمة movsd نعود إلى الدالة memcpy2_rtl.

الخطأ الأول:

      add esi, cnt
      add edi, cnt

لاحظ أنك أشرت إلى بعد آخر بايت من المصدر و المستهدف. لا كن نحن سننسخ بالأربع بايتات من اليسار إلى اليمين، يعني يجب أن نشير إلى آخر 4 بايتات ليبدأ النسخ من هناك. لتصحيح العملية، ننقص 4 من كلتا المسجلين ESI و EDI

الخطأ الثاني:

REMAINS:
      mov ecx, eax
      rep movsb

فكرتك أنك تنقل بالأربع بايتات في المرة قدر المستطاع، أما الباقي يتم نسخها بايت ببايت و عددها هو الذي تم حسابه و وضعه في المسجل EAX. نعلم أنّ عند إنتهاء تنفيذ التعليمة movsd الأولى سيصبح المسجل ESI يشير إلى عنوان آخر بايت مصدري تم نقله ناقص4 و المسجل EDI يشير إلى عنوان آخر بايت نُقل ناقص 4. لذلك علينا إظافة 3 إلى المسجلين ESI و EDI ليشيرا إلى باقي البايتات و نكمل عملية النقل.

الكود بعد التصحيح:

void memcpy_rtl(void* dst, const void* src, size_t cnt)
{
   __asm
   {
      mov esi, src
      mov edi, dst
      mov ecx, cnt
      add esi, cnt
      add edi, cnt
dec esi
dec edi
  std
      rep movsb
   }
}

void memcpy2_rtl(void* dst, const void* src, size_t cnt)
{
   __asm
   {
      mov edi, dst
      mov esi, src
      mov ecx, cnt
      add esi, cnt
      add edi, cnt
      sub esi,4
      sub edi,4

      mov eax, ecx
      shr ecx, 2
      shl ecx, 2
      sub eax, ecx
      shr ecx, 2

      std
      jz  REMAINS
      rep movsd

REMAINS:
      mov ecx, eax
      add esi, 3
      add edi, 3
      rep movsb
   }
}

نصيحة: لتبسيط أي شيئ في لغة الأسمبلي عليك أولا فهم آلية عمل التعليمة

جرب التغييرات و أخبرني إن كان هناك خطأ

أرجو أن أكون و فّقت في الشرح

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

2
#4

Z3r0n3

تمضي لأن تكون الأفضل، خطواتك ذهبية، واصل أخي بورك مسعاك.

GM

لك كل التحية

أخي علاء ... شكلك تحب سوالف النسخ. happy.gif

" إن الله كتب الإحسان على كل شيء"

::

الإرادة ... تحقق السيادة.

#5

شكرا للأخوه على المساعده، و شكر خاص لـ GM و Z3r0n3.

بإستخدام محرر Assembly الموجود بالفيجوال ستديو بالإضافه لملحوظة أخى GM استطعت الوصول للحل الذى قدمه الأخ Z3r0n3 و لكن المشكله التى لازالت قائمه هى stack corruption و التى احاول الوصول لحل لها حيث يبدو انها تحدث فقط اثناء الـ debug time.

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

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

#6

ربما سبب تلف الـstack هو بقاء الـDirection Flag على حالته (SET) مما يؤدي الى التلف لاحقا، اضف تعليمة CLD بعد النسخ ولاحظ ذلك مرة اخرى ...

1

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#7

ربنا يبارك لك يا GM فعلا كانت هى دي المشكله و تم الحل كما ذكرت.

أليس من المفترض أن المترجم يقوم بحفظ EFLAGS مع الدخول للداله و يقوم بإسترجاعه مع الخروج منها كما يحدث مع المسجلات العاديه التى يتم إستخدامها من قبلها؟

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

#8

لا يحدث هذا في الاكواد العادية، خصوصا ان الـreturn status يمكن ان يكون احد الـFlags في EFLAGS مثل الـZF أو CF، أحيانا تجد امر قفز مشروط على الـZF او الـCF مباشرة بعد بعض الدوال.

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#9

السلام عليكم،

اقتباس

Z3r0n3

تمضي لأن تكون الأفضل، خطواتك ذهبية، واصل أخي بورك مسعاك.

شكرا أخ A.S Hack

اقتباس
أخي علاء ... شكلك تحب سوالف النسخ

لاحظت هذا أيظا، دائما أسئلة الأخ علاء في هذا الاقسم تكون دائما على عمليات النسخ :happy:

اقتباس
و لكن المشكله التى لازالت قائمه هى stack corruption و التى احاول الوصول لحل لها حيث يبدو انها تحدث فقط اثناء الـ debug time.

ممكن ترفق لنا البرنامج -ليس كاملا لكن يحتوي على الدالتين ثم يقوم بإستدعائهما- لتنقيحه و معرفة الخطأ ثم تصحيحه

اقتباس
ربما سبب تلف الـstack هو بقاء الـDirection Flag على حالته (SET) مما يؤدي الى التلف لاحقا، اضف تعليمة CLD بعد النسخ ولاحظ ذلك مرة اخرى ...

لماذا يحدث الخطأ عند بقاء راية الإتجاه مرفوعة؟

تم تعديل هذه المشاركة بواسطة Z3r0n3 في 15 مارس 2011 في 16:00

#10
اقتباس
لماذا يحدث الخطأ عند بقاء راية الإتجاه مرفوعة؟

لأن البرنامج بشكل او اخر سيقوم باستخدام تعليمات النسخ لنسخ بعض الداتا من هنا الى هناك وعندها تحدث المشكلة، ليس بالضرورة ان تكون المشكلة هي Stack corruption ، ربما يحدث قبل ذلك Corruption في مكان آخر.

تم تعديل هذه المشاركة بواسطة GamingMasteR في 15 مارس 2011 في 17:20

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#11
اقتباس
لأن البرنامج بشكل او اخر سيقوم باستخدام تعليمات النسخ لنسخ بعض الداتا من هنا الى هناك وعندها تحدث المشكلة، ليس بالضرورة ان تكون المشكلة هي Stack corruption ، ربما يحدث قبل ذلك Corruption في مكان آخر.

يعني المشكلة تقع بسبب المبرمج لأنه نسي وضع التعليمة CLD قبل البدأ في عملية النسخ. هل هذا صحيح؟

#12

الطبيعي ان يكون الـDirection Flag بحالة Clear، العيب ان يقوم المبرمج بوضع العلم وتجاهل مسحه من وجهة نظري smile.gif

لأن الشائع ان يكون النسخ من اليسار الى اليمين وهي عملية تكان لا تخلو منها معظم الدوال والاجراءات في البرامج، اذن من المكلف ان تقوم بحشر التعليمة CLD قبل اي عملية نسخ بل الافضل وضعها بعد عملية النسخ من اليمين الى اليسار وهي عملية نادرة.

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#13
اقتباس
لا يحدث هذا في الاكواد العادية، خصوصا ان الـreturn status يمكن ان يكون احد الـFlags في EFLAGS مثل الـZF أو CF، أحيانا تجد امر قفز مشروط على الـZF او الـCF مباشرة بعد بعض الدوال.

هل تقصد شئ مثل هذا:

call ComeFunction
jz   ZERO_JUMP
   .
   .
   .
   .
ZERO_JUMP:

من سيقوم بكتابة كود مثل هذا؟ حتى داخل الكرنل التى ادرسها لم اري عمليات مشابهه، ممكن مثال بسيط؟

اقتباس

لأن الشائع ان يكون النسخ من اليسار الى اليمين وهي عملية تكان لا تخلو منها معظم الدوال والاجراءات في البرامج، اذن من المكلف ان تقوم بحشر التعليمة CLD قبل اي عملية نسخ بل الافضل وضعها بعد عملية النسخ من اليمين الى اليسار وهي عملية نادرة.

هل هذا يعنى انه لابد مني كمبرمج ان اعيد الـ EFLAGS التى استخدمتها إلى حالتها الإفتراضيه قبل الخروج من الداله؟

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

#14
اقتباس
من سيقوم بكتابة كود مثل هذا؟ حتى داخل الكرنل التى ادرسها لم اري عمليات مشابهه، ممكن مثال بسيط؟

اذكر اني رأيت شيء كهذا ايام كسر البرامج، وتقريبا كانت برامج دلفي ...

اقتباس
هل هذا يعنى انه لابد مني كمبرمج ان اعيد الـ EFLAGS التى استخدمتها إلى حالتها الإفتراضيه قبل الخروج من الداله؟

لا أظن ذلك لأن هذه العملية مكلفة، تخيل برنامج كبير به كم الف اجراء.

هذا الموضوع يحتاج الى دراسة لمعرفة ما هو الوضع الافتراضي الذي ستعمل معظم التطبيقات عليه دون مشاكل وبالتالي استرجاء FLAG معين يكون بعد تغييره اذا ما كان سيؤثر على باقي الاجراءات كحالة DF المذكورة، في حين ان علم كـZF لن يؤثر تغييره على باقي الكود لأن من الطبيعي قبل اختباره ان يتم وضحه/مسحة عن طريق تعليمات مثل cmp/test .

1

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#15
اقتباس

إذا كنا ننسخ من اليسار إلى اليمين بعد النسخ تقوم هذه التعليمة بإنقاص 4 من كلتا المسجلين (ESI و EDI)

اقتباس

لاحظ أنك أشرت إلى بعد آخر بايت من المصدر و المستهدف. لا كن نحن سننسخ بالأربع بايتات من اليسار إلى اليمين، يعني يجب أن نشير إلى آخر 4 بايتات ليبدأ النسخ من هناك. لتصحيح العملية، ننقص 4 من كلتا المسجلين ESI و EDI

أقصد في كلتا الإقتباسين "من اليمين إلى اليسار"

تم تعديل هذه المشاركة بواسطة Z3r0n3 في 19 مارس 2011 في 15:28

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