أقدم لكم ترجمة مقالتي من مدونة مشروع دارلينج. القليل من المساعدة حول المفاهيم المستخدمة: داروين - نظام تشغيل مفتوح المصدر يقوم على macOS و iOS وأنظمة تشغيل أخرى من Apple ؛ Mach-O هو تنسيق ثنائي للملفات والمكتبات القابلة للتنفيذ المستخدمة في داروين ؛ dyld - أداة تحميل ديناميكية تستخدمها داروين لتنزيل ملفات Mach-O ؛ dylib هي مكتبة قابلة للتحميل ديناميكيًا (عادةً ما يكون لها الامتداد .dylib
).

الهدف من مشروع Darling هو تمكين تطبيقات macOS من العمل على Linux ، والقدرة على تنزيل الملفات الثنائية بتنسيق Mach-O هي إحدى الخطوات الرئيسية نحو هذا الهدف.
في البداية ، تم بناء دارلينج حول تنفيذ محمل Mach-O وفكرة بث المكالمات بين واجهة برمجة تطبيقات داروين عالية المستوى ونظرائها في لينكس. منذ ذلك الحين ، تحول تركيزنا إلى تشغيل التعليمات البرمجية في حاوية داروين المعزولة بشكل متزايد. نظرًا لأننا تحولنا إلى استخدام Mach-O للمكونات الداخلية لـ Darling ، فقد تمكنا من استخدام dyld الأصلي لـ Apple ، بالإضافة إلى إنشاء العديد من مكونات Darwin مفتوحة المصدر الأخرى. ما زلنا بحاجة إلى محمل إقلاع بسيط من نوع Mach-O للتمهيد نفسه.
ربما تكون Mach-O ، إلى جانب Mach نفسها ، من أبرز العلامات المميزة لداروين ، وتستخدم المكتبات والأطر المختلفة التي توفرها Apple على نطاق واسع العديد من الميزات الغامضة لتنسيق Mach-O. هذا يجعل العمل مع Mach-O أحد أهم المهام وأبرزها في تطوير Darling. من تنفيذ لوادر Mach-O الأصلية إلى تجميع مكونات داروين (أولاً في شكل ملفات ELF خاصة ، والآن في شكل Mach-O حقيقي) - نحتاج إلى فهم البنية الداخلية لـ Mach-O على مستوى أعمق بكثير مما هو مطلوب عادةً من العادي مطوري لمنصة داروين.
ننتهي من هذه المقدمة وننتقل إلى مناقشة بعض الحيل التي يمكن أن يقدمها لنا تنسيق Mach-O.
أسماء التثبيت
في نظامي التشغيل Windows و Linux ، تتم الإشارة إلى المكتبات الديناميكية باسمها (على سبيل المثال ، libc.so
) ، وبعد ذلك تكون مهمة الرابط الديناميكي هي العثور على مكتبة باسم مطابق في أحد أدلة المكتبة القياسية. يستخدم داروين على الفور المسار الكامل (تقريبًا) لملف المكتبة بدلاً من ذلك ، والذي يسمى اسم التثبيت لهذه المكتبة. من المفترض أن هذا تم لمنع استبدال dylib [dylib hijacking] - هجوم يتم فيه وضع dylib مزيف في دليل حيث يتم العثور عليه بواسطة رابط ديناميكي في وقت سابق عن الرابط الحقيقي ، والذي يسمح لـ dylib المزيف بتنفيذ رمز تعسفي نيابة عن البرنامج ، مما أقنع بذلك تحميل ديليب.
لا تقوم الملفات التنفيذية والمكتبات فقط بتخزين أسماء التثبيت الكاملة الخاصة بتبعياتها ، ولكن تبعيات Mach-O نفسها "تعرف" أسماء التثبيت الخاصة بها. في الواقع ، هذه هي الطريقة التي يتعرف بها الرابط على أسماء التثبيت التي يجب استخدامها في التبعيات: فهو يقرأها من التبعيات نفسها.
عند ربط dylib ، فإنك تحدد اسم التثبيت باستخدام الخيارات ld -dylib_install_name
أو -dylib_install_name
:
$ ld -o libfoo.dylib foo.o -install_name /usr/local/lib/libfoo.dylib
الآن عند ربط ملف Mach-O آخر (قل libbar.dylib
) libfoo.dylib
، سوف يكتب ld اسم تثبيت libfoo - /usr/local/lib/libfoo.dylib
- في libbar
تبعيات libbar
، وهنا يكون dyld سيبحث عن libfoo
في وقت التشغيل.
استخدام المسار الكامل يعمل بشكل جيد بما فيه الكفاية لمكتبات النظام ، والتي ، في الواقع ، مثبتة في أماكن معروفة سابقًا في نظام الملفات ؛ ولكن مع المكتبات التي تأتي كجزء من حزم التطبيق ، هناك مشكلة. على الرغم من أن كل تطبيق يمكن أن يفترض أنه سيتم تثبيته في /Applications/AppName.app
، بشكل عام ، فهذا يعني أن تجميعات التطبيقات محمولة ويمكن نقلها بحرية حول نظام الملفات ، لذلك لا يمكن لمسار المكتبة المحدد داخل هذه التجميعات تكون معروفة مقدما.
كحل لهذه المشكلة ، يسمح داروين لأسماء التثبيت أن تبدأ بـ @executable_path
أو @rpath
أو @rpath
- وهذا ليس مطلقًا ، ولكن يشير إلى مسار المكتبة نسبة إلى المسار إلى الملف القابل للتنفيذ الرئيسي ، المسار إلى "تحميل" (ملف أو مكتبة قابلة للتنفيذ ، والتي تعتمد بشكل مباشر على هذه المكتبة) أو نسبة إلى قائمة المسارات المحددة بواسطة الملف التنفيذي الرئيسي ، على التوالي. @executable_path
و @loader_path
بدون تعقيد إضافي ، ولكن إذا كان على الأقل أحد @executable_path
أو @loader_path
العابرة) يحتوي على اسم إعداد باستخدام @rpath
، يجب عليك تعيين @rpath
بشكل صريح عند ربط ملفك القابل للتنفيذ باستخدام خيار ld -rpath
بنفس القدر مرات كم تحتاج:
$ ld -o runme -rpath @executable_path/../Frameworks -rpath @executable_path/../bin/lib
(مفهوم rpath يدمر إلى حد ما الفكرة الأصلية لمسارات المكتبة المعروفة ، ويفتح إمكانية هجمات انتحال dylib. يمكننا أن نفترض أن هذا يجعل كل شيء يتعلق بأسماء التثبيت عديم الفائدة إلى حد ما.)
تبعيات دورية
عندما يشغل الكود المصدري للمشروع عدة ملفات ، فمن الطبيعي تمامًا إذا كانت هذه الملفات تعتمد على بعضها البعض. يعمل هذا بشكل جيد طالما يتم تجميع كل هذه الملفات في ثنائي واحد - ملف قابل للتنفيذ أو مكتبة. ما لا يعمل عندما تعتمد عدة مكتبات ديناميكية على بعضها البعض.
قد تجادل بأنه بدلاً من استخدام التبعيات الدورية بين المكتبات الديناميكية ، يجدر إعادة تصميم بنية المشروع ، وأنا أتفق معك. ولكن إذا كان هناك شيء نموذجي لـ Apple ، فهو أنهم لا يتوقفون أبدًا عن التفكير في الأشياء والقيام بذلك بشكل صحيح ؛ بدلاً من ذلك ، يضعون العكازات والحيل فوق بعضهم البعض. في هذه الحالة ، بالنسبة لـ Darling ، نحتاج إلى جعل التبعيات الدائرية تعمل ، لأن libSystem
libSystem libSystem
المختلفة ، مثل libsystem_dyld
و libsystem_kernel
و libsystem_pthread
، تعتمد جميعها على بعضها البعض. (حتى وقت قريب ، كان علينا أيضًا أن نربط أطر عمل Cocoa بشكل دوري مثل AppKit و Core Graphics و Core OpenGL ، نظرًا للطريقة التي تم بها تنفيذ Core OpenGL في The Cocotron ، لكننا أعدنا تصميم بنية تنفيذ Core OpenGL وتمكننا من التخلص من هذا الدوري التبعيات.)
من حيث المبدأ ، يجب أن تعمل التبعيات الدائرية بشكل جيد: يعرف الرابط الديناميكي بالفعل كيفية تحميل كل مكتبة مرة واحدة فقط ، لذلك لن يواجه مشاكل في العودية اللانهائية. تكمن المشكلة في أنه لا يمكن ربط هذه المكتبات ببساطة ، حيث أن كل مكالمة رابط تنشئ مكتبة واحدة فقط ، وعند ربط أي ثنائيات ، يجب نقل جميع تبعياتها المرتبطة بالفعل إلى الرابط. يجب علينا ربط إحدى مكتباتنا أولاً ، وفي الوقت الحالي ليست الأخرى جاهزة بعد ، لذلك لن نتمكن من نقلها إلى الرابط.
الحيلة هنا هي ربط بعض (أو ببساطة) جميع هذه المكتبات مرتين . لأول مرة ، اطلب من الرابط تجاهل التبعيات المفقودة ، وحقاً لا تمر التبعيات:
$ ld -o libfoo.dylib foo.o -flat_namespace -undefined suppress $ ld -o libbar.dylib bar.o -flat_namespace -undefined suppress
(انظر أدناه للحصول على -flat_namespace
.)
بالطبع ، إذا حاولت استخدام dylibs الناتجة مباشرة ، فستحصل على أخطاء الارتباط الديناميكي في وقت التشغيل. بدلاً من ذلك ، قم بربط هذه المكتبات مرة ثانية ، بتمرير dylibs الناتجة على أنها تبعيات:
$ ld -o libfoo.dylib foo.o libbar.dylib $ ld -o libbar.dylib bar.o libfoo.dylib
في هذه المرة ، يرى الرابط جميع الأحرف ، لذلك لا نطلب منه تجاهل الأخطاء (وإذا كانت بعض الأحرف مفقودة حقًا ، فستحصل على خطأ).
على الرغم من حقيقة أن بعض المكتبات ، إن لم يكن كلها ، المرتبطة بالنسخ "الخاطئة" من تبعياتها ، فإن dyld سيرى الإصدارات الصحيحة في وقت التشغيل. لكي يعمل هذا ، تأكد من أن كلتا نسختين من كل مكتبة لها نفس اسم التثبيت.
تفاصيل أخرى هنا هي ترتيب التهيئة. يمكن لأي رمز أن يصرح بوظائف __attribute__((constructor))
باستخدام الأمر __attribute__((constructor))
السحري __attribute__((constructor))
تقع قائمة __mod_init_func
في قسم __mod_init_func
في ملف Mach-O). يتم استدعاء هذه الوظائف بواسطة dyld عند تحميل الثنائي الذي توجد فيه قبل استدعاء main()
. عادةً ، يتم تنفيذ مُهيئ كل مكتبة بعد مُهيئو تبعياتها ، لذلك يمكن لكل مُهيئ أن يتوقع أن مكتبات التبعية قد تمت تهيئتها بالفعل وجاهزة للعمل. بالطبع ، لا يمكن ضمان ذلك بالنسبة للتبعيات الدورية ؛ ستقوم dyld بتنفيذ عمليات التهيئة الخاصة بها في بعض الترتيب. يمكنك تمييز التبعيات على أنها تبعيات تصاعدية لتخصيص هذا الطلب ؛ سيقوم dyld بتهيئة المكتبات التي قام شخص ما بتمييزها كاعتماد عليها ، أخيرًا. لذا ، لتهيئة libbar
بعد libbar
، قم libbar
التالي:
$ ld -o libfoo.dylib foo.o libbar.dylib $ ld -o libbar.dylib bar.o -upward_library libfoo.dylib
لجعلها أكثر ملاءمة ، لدينا وظيفة add_circular
CMake تسمى add_circular
، والتي add_circular
جميع الصعوبات وتسمح لك باستخدامها بهذه الطريقة ببساطة add_circular
:
set(DYLIB_INSTALL_NAME "/usr/lib/system/libdispatch.dylib") add_circular(libdispatch_shared FAT SOURCES ${dispatch_SRCS} SIBLINGS system_c system_kernel system_malloc system_blocks system_pthread system_dyld system_duct unwind platform compiler_rt UPWARD objc )
مساحة اسم شخصية من مستويين
لا تقوم جداول الرموز في Mach-O بتخزين أسماء الرموز فحسب ، بل "تتذكر" أيضًا أي مكتبة (أو ملف تنفيذي) يتم أخذ الرمز منه. بمعنى آخر ، توجد أسماء الرموز في مساحات الأسماء المعرفة التي يحددها الثنائي ؛ ومن هنا فإن "مساحة الاسم ذات المستويين" (مستوى آخر يعني أسماء الرموز نفسها).
تم إدخال مساحة اسم ذات مستويين لمنع تعارضات أسماء الرموز. عادة ، إذا حددت العديد من المكتبات أحرفًا تحمل نفس الاسم ، فسوف تحصل على خطأ أثناء الربط ؛ ولكن هذا قد لا يعمل إذا قمت بتحميل المكتبات في وقت التشغيل (على سبيل المثال ، الإضافات) أو عندما تختلف إصدارات المكتبات في وقت الارتباط ووقت التشغيل. بالنسبة للمكتبات التي تستخدم مساحة اسم ذات مستويين ، هذه ليست مشكلة - فهي تسمح للمكتبات المتعددة بتعريف الأحرف التي تحمل نفس الاسم دون إنشاء تعارضات.
يمكن إيقاف مساحة الاسم ذات المستويين عن طريق العودة إلى استخدام "مساحة اسم مسطحة" (أحد أسباب ذلك هو أن استخدام مساحة اسم ذات مستويين يعني أنه يجب السماح لكل حرف أثناء الربط ، لذا يلزم مساحة اسم مسطحة من أجل -undefined_suppress
، كما رأينا أعلاه). يحتوي Ld على -flat_namespace
تعطيل مساحة الاسم ذات المستويين أثناء الربط: -flat_namespace
، الذي يؤثر على ملف Mach-O واحد فقط ، -force_flat_namespace
، الذي يعمل فقط مع الملفات القابلة للتنفيذ ، وليس المكتبات ، -force_flat_namespace
العملية بأكملها على استخدام مسطح مساحة الاسم. بالإضافة إلى ذلك ، يمكنك إجبار dyld على استخدام مساحة اسم ثابتة في وقت التشغيل عن طريق تعيين متغير البيئة DYLD_FORCE_FLAT_NAMESPACE
.
تتمثل إحدى ميزات استخدام مساحة الاسم ذات المستويين في أنه يتعين عليك دائمًا ربط كل Mach-O صراحةً بجميع المكتبات والأطر التي تعتمد عليها. على سبيل المثال ، إذا قمت بالربط إلى AppKit ، فلا يمكنك استخدام Foundation فقط ؛ عليك أن تربطها صراحة. ميزة أخرى هي أنه ، كمؤلف مكتبة أو إطار عمل ، لا يمكنك نقل تطبيق الرمز "لأسفل" بحرية على طول سلسلة التبعية ، كما قد تعتاد على القيام بذلك (على سبيل المثال ، لا يمكنك فقط نقل الرمز من AppKit إلى Foundation). للسماح بهذا ، فإن Mach-O و ld و dyld لها العديد من الميزات الإضافية ، وهي المكتبات الفرعية وإعادة تصدير الأحرف والأحرف الوصفية .
مكتبات فرعية
المكتبات الفرعية - آلية تسمح لمكتبة واحدة (تسمى مكتبة الواجهة [مكتبة المظلة] أو مكتبة المظلة [مكتبة المظلة]) بتفويض تنفيذ جزء من وظيفتها إلى مكتبة أخرى (تسمى المكتبة الفرعية [المكتبة الفرعية]) ؛ أو ، إذا نظرت إليها من الجانب الآخر ، مما يسمح للمكتبة بإعادة تصدير الرموز التي تقدمها مكتبة أخرى بشكل علني.
المكان الرئيسي الذي يستخدم فيه هذا ، مرة أخرى ، libSystem
مع libSystem
الفرعية الموجودة في /usr/lib/system
؛ ولكن يمكن استخدامه مع أي زوج من المكتبات:
$ ld -o libfoo.dylib foo.o -lobjc -sub_library libobjc
الشيء الوحيد الذي يؤثر على هذا مقارنة بالربط فقط بتلك المكتبة هو أن أمر LC_REEXPORT_DYLIB
مكتوب على الملف الناتج بدلاً من LC_LOAD_DYLIB
المعتاد (بما في ذلك الرموز من المكتبة الفرعية أثناء الربط لا يتم نسخها إلى مكتبة المظلة ، لذلك لا يلزم حتى الارتباط في حالة إضافة أحرف جديدة إلى المكتبة الفرعية لاحقًا). في وقت التشغيل ، تعمل LC_REEXPORT_DYLIB
أيضًا على غرار LC_LOAD_DYLIB
: سيقوم dyld بتحميل المكتبة الفرعية LC_LOAD_DYLIB
أحرفها للباقي (ولكن بخلاف LC_LOAD_DYLIB
، من حيث مساحة الاسم ذات المستويين ، ستأتي الأحرف من المكتبة الشاملة).
ما يختلف حقًا في LC_REEXPORT_DYLIB
هو ما يفعله ld عند ربط مكتبة أخرى بـ libfoo
: بدلاً من مجرد البحث عن الرموز في جميع ملفات الكائنات والملفات التي تم تمريرها إليها ، سيفتح ld أيضًا وينظر في المكتبة الفرعية المعاد تصديرها (في هذا مثال libobjc
).
كيف يعرف أين يبحث عنها؟ الشيء الوحيد المحفوظ في libfoo.dylib
هو اسم التثبيت libobjc.dylib
، حيث يتوقع ld العثور عليه. هذا يعني أنه يجب تثبيت المكتبة في مكانها قبل استخدامها كمكتبة فرعية لأي شيء آخر ؛ هذا يعمل بشكل جيد لمكتبات النظام مثل libobjc
، ولكن يمكن أن يكون غير مريح للغاية أو مستحيل تمامًا إذا كنت تحاول إعادة تصدير مكتبتك الفرعية الخاصة.
لحل هذه المشكلة ، يوفر ld خيار -dylib_file
، الذي يسمح لك بتحديد مسار dylib مختلف لاستخدامه أثناء الربط:
$ ld -o libfoo.dylib foo.o -reexport_library /path/to/libsubfoo.dylib $ ld -o libbar.dylib bar.o libfoo.dylib -dylib_file @executable_path/lib/foo/libsubfoo.dylib:/path/to/libsubfoo.dylib
على الرغم من أن libSystem
وبعض مكتبات النظام الأخرى تعيد تصدير مكتباتها الفرعية ، إلا أنك لست مضطرًا إلى استخدام -dylib_file
عند ربط كل من الملفات القابلة للتنفيذ على macOS ؛ وذلك لأن مكتبات النظام مثبتة بالفعل وفقًا لاسم التثبيت الخاص بها. ولكن عند بناء Darling على Linux ، يتعين علينا تمرير عدد قليل من خيارات -dylib_file
(والحجج الشائعة الأخرى) إلى كل مكالمة ld. نقوم بذلك باستخدام وظيفة خاصة يتم تطبيقها تلقائيًا عند استخدام add_darling_library
و add_darling_executable
وغيرها.
إعادة تصدير الأحرف
في بعض الأحيان قد تحتاج المكتبة إلى إعادة تصدير بعض الشخصيات - ولكن ليس كلها مرة واحدة - من مكتبة أخرى. على سبيل المثال ، تقوم Core Foundation NSObject
، والذي يتم تطبيقه في الإصدارات الحديثة داخل وقت تشغيل Objective-C ، من أجل التوافق.
(إذا كنت مهتمًا بالسبب في أن NSObject
كانت في مؤسسة Core بدلاً من المؤسسة ، فذلك لأن كيفية التحويل المجاني [الجسر المجاني ، والقدرة على الإلقاء مباشرة بين أنواع Core Foundation وأنواع Foundation المقابلة دون تحويل إضافي] ، يتطلب أن يتم تنفيذ __NSCFString
خاصة على الأنواع من Core Foundation (على سبيل المثال ، __NSCFString
) في Core Foundation ؛ __NSCFString
Objective-C ، يجب أن تكون موروثة من NSObject
. ربما يمكن تحقيق ذلك كله من خلال لآخر ، وترك NSObject
مع جميع ورثتها في المؤسسة والحلقة esky يربط بين Core Foundation والمؤسسة ، لكن Apple قررت ترحيل هذه الفئات الخاصة المساعدة إلى جانب NSObject
إلى Core Foundation ، وفي Darling نقوم بذلك بنفس الطريقة للحفاظ على التوافق.)
يمكنك تمرير قائمة من الأحرف -reexported_symbols_list
باستخدام الخيار -reexported_symbols_list
الخاص -reexported_symbols_list
:
$ echo .objc_class_name_NSObject > reexport_list.exp $ ld -o CoreFoundation CFFiles.o -lobjc -reexported_symbols_list reexport_list.exp
على الرغم من أن إعادة تصدير بعض الرموز تبدو مشابهة جدًا لإعادة تصدير جميع الرموز ، إلا أن الآلية التي يتم من خلالها تنفيذ ذلك تختلف تمامًا عن طريقة عمل المكتبات الفرعية. لا يتم استخدام أمر LC_*_DYLIB
؛ بدلاً من ذلك ، يتم إدراج رمز غير مباشر خاص (يشار إليه N_INDIR
) في جدول الأسماء ، ويتصرف مثل الرمز المحدد في هذه المكتبة. إذا كانت المكتبة نفسها تستخدم هذا الرمز ، فستظهر النسخة الثانية "غير المحددة" من الرمز في جدول الأسماء (كما يحدث بدون إعادة تصدير).
من الأشياء الصغيرة المهمة التي يجب وضعها في الاعتبار عند استخدام إعادة تصدير الأحرف بشكل صريح أنك تحتاج على الأرجح إلى إعادة تصدير الأحرف بأسماء مختلفة لهياكل مختلفة. في الواقع ، تختلف اصطلاحات تسمية [name mangling Convention] لـ Objective-C وواجهة Objective-C الثنائية [ABI] لـ i386 و x86-64 ، لذلك في i386 ما عليك سوى .objc_class_name_NSObject
، وعلى x86-64 - _OBJC_CLASS_$_NSObject
و _OBJC_IVAR_$_NSObject.isa
و _OBJC_METACLASS_$_NSObject
. لا يتعين عليك التفكير في الأمر عند استخدام المكتبات الفرعية ، نظرًا لأنه يتم إعادة تصدير جميع الرموز المتاحة تلقائيًا لكل بنية.
تتعامل معظم أدوات العمل مع Mach-O بشفافية مع الثنائيات "السميكة" أو العامة (ملفات Mach-O التي تحتوي على العديد من Mach-O الفرعية للعديد من التصميمات). يمكن لـ Clang تجميع الثنائيات العالمية مع جميع البنيات المطلوبة ، ويختار dyld أي بنية يتم تحميلها من dylib ، ويبحث في أي بنى يدعمها الدعم القابل للتنفيذ ، وأدوات مثل ld و otool و nm تعمل مع البنية المطابقة لهندسة الكمبيوتر (أي . x86-64) ، ما لم تطلب صراحة بنية مختلفة بعلامة خاصة. , - , – , , .
. ld , , dylib- lipo:
$ ld -o CF_i386.dylib CFFiles.o -arch i386 -lobjc -reexported_symbols_list reexport_i386.exp $ ld -o CF_x86-64.dylib CFFiles.o -arch x86_64 -lobjc -reexported_symbols_list reexport_x86_64.exp $ lipo -arch i386 CF_i386.dylib -arch x86_64 CF_x86-64.dylib -create -output CoreFoundation
Darling CMake- add_separated_framework
, lipo, CMake- Core Foundation :
add_separated_framework(CoreFoundation CURRENT_VERSION SOURCES ${cf_sources} VERSION "A" DEPENDENCIES objc system icucore LINK_FLAGS
- – , , Apple , .
Mach-O- macOS, , -mmacosx-version-min=10.x
( iOS, tvOS, watchOS , Apple ). ; , AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER
C++ libstdc++
( GNU) libc++
( LLVM). , Mach-O. , ld -macosx_version_min
( m
), Mach-O- LC_VERSION_MIN_MACOSX
( dyld, , ).
, ld -macosx_version_min
, - Mach-O- . - – , $ld$
, ld, , : , . $ld$$$
.
os10.5
, - – , Mach-O- , ;
add
, hide
, install_name
compatibility_version
, ld ,
,
( ) , .
, , , ; , -, libobjc
, NSObject
, macOS:
$ld$hide$os10.0$_OBJC_CLASS_$_NSObject $ld$hide$os10.0$_OBJC_IVAR_$_NSObject.isa $ld$hide$os10.0$_OBJC_METACLASS_$_NSObject $ld$hide$os10.1$_OBJC_CLASS_$_NSObject $ld$hide$os10.1$_OBJC_IVAR_$_NSObject.isa $ld$hide$os10.1$_OBJC_METACLASS_$_NSObject $ld$hide$os10.2$_OBJC_CLASS_$_NSObject $ld$hide$os10.2$_OBJC_IVAR_$_NSObject.isa $ld$hide$os10.2$_OBJC_METACLASS_$_NSObject $ld$hide$os10.3$_OBJC_CLASS_$_NSObject $ld$hide$os10.3$_OBJC_IVAR_$_NSObject.isa $ld$hide$os10.3$_OBJC_METACLASS_$_NSObject $ld$hide$os10.4$_OBJC_CLASS_$_NSObject $ld$hide$os10.4$_OBJC_IVAR_$_NSObject.isa $ld$hide$os10.4$_OBJC_METACLASS_$_NSObject $ld$hide$os10.5$_OBJC_CLASS_$_NSObject $ld$hide$os10.5$_OBJC_IVAR_$_NSObject.isa $ld$hide$os10.5$_OBJC_METACLASS_$_NSObject $ld$hide$os10.6$_OBJC_CLASS_$_NSObject $ld$hide$os10.6$_OBJC_IVAR_$_NSObject.isa $ld$hide$os10.6$_OBJC_METACLASS_$_NSObject $ld$hide$os10.7$_OBJC_CLASS_$_NSObject $ld$hide$os10.7$_OBJC_IVAR_$_NSObject.isa $ld$hide$os10.7$_OBJC_METACLASS_$_NSObject
, , , , .
dyld – [symbol resolvers], . , , , dyld , .
ld, . .symbol_resolver
:
; foo _foo1: movl 1, %eax ret _foo2: movl 2, %eax ret .symbol_resolver _foo ; - call _condition jz .ret_foo2 movq _foo1, %rax ret .ret_foo2: movq _foo2, %rax ret ; , _foo ; , , ; . .global _foo _foo:
C , , C:
static int foo1() { return 1; } static int foo2() { return 2; } int foo() {
( _foo
foo
, Darwin C, . , Mach-O Darling, ELF-, .)
_foo
, ( ), foo()
foo_resolver()
, :
void *foo() { __asm__(".symbol_resolver _foo"); return condition() ? &foo1 : &foo2; }
, – - , foo()
, ( int
-). , , : dlsym("_foo")
_foo
– , , , – . , , , foo()
_foo
.
. Apple libplatform
:
#define _OS_VARIANT_RESOLVER(s, v, ...) \ __attribute__((visibility(OS_STRINGIFY(v)))) extern void* s(void); \ void* s(void) { \ __asm__(".symbol_resolver _" OS_STRINGIFY(s)); \ __VA_ARGS__ \ } #define _OS_VARIANT_UPMP_RESOLVER(s, v) \ _OS_VARIANT_RESOLVER(s, v, \ uint32_t *_c = (void*)(uintptr_t)_COMM_PAGE_CPU_CAPABILITIES; \ if (*_c & kUP) { \ extern void OS_VARIANT(s, up)(void); \ return &OS_VARIANT(s, up); \ } else { \ extern void OS_VARIANT(s, mp)(void); \ return &OS_VARIANT(s, mp); \ })
, – – ( kUP
, commpage ), , , [spinlock]. , .
Darling : Mach-O- ELF- Linux, [host, , Darling] – , libX11
libcairo
.
ELF- – libelfloader
, ELF, , ld-linux, dyld Linux, ld-linux ELF-. libelfloader
Mach-O /usr/lib/darling/libelfloader.dylib
Darwin-chroot-; , Darwin-.
, libelfloader
Mach-O ELF. ( _elfcalls
), libSystem
, Darwin , ELF- Linux. «» Darwin Linux – , C ( libSystem_c
glibc
, ).
ELF- Darwin, - libelfloader
API _elfcalls->dlsym_fatal(_elfcalls->dlopen_fatal("libX11.so"), "XOpenDisplay")
. , wrapgen , ELF- , , The Cocotron – Linux – . wrapgen ELF- (, libX11.so
), :
#include <elfcalls.h> extern struct elf_calls* _elfcalls; static void* lib_handle; __attribute__((constructor)) static void initializer() __attribute__((destructor)) static void destructor() void* XOpenDisplay()
Mach-O- /usr/lib/native/libX11.dylib
, Mach-O- , libX11.so
, Mach-O. , CMake- wrap_elf
, wrapgen, Mach-O- /usr/lib/native
: wrap_elf(X11 libX11.so)
libX11
, Mach-O-.
Linux . , Darling , Darwin Linux, . Darling – Darwin ( , Darwin); , , , Darwin, libSystem
, dyld, XNU launchd, , , commpage. , libsystem_kernel
, , Linux, «» Darwin- – Linux GNU/Linux . Linux- , Linux ( X server) , [witnessing a magic trick] – libelfloader
, wrapgen, , . , , , , , .
- , Mach-O-, ld . ( , – , Apple, , .)
, , , , [order file], ld :
$ ld -o libfoo.dylib foo.o -order_file foo.order
-reexported_symbols_list
, , -order_file
, :
symbol1 symbol2 # . # # , , # ( C) # . foo.o: _static_function3 # , , # ; # # lipo, # . i386:symbol4
( , , ) «» . , , , , .subsections_via_symbols
, Mach-O- , , , , .
, Apple – libdispatch
. libdispatch
, «OS object», , . Objective-C, libdispatch
( Core Foundation), libdispatch
- Objective-C- , Objective-C-. , dispatch_data_t
NSData *
API Cocoa ( ).
, , Objective-C- OS object vtables . , DISPATCH_OBJECT_TFB
, , Objective-C libdispatch
-, isa
vtable
- dispatch_object
object
:
#define DISPATCH_OBJECT_TFB(f, o, ...) \ if (slowpath((uintptr_t)((o)._os_obj->os_obj_isa) & 1) || \ slowpath((Class)((o)._os_obj->os_obj_isa) < \ (Class)OS_OBJECT_VTABLE(dispatch_object)) || \ slowpath((Class)((o)._os_obj->os_obj_isa) >= \ (Class)OS_OBJECT_VTABLE(object))) { \ return f((o), ##__VA_ARGS__); \ }
, , libdispatch
.
( ) – DYLD_INSERT_LIBRARIES
, dyld Mach-O- . , , , DYLD_FORCE_FLAT_NAMESPACE
.
, , - . ( ), dlsym()
RTLD_NEXT
, :
int open(const char* path, int flags, mode_t mode) { printf(" open(%s)\n", path); // " " if (strcmp(path, "foo") == 0) { path = "bar"; } int (*original_open)(const char *, int, mode_t); original_open = dlsym(RTLD_NEXT, "open"); return original_open(path, flags, mode); }
, dyld , dyld- [dyld iterposing]. Mach-O- __interpose
, dyld , – .
, – , __interpose
– [implicit interposing]. , __interpose
( ), dyld . , dyld- , - . , dyld , - , - ( Mach-O-):
static int my_open(const char* path, int flags, mode_t mode) { printf("Called open(%s)\n", path); // " " if (strcmp(path, "foo") == 0) { path = "bar"; } // , // open() my_open(). return open(path, flags, mode); } // __interpose __attribute__ ((section ("__DATA,__interpose"))) static struct { void *replacement, *replacee; } replace_pair = { my_open, open };
, – – Mach-O- - [relocation table]. , dyld ( ), .
, dyld_dynamic_interpose
, :
typedef struct { void *replacement, *replacee; } replacement_tuple; extern const struct mach_header __dso_handle; extern void dyld_dynamic_interpose(const struct mach_header*, const replacement_tuple replacements[], size_t count); void interpose() { replacement_tuple replace_pair = { my_open, open }; dyld_dynamic_interpose(&__dso_handle, &replace_pair, 1); }
, , , .
DYLD_INSERT_LIBRARIES
dyld- Objective-C, C, - , Objective-C- ( IMP
), Objective-C , method swizzling ( isa swizzling ).
Darling xtrace, .
Darwin Darwin ( – BSD Mach- [Mach traps]). libsystem_kernel
, ABI userspace. Darling, libsystem_kernel
Darwin Linux Darling-Mach , Linux, Mach .
strace, Linux, , Linux-; strace Darwin, Darling, Linux, Darwin ( Linux, ELF- ). , Darwin Linux , , Darwin – , – .
, xtrace . strace, , ptrace()
API, xtrace . DYLD_INSERT_LIBRARIES=/usr/lib/darling/libxtrace.dylib
, - [trampoline functions] , . xtrace , strace, , , :
Darling [~]$ xtrace arch <......> [223] mach_timebase_info_trap (...) [223] mach_timebase_info_trap () -> KERN_SUCCESS [223] issetugid (...) [223] issetugid () -> 0 [223] host_self_trap () [223] host_self_trap () -> port right 2563 [223] mach_msg_trap (...) [223] mach_msg_trap () -> KERN_SUCCESS [223] _kernelrpc_mach_port_deallocate_trap (task=2563, name=-6) [223] _kernelrpc_mach_port_deallocate_trap () -> KERN_SUCCESS [223] ioctl (...) [223] ioctl () -> 0 [223] fstat64 (...) [223] fstat64 () -> 0 [223] ioctl (...) [223] ioctl () -> 0 [223] write_nocancel (...) i386 [223] write_nocancel () -> 5 [223] exit (...)
, BSD Mach. , write()
exit()
, Linux-, . , Mach- ioctl
- /dev/mach
, ; BSD- ioctl()
, stdio , stdin stdout ( tty) readlink()
/proc/self/fd/
.
Mach-O, , dyld. :
- , , dylib, , . ld
-bundle_loader
. - ,
LC_LOAD_DYLIB
, LC_REEXPORT_DYLIB
LC_DYLIB_ID
, [compatibility version, current version] , – , . ld -current_version
-compatibility_version
, . dyld , , , . - , Mach-O [source version]. ,
LC_SOURCE_VERSION
. ld -source_version
, , Mach-O- – -add_source_version
-no_source_version
. Info.plist
__info_plist
Mach-O- [codesign] , Info.plist
. ad-hoc Security.framework, , CFBundle
/ NSBundle
API, , , .
, , , ld dyld , « », libSystem
, -. /usr/lib/
.