Trik untuk menautkan dan mengunduh file Mach-O

Saya mempersembahkan kepada Anda terjemahan artikel saya dari blog Darling Project. Sedikit bantuan pada konsep yang digunakan: Darwin - sistem operasi open source yang mendasari macOS, iOS dan sistem operasi lain dari Apple; Mach-O adalah format biner dari file dan perpustakaan yang dapat dieksekusi yang digunakan di Darwin; dyld - bootloader dinamis yang digunakan Darwin untuk mengunduh file Mach-O; dylib adalah pustaka yang dapat dimuat secara dinamis (biasanya memiliki ekstensi .dylib ).


Gambar untuk menarik perhatian


Tujuan dari Proyek Darling adalah untuk mengaktifkan aplikasi macOS untuk berjalan di Linux, dan kemampuan untuk mengunduh file biner dalam format Mach-O adalah salah satu langkah kunci menuju tujuan ini.


Awalnya, Darling dibangun berdasarkan penerapan Mach-O loader dan gagasan penyiaran panggilan antara API Darwin tingkat tinggi dan rekan-rekan Linux-nya. Sejak itu, fokus kami telah bergeser ke menjalankan kode dalam wadah Darwin yang semakin terisolasi. Karena kami beralih menggunakan Mach-O untuk komponen internal Darling , kami dapat menggunakan dyld asli Apple, serta membangun banyak komponen open source Darwin lainnya. Kita masih membutuhkan bootloader Mach-O sederhana untuk boot dyld itu sendiri.


Mach-O, bersama dengan Mach sendiri, mungkin merupakan ciri khas Darwin yang paling menonjol, dan berbagai perpustakaan dan kerangka kerja yang disediakan oleh Apple memanfaatkan banyak fitur tidak jelas dari format Mach-O. Ini menjadikan bekerja dengan Mach-O salah satu tugas paling penting dan terkemuka dalam mengembangkan Darling. Dari implementasi pemuat Mach-O asli hingga perakitan komponen Darwin (pertama dalam bentuk file ELF khusus, dan sekarang dalam bentuk Mach-O nyata) - kita perlu memahami struktur internal Mach-O pada tingkat yang jauh lebih dalam daripada yang biasanya diperlukan dari biasa pengembang untuk platform Darwin.


Kami menyelesaikan pengantar ini dan melanjutkan untuk membahas beberapa trik yang ditawarkan format Mach-O kepada kami.


Nama pemasangan


Pada Windows dan Linux, pustaka dinamis direferensikan dengan namanya (misalnya, libc.so ), setelah itu tugas penghubung dinamis adalah menemukan pustaka dengan nama yang cocok di salah satu direktori pustaka standar. Darwin segera menggunakan (hampir) path lengkap ke file perpustakaan, yang disebut nama instal untuk perpustakaan ini. Agaknya, ini dilakukan untuk mencegah penggantian dylib [pembajakan dylib] - serangan di mana dylib palsu ditempatkan di direktori di mana ia akan ditemukan oleh penghubung dinamis lebih awal dari yang asli, yang memungkinkan dylib palsu untuk mengeksekusi kode arbitrer atas nama program, yang dengan demikian meyakinkan hal ini unduh dylib.


Tidak hanya executable dan libraries menyimpan nama instalasi penuh dari dependensi mereka, tetapi dependensi Mach-O sendiri β€œtahu” nama instalasinya sendiri. Sebenarnya, ini adalah bagaimana linker mempelajari nama instalasi mana yang akan digunakan untuk dependensi: ia membacanya dari dependensi itu sendiri.


Saat menautkan dylib, Anda menentukan nama instalasinya menggunakan opsi ld -dylib_install_name atau -dylib_install_name :


 $ ld -o libfoo.dylib foo.o -install_name /usr/local/lib/libfoo.dylib 

Sekarang ketika Anda menautkan file Mach-O lain (misalnya libbar.dylib ) ke libfoo.dylib , ld akan menulis nama instalasi libfoo - /usr/local/lib/libfoo.dylib libbar /usr/local/lib/libfoo.dylib libbar - dalam libbar dependensi libbar , dan di situlah dyld berada akan mencari libfoo saat runtime.


Menggunakan path lengkap berfungsi cukup baik untuk pustaka sistem, yang, pada kenyataannya, dipasang di tempat-tempat yang sebelumnya dikenal dalam sistem file; tetapi dengan perpustakaan yang datang sebagai bagian dari bundel aplikasi, ada masalah. Meskipun setiap aplikasi dapat berasumsi bahwa itu akan diinstal di /Applications/AppName.app , secara umum, itu berarti bahwa rakitan aplikasi bersifat portabel dan dapat dipindahkan secara bebas di sekitar sistem file, sehingga jalur pustaka tertentu di dalam rakitan tersebut tidak dapat diketahui terlebih dahulu.


