تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
1.6 استكشاف الlinking من الداخل بشكل عميق مع  النوعين static  و dynamic
#1
[صورة مرفقة: 128383135-210777607283724-8238622407463275907-n.jpg]


رحلة في عالم البرمجة: إتقان أساسيات البرمجة وتطبيقاتها باستخدام لغة C++
 
https://www.youtube.com/playlist?list=PLmOrGO9IfJEcDyuBLOX5AzAqaKxmAKHrq





# الفصل 0 : تمهيد # الفصل 1 :

[صورة مرفقة: mqdefault.jpg]

 
https://www.youtube.com/watch?v=kQQyleLZViA




عادةً ما يتكون البرنامج من عدة أجزاء منفصلة ، وغالبًا ما يتم تطويرها بواسطة أشخاص مختلفين. على سبيل المثال ، يتكون هذا البرنامج من الجزء الذي كتبناه بالإضافة إلى أجزاء من مكتبة C++ standard.

يجب تجميع هذه الأجزاء المنفصلة (المعروفة أحيانًا بال modules أو  translation units) ويجب ربط ملفات object code الناتجة معًا لتكوين برنامج قابل للتنفيذ.
يُطلق على البرنامج الذي يربط هذه الأجزاء معًا (بلا داعٍ للدهشة) اسم الLinker.

يسمى ناتج الربط ملفًا قابل للتنفيذ ، وفي نظام التشغيل Windows ، غالبًا ما يُعطى اسمه لاحقة .exe.
يرجى ملاحظة أن رمز الكائن والملفات القابلة للتنفيذ غير قابلة للتنقل بين الأنظمة. على سبيل المثال ، عند الcompiling لجهاز يعمل بنظام التشغيل Windows ، تحصل على object code لنظام التشغيل Windows لن يعمل على جهاز يعمل بنظام التشغيل Linux.



الlibrary هي ببساطة بعض التعليمات البرمجية - عادةً ما يتم كتابتها بواسطة آخرين - التي نصل إليها باستخدام الdeclarations الموجودة في imported module. 

الdeclaration هو عبارة عن  برنامج statement يحدد كيفية استخدام قطعة من التعليمات البرمجية.

الأخطاء التي يجدها الcompiler تسمى أخطاء وقت التجميع compile-time errors ، والأخطاء التي يجدها الlinker تسمى أخطاء وقت الارتباط link-time errors ، والأخطاء التي لا يتم العثور عليها إلا حتى يتم تشغيل البرنامج تسمى أخطاء وقت التشغيل أو أخطاء المنطق  run-time errors or
logic errors.
بشكل عام ، يكون من الأسهل فهم أخطاء compile-time وإصلاحها من أخطاء link-time ، وغالبًا ما يكون من الأسهل العثور على أخطاء link-time وإصلاحها من أخطاء run-time.




الآن ، سنتحدث عن أساسيات الlinking الثابت والديناميكي static and dynamic linking.

غالبًا ما نرغب في استخدام كود تم تنفيذه كجزء من مكتبة ما ، ومن الأمثلة الرائعة على ذلك أشياء من مكتبة C ++ الstandard. الطريقة التي نستخدم بها هذا الكود هي من خلال الlinking.

لذلك ، سيكون علينا الlinking بهذه المكتبة للوصول إلى الكود الذي نريد استخدامه ، وهناك نوعان رئيسيان من الlinking ، static linking و dynamic linking.

لذلك ، سننظر في أساسيات هذين الشكلين من الlinking اليوم وكيف يختلفان باستخدام مثال بسيط للغاية.


سنستخدم ملف main.cpp كمثال بسيط فهو برنامج صغير جدًا. يحتوي هذا الملف على وظيفة رئيسية تقوم بالطباعة باستخدام الأمر s t d c out ثم إنهاء البرنامج.

الآن, سنرى الفرق بين ربط هذا البرنامج بالمكتبة الأساسية بشكل ديناميكي أو بشكل ثابت. بشكل افتراضي, عند استخدام أدوات مثل G++ لإنشاء ملف قابل للتنفيذ, يتم ربطه بالمكتبة الأساسية بشكل ديناميكي.

على سبيل المثال, إذا قمنا بتجميع main.cpp باستخدام G++ وإنشاء ملف قابل للتنفيذ باسم main_dynamic, فسيتم ربطه بالمكتبة الأساسية بشكل ديناميكي.