Sebagai solusi untuk masalah ini, Darwin mengizinkan nama instalasi untuk memulai dengan @executable_path , @loader_path , atau @rpath - yaitu, tidak mutlak, tetapi menunjukkan jalur pustaka relatif ke jalur ke file yang dapat dieksekusi utama, jalur ke "memuat" (file atau pustaka yang dapat dieksekusi, yang secara langsung tergantung pada pustaka ini) atau relatif terhadap daftar path yang ditentukan oleh file executable utama, masing-masing. @executable_path dan @loader_path berfungsi tanpa kerumitan tambahan, tetapi jika setidaknya satu dari dependensi Anda (atau dependensi transitifnya) memiliki nama setup menggunakan @rpath , Anda harus secara eksplisit mengatur @rpath ketika menautkan file executable Anda menggunakan opsi ld -rpath berapa banyak yang Anda butuhkan:


 $ ld -o runme -rpath @executable_path/../Frameworks -rpath @executable_path/../bin/lib 

(Konsep rpath sampai batas tertentu menghancurkan ide asli jalur perpustakaan terkenal, dan membuka kemungkinan serangan spoofing dylib. Kita dapat mengasumsikan bahwa ini membuat segala sesuatu yang berhubungan dengan nama instalasi cukup tidak berguna.)


Ketergantungan siklik


Ketika kode sumber proyek menempati beberapa file, sangat normal jika file-file ini saling bergantung satu sama lain. Ini berfungsi dengan baik selama semua file ini dikompilasi menjadi biner tunggal - file yang dapat dieksekusi atau perpustakaan. Apa yang tidak berfungsi adalah ketika beberapa pustaka dinamis saling bergantung.


Anda mungkin berpendapat bahwa alih-alih menggunakan dependensi siklik antara pustaka dinamis, ada baiknya mendesain ulang arsitektur proyek, dan saya setuju dengan Anda. Tetapi jika ada sesuatu yang khas dari Apple, itu adalah bahwa mereka tidak pernah berhenti untuk memikirkan semuanya dan melakukannya dengan benar; sebaliknya, mereka meletakkan tongkat dan trik di atas satu sama lain. Dalam hal ini, untuk Sayang, kita perlu membuat dependensi melingkar berfungsi, karena berbagai libSystem libSystem , seperti libsystem_dyld , libsystem_kernel dan libsystem_pthread , semuanya saling bergantung satu sama lain. (Sampai saat ini, kami juga harus secara siklik menghubungkan kerangka kerja Kakao seperti AppKit, Core Graphics dan Core OpenGL, karena cara Core OpenGL diimplementasikan di The Cocotron, tetapi kami mendesain ulang arsitektur implementasi Core OpenGL kami dan dapat menyingkirkan siklus ini. dependensi.)


Pada prinsipnya, dependensi melingkar harus berfungsi dengan baik: penghubung dinamis sudah tahu cara memuat setiap perpustakaan hanya sekali, sehingga tidak akan memiliki masalah dengan rekursi tak terbatas. Masalahnya adalah bahwa pustaka semacam itu tidak dapat dengan mudah dihubungkan , karena setiap panggilan tautan hanya membuat satu pustaka, dan ketika menautkan binari apa pun, semua ketergantungannya yang sudah ditautkan harus ditransfer ke tautan tersebut. Kami harus menautkan salah satu perpustakaan kami terlebih dahulu, dan saat ini yang lain belum siap, jadi kami tidak akan dapat mentransfernya ke tautan tersebut.


Kuncinya di sini adalah menghubungkan beberapa (atau untuk kesederhanaan, semua) perpustakaan ini dua kali . Untuk pertama kalinya, beri tahu tautan untuk mengabaikan dependensi yang hilang, dan sungguh, jangan lulus dependensi:


 $ ld -o libfoo.dylib foo.o -flat_namespace -undefined suppress $ ld -o libbar.dylib bar.o -flat_namespace -undefined suppress 

(Lihat di bawah untuk -flat_namespace .)


Tentu saja, jika Anda mencoba untuk langsung menggunakan dylib yang dihasilkan, Anda akan mendapatkan kesalahan tautan dinamis pada saat run time. Sebagai gantinya, tautkan pustaka-pustaka ini untuk kedua kalinya, dengan meneruskan dylib yang dihasilkan sebagai dependensi:


 $ ld -o libfoo.dylib foo.o libbar.dylib $ ld -o libbar.dylib bar.o libfoo.dylib 

Kali ini, penghubung melihat semua karakter, jadi kami tidak menyuruhnya mengabaikan kesalahan (dan jika beberapa karakter benar-benar hilang, Anda akan mendapatkan kesalahan).


Terlepas dari kenyataan bahwa beberapa, jika tidak semua, perpustakaan terkait dengan salinan dependensi yang "salah", dyld akan melihat versi yang benar saat runtime. Agar ini berfungsi, pastikan kedua salinan dari masing-masing perpustakaan memiliki nama instalasi yang sama.


Detail lain di sini adalah urutan inisialisasi. Kode apa pun dapat mendeklarasikan fungsi penginisialisasi menggunakan perintah __attribute__((constructor)) compiler (daftar inisialisasi tersebut masuk ke bagian __mod_init_func di file Mach-O). Fungsi-fungsi ini dipanggil oleh dyld saat memuat biner di mana mereka berada sebelum memanggil main() . Biasanya, inisialisasi masing-masing perpustakaan dijalankan setelah inisialisasi dependensinya, sehingga setiap inisialisasi dapat berharap bahwa perpustakaan dependensi sudah diinisialisasi dan siap untuk bekerja. Tentu saja, ini tidak dapat dijamin untuk dependensi siklik; dyld akan menjalankan inisialisasi mereka dalam beberapa urutan. Anda dapat menandai dependensi sebagai dependensi ke atas untuk menyesuaikan pesanan ini; dyld akan menginisialisasi perpustakaan yang telah ditandai oleh seseorang sebagai ketergantungannya, terakhir. Jadi, untuk membuat libfoo diinisialisasi setelah libbar , tautkan mereka seperti ini:


 $ ld -o libfoo.dylib foo.o libbar.dylib $ ld -o libbar.dylib bar.o -upward_library libfoo.dylib 

Untuk membuatnya lebih nyaman, kami memiliki fungsi Darling CMake yang disebut add_circular , yang menangani semua kesulitan dan memungkinkan Anda untuk menggunakannya seperti itu secara sederhana dan secara deklaratif:


 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 ) 

Namespace karakter dua tingkat


Tabel simbol di Mach-O tidak hanya menyimpan nama simbol, mereka juga "mengingat" dari perpustakaan mana (atau file yang dapat dieksekusi) simbol mana yang diambil. Dengan kata lain, nama simbol ada dalam ruang nama yang ditentukan oleh biner yang mendefinisikannya; karenanya "namespace dua tingkat" (level lain berarti nama simbol itu sendiri).


Ruang nama dua tingkat diperkenalkan untuk mencegah konflik nama simbol. Biasanya, jika beberapa perpustakaan menentukan karakter dengan nama yang sama, Anda akan mendapatkan kesalahan saat menautkan; tetapi ini mungkin tidak berfungsi jika Anda memuat pustaka saat runtime (misalnya, plugin) atau ketika versi pustaka pada waktu tautan dan runtime berbeda. Untuk perpustakaan yang menggunakan namespace dua tingkat, ini bukan masalah - ini memungkinkan banyak perpustakaan untuk mendefinisikan karakter dengan nama yang sama tanpa membuat konflik.


Ruang nama dua tingkat dapat dimatikan dengan kembali menggunakan "ruang nama datar" (salah satu alasan untuk ini adalah bahwa menggunakan ruang nama dua tingkat menyiratkan bahwa setiap karakter harus diizinkan selama menghubungkan, sehingga ruang nama datar diperlukan untuk -undefined_suppress , karena kami lihat di atas). Ld memiliki dua flag yang memungkinkan menonaktifkan namespace dua tingkat selama menghubungkan: -flat_namespace , yang hanya mempengaruhi satu file Mach-O, dan -force_flat_namespace , yang hanya bekerja dengan file yang dapat dieksekusi, bukan perpustakaan, dan memaksa seluruh proses untuk menggunakan flat namespace. Selain itu, Anda dapat memaksa dyld untuk menggunakan namespace datar saat runtime dengan mengatur variabel lingkungan DYLD_FORCE_FLAT_NAMESPACE .


Salah satu fitur menggunakan namespace dua-tingkat adalah bahwa Anda harus selalu menghubungkan setiap Mach-O secara eksplisit ke semua pustaka dan kerangka kerja tempat bergantung. Misalnya, jika Anda menautkan ke AppKit, Anda tidak bisa hanya menggunakan Yayasan; Anda harus menautkannya secara eksplisit. Fitur lain adalah bahwa, sebagai penulis perpustakaan atau kerangka kerja, Anda tidak dapat dengan bebas memindahkan implementasi simbol "turun" di sepanjang rantai ketergantungan, seperti yang biasa Anda lakukan (misalnya, Anda tidak bisa hanya memindahkan kode dari AppKit ke Foundation). Untuk memungkinkan ini, Mach-O, ld, dan dyld memiliki beberapa fitur tambahan, yaitu sub-perpustakaan , ekspor ulang karakter, dan meta-karakter .


Sub perpustakaan


Sub-perpustakaan - suatu mekanisme yang memungkinkan satu perpustakaan (disebut perpustakaan fasad [perpustakaan payung] atau perpustakaan payung [perpustakaan payung]) untuk mendelegasikan pelaksanaan sebagian fungsinya ke perpustakaan lain (disebut sub-perpustakaannya [sub-perpustakaan]); atau, jika Anda melihatnya dari sisi lain, memungkinkan perpustakaan untuk mengekspor kembali simbol yang disediakan oleh perpustakaan lain ke publik.


Tempat utama di mana ini digunakan adalah, sekali lagi, libSystem dengan sub-pustaka yang ada di /usr/lib/system ; tetapi dapat digunakan dengan sepasang perpustakaan:


 $ ld -o libfoo.dylib foo.o -lobjc -sub_library libobjc # : $ ld -o libfoo.dylib foo.o -reexport-lobjc 

Satu-satunya hal yang memengaruhi ini dibandingkan dengan hanya menautkan ke perpustakaan itu adalah bahwa perintah LC_REEXPORT_DYLIB ditulis ke file yang dihasilkan alih-alih LC_LOAD_DYLIB biasa (termasuk simbol dari sub-perpustakaan saat menautkan tidak disalin ke perpustakaan payung, sehingga tidak perlu lagi tautan jika karakter baru ditambahkan ke sublibrary nanti). Pada saat runtime, LC_REEXPORT_DYLIB juga bekerja mirip dengan LC_LOAD_DYLIB : dyld akan memuat sub-perpustakaan dan membuat karakternya tersedia untuk yang lainnya (tetapi tidak seperti LC_LOAD_DYLIB , dalam hal namespace dua tingkat, karakter akan berasal dari perpustakaan payung).


Apa yang benar-benar berbeda tentang LC_REEXPORT_DYLIB adalah apa yang saya lakukan ketika Anda menautkan perpustakaan lain ke libfoo : alih-alih hanya mencari simbol di semua objek dan file dylib yang diteruskan ke sana, ld juga akan membuka dan melihat sublibrary yang diekspor kembali (dalam hal ini contoh libobjc ).


Bagaimana dia tahu di mana mencarinya? Satu-satunya yang disimpan di libfoo.dylib adalah nama instalasi libobjc.dylib , jadi di situlah ld berharap untuk menemukannya. Ini berarti bahwa perpustakaan harus dipasang di tempatnya sebelum dapat digunakan sebagai sub-perpustakaan untuk hal lain; ini berfungsi dengan baik untuk pustaka sistem seperti libobjc , tetapi bisa sangat tidak nyaman atau sama sekali tidak mungkin jika Anda mencoba untuk mengekspor kembali sub-pustaka Anda sendiri.


Untuk mengatasi masalah ini, ld menyediakan opsi -dylib_file , yang memungkinkan Anda menentukan jalur dylib yang berbeda untuk digunakan selama menautkan:


 $ 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 

Meskipun libSystem dan beberapa pustaka sistem lainnya mengekspor kembali sub-pustaka mereka, Anda tidak harus menggunakan -dylib_file saat menautkan masing-masing file yang dapat dieksekusi pada macOS; ini karena pustaka sistem sudah diinstal sesuai dengan nama instalasinya. Tetapi ketika membangun Darling di Linux, kita harus memberikan beberapa opsi -dylib_file (dan argumen umum lainnya) untuk setiap panggilan ld. Kami melakukan ini dengan fungsi khusus yang secara otomatis diterapkan ketika menggunakan add_darling_library , add_darling_executable , dan lainnya.


Mengekspor ulang karakter


Terkadang perpustakaan mungkin perlu mengekspor kembali beberapa karakter - tetapi tidak sekaligus - dari perpustakaan lain. Sebagai contoh, Core Foundation NSObject , yang dalam versi terbaru diimplementasikan di dalam runtime Objective-C, untuk kompatibilitas.


(Jika Anda tertarik mengapa NSObject dulunya berada di Core Foundation dan bukan Foundation, itu karena bagaimana konversi gratis [bridging bebas pulsa, kemampuan untuk secara langsung melemparkan antara Core Foundation dan tipe Foundation yang sesuai tanpa konversi tambahan], mensyaratkan bahwa kelas pembungkus pribadi lebih dari jenis dari Core Foundation (misalnya, __NSCFString ) diimplementasikan di Core Foundation, dan sebagai objek Objective-C, mereka harus diwarisi dari NSObject . Mungkin, semua ini dapat diwujudkan dengan ke yang lain, meninggalkan NSObject dengan semua ahli warisnya di Yayasan dan loop esky menghubungkan Core Foundation dan Foundation, tetapi Apple memutuskan untuk memigrasi kelas privat pembantu ini bersama dengan NSObject ke Core Foundation, dan di Darling kami melakukannya dengan cara yang sama untuk menjaga kompatibilitas.)


Anda dapat mengirimkan daftar karakter yang akan -reexported_symbols_list menggunakan opsi -reexported_symbols_list -nya:


 $ echo .objc_class_name_NSObject > reexport_list.exp $ ld -o CoreFoundation CFFiles.o -lobjc -reexported_symbols_list reexport_list.exp 

Meskipun mengekspor kembali beberapa simbol terdengar sangat mirip dengan mengekspor kembali semua simbol, mekanisme penerapannya sangat berbeda dari cara kerja sub-perpustakaan. Tidak ada perintah LC_*_DYLIB yang digunakan; sebaliknya, simbol tidak langsung khusus (ditunjukkan oleh bendera N_INDIR ) dimasukkan ke dalam tabel nama, dan itu berperilaku seperti simbol yang didefinisikan dalam perpustakaan ini. Jika perpustakaan itu sendiri menggunakan simbol ini, salinan kedua dari simbol tersebut akan muncul di tabel nama (seperti yang terjadi tanpa ekspor ulang).


Satu hal kecil yang perlu diingat ketika menggunakan ekspor ulang karakter secara eksplisit adalah Anda kemungkinan besar perlu mengekspor kembali karakter dengan nama yang berbeda untuk arsitektur yang berbeda. Memang, konvensi penamaan [name mangling] konvensi untuk Objective-C dan antarmuka biner Objective-C [ABI] untuk i386 dan x86-64 berbeda, jadi pada i386 Anda hanya perlu .objc_class_name_NSObject , dan pada x86-64 - _OBJC_CLASS_$_NSObject , _OBJC_IVAR_$_NSObject.isa dan _OBJC_METACLASS_$_NSObject . Anda tidak perlu memikirkannya saat menggunakan sub-perpustakaan, karena di sana semua simbol yang tersedia secara otomatis untuk setiap arsitektur diekspor kembali secara otomatis.


Sebagian besar alat untuk bekerja dengan Mach-O secara transparan menangani biner "tebal" atau universal (file Mach-O berisi beberapa sub-Mach-O untuk beberapa arsitektur). Dentang dapat mengkompilasi binari universal dengan semua arsitektur yang diminta, dapat memilih arsitektur mana untuk memuat dari dylib, melihat di mana arsitektur yang dapat dieksekusi mendukung, dan alat-alat seperti ld, otool dan nm bekerja dengan arsitektur yang sesuai dengan arsitektur komputer (mis. .x86-64), kecuali jika Anda secara eksplisit memerlukan arsitektur yang berbeda dengan bendera khusus. , - , – , , .


. 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 # ...    ) set_property(TARGET CoreFoundation_i386 APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-reexported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/reexport_i386.exp") set_property(TARGET CoreFoundation_x86_64 APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-reexported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/reexport_x86_64.exp") 

-


- – , , 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() { //         return 0; } static void *foo_resolver() { __asm__(".symbol_resolver _foo"); return condition() ? &foo1 : &foo2; } 

( _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() { lib_handle = _elfcalls->dlopen_fatal("libX11.so"); } __attribute__((destructor)) static void destructor() { _elfcalls->dlclose_fatal(lib_handle); } void* XOpenDisplay() { __asm__(".symbol_resolver _XOpenDisplay"); return _elfcalls->dlsym_fatal(lib_handle, "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/ .

Source: https://habr.com/ru/post/id417507/


All Articles