الآن يمكننا تغيير هذا وإخبار برنامج الcompiler الخاص بنا أو برنامج تشغيل الcompiler أننا نرغب في الlinking الثابت بهذا المكتبة الstandard الخاصة بنا.
في حالة GCC ، ستكون هذه المكتبة lib s t d c++ . s o. إذن يمكننا إخبار الcompiler الخاص بنا أننا نريد القيام بذلك من خلال flag بسيط هنا.
لذلك دعونا نتحرك ونغير إخراجنا قابل للتنفيذ إلى main_static ونقوم بتمرير هذا الflag ب -static-libstdc++ ، كما ترى.

لقد قمنا بإنشاء executables قابلين للتنفيذ مختلفين هنا حتى نتمكن من تشغيل كليهما ونرى أنهما يعطياننا نفس النتيجة ، وكلاهما سيطبع "A T 4 R E". ولكن الفرق يكمن حقًا في الداخل.

لذلك ، أول شيء يمكننا النظر إليه هو الdependencies التي يمتلكها هذان القابلان للتنفيذ ، على وجه التحديد الdependencies على المكتبات المشتركة shared libraries.

إحدى الطرق التي يمكننا من خلالها فحص ذلك هي من خلال أداة LDD. 
يمكننا إذن القيام بـ ldd على مثل main_dynamic للحصول على قائمة بالdependencies ويمكنك أن ترى هنا هذه المكتبات المشتركة التي نعتمد عليها هي lib s t d c++ . s o و يعطينا موقع location هذه المكتبة المشتركة.


إذن فهذا هو implementation  للshared Library هنا.

لذلك ، إذا أردنا تشغيل هذا الملف التنفيذي  main_synamic ، فسيحتاج Dynamic Linker إلى القدرة على العثور على هذه المكتبة المشتركة الصحيحة هنا. 
نحن نعتمد على تنفيذ الأشياء الموجودة داخل مكتبة lib s t d c++ . s o.

لكن هناك اختلاف جوهري بين هذا المثال dynamic والمثال الstatic  فإذا قمنا بتنفيذ الأمر ldd على Main Static، نرى أننا لم نعد نعتمد على مكتبة lib s t d c++ .so.
صحيح أن لدينا بعض المكتبات المشتركة الأخرى هنا التي لم يتم ربطها بطريقة ثابتة ، لكننا لم نعد نمتلك مكتبة lib s t d c++ .so ضمن قائمة الdependencies.

إذن ماذا يعني هذا بالضبط؟ لماذا لم نعد نمتلك هذه المكتبة هنا؟ السبب هو أننا قمنا بالربط الثابت في المثال الثاني ، أي قمنا باستخراج المعلومات التي نحتاجها من تلك المكتبة الstandard وقمنا ببنائتها داخل ملفنا التنفيذي.

وعليه ، فإننا لم نعد بحاجة إلى الوصول إلى مكتبة lib s t d c++ . so في وقت التشغيل (runtime). إذن ، يعد هذا أحد الاختلافات الرئيسية بين الربط الديناميكي والربط الثابت. فمع الربط الديناميكي ، نحتاج إلى تحميل الكود الذي نحتاجه من مكتبتنا المشتركة للملف التنفيذي في وقت التشغيل ، بينما يكون هذا الكود مضمنًا بالفعل كجزء من ملفنا التنفيذي في حالة الربط الثابت.


وبإمكاننا أيضًا ملاحظة ذلك من خلال مجرد فحص حجم ملفينا التنفيذيين. فإذا قمنا بتنفيذ الأمر ls -la ، سترى أن حجم الكود الlinked بطريقة ثابتة أكبر بكثير من حجم الكود الlinked بطريقة ديناميكية.

والسبب في ذلك أن العناصر التي احتجناها من مكتبة libstdc++ مضمنة الآن في هذا الملف التنفيذي (main_static) ، بينما تظل موجودة في مكتبة lib s t d c++ .so المشتركة في حالة الملف main_dynamic. لذلك ، سيتم تحميل هذه العناصر فقط في وقت التشغيل بدلاً من دمجها ضمن الملف التنفيذي.

بالإمكان أيضًا تفكيك disassemble هذين الملفين التنفيذيين لرؤية المزيد من التفاصيل حول ما يحدث خلف الكواليس.

لنستخدم إذن الأمر objdump أو object dump لتفكيك الملفات التنفيذية.
سنستخدم objdump dash DC لتفكيك الملف واستخراج الأسماء ، وسنبدأ بـ main_dynamic مع إعادة توجيه الإخراج إلى ملف يسمى out_dynamic.s ، ثم نفعل الشيء نفسه مع مثالنا الlinked بطريقة ثابتة main_static.

بعد ذلك، يمكننا فتح ملفات المفككة الdisassembled هذه باستخدام محرر نصوص مثل vim. سنفتح الملفين out_dynamic.s و out_static.s.


حسنًا ، إذن لدينا هنا ملفانا التنفيذيان بعد تفكيكهما. دعنا ننتقل إلى تعريف function main (المكان الذي توجد فيه function main).
نتحقق في كلا الملفين وسنرى أنهما متشابهتان إلى حد كبير ولا يوجد بينهما سوى اختلافات بسيطة.

لا يزال لدينا عنصر load effective address لـ std c out ، لكننا نرى هنا وجود G lib c x x 3.4 بينما نمتلك فقط s t d c out في نسختنا المرتبطة بطريقة static.
بعد ذلك توجد لدينا عملية الاستدعاء call .
علامة اصغر من مرتين في كلا المثالين ، والتي نستخدمها لطباعة الstring.

لننتابع الآن هذا العنوان الذي نستدعيه.
ننفذ عملية الاستدعاء call 1080 في مثالنا الlinked بطريقة ديناميكية ، ثم نتابع أيضًا عنوان 7830 في مثالنا الlinked بطريقة ثابتة. سنرى هنا اختلافًا كبيرًا.

داخل مثالنا الlinked بطريقة ديناميكية ، نرى وجود ملحق P L T , في نهاية output operator .
يرمز P L T إلى جدول ربط الإجراءات (Procedure Linkage Table) ، ويمكنك أن تلاحظ أنه بدلاً من وجود function كاملة هنا تنفذ output operator لكتابة المعلومات ب c out ، لدينا مجرد نموذج (stub) لfunction بدلاً من ذلك.

والسبب في ذلك هو أن التنفيذ الفعلي لهذه الfunction لا يوجد ضمن هذا الملف.


حسنًا ، نستخدم عناصر مثل جدول ربط الإجراءات (P L T) وجدول الإزاحة العام ( - G O T) Global offset table للانتقال jump إلى الfunction الصحيحة في وقت التشغيل run-time بعد أن يقوم linker الديناميكي بتحميل (load) هذه المعلومات من المكتبة المشتركة.

أما بالنسبة للمثال الlinked بطريقة static (أسفل الشاشة) ، فيمكنك أن تلاحظ أننا نمتلك فقط تنفيذ هذه الfunction مباشرةً.
لقد تم ربطها بطريقة ثابتة ، حيث قمنا باستخراج المعلومات التي نحتاجها من مكتبة lib s t d c++ .so وتم بناءها - built in  - كجزء من ملفنا التنفيذي.

هذه إذن بعض الاختلافات بين الربط الثابت static والربط الديناميكي.
يكمن الاختلاف الجوهري في مكان وجود الكود الذي نحاول الوصول إليه من هذه المكتبة بالنسبة إلى ملفنا التنفيذي النهائي. 
في حالة الربط الديناميكي ، يظل الكود الخاص بالمكتبة موجودًا كجزء من مكتبة مشتركة ، ونقوم بتحميله في وقت التشغيل مباشرةً عند الحاجة إلى استخدامه.

أما بالنسبة للمثال الlinked بطريقة ثابتة ، فإننا نقوم ببناء هذا الكود كجزء من ملفنا التنفيذي ، مما يلغي الاعتماد على المكتبة الخارجية.
كما يمكنك أن تتخيل ، هناك عدد من المقايضات المختلفة هنا. توجد مقايضات متعلقة بالوقت الذي يستغرقه تحميل الأشياء من المكتبات المشتركة في حالة الربط الديناميكي ، ولكن هناك أيضًا مقايضات متعلقة بحجم الملفات التنفيذية في حالة الربط الثابت حيث يتعين تحميل هذه الملفات التنفيذية الكبيرة بالكامل.

هذه إذن أساسيات الربط الثابت والربط الديناميكي ، وسنتحدث عنها بالتفصيل لاحقًا خاصة عندما نتطرق إلى بناء مكتباتنا المشتركة الخاصة والربط ضدها. أتمنى لك يوما سعيدًا.

 flower
قال الخضر لموسى: ما نقص علمي وعلمك من علم الله إلا كنقرة هذا العصفور في البحر.
 
R333T | Full-Stack & DevOps Engineer | Reverse Engineering Enthusiast

Password always: AT4RE

All files shared with AT4RE Community: https://t.me/+92mxXRqUvYNhOTM0


You may view threads: 3892, 3966 & 3972
أعضاء أعجبوا بهذه المشاركة : Ogredbg


التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم