Ich präsentiere Ihnen die Übersetzung meines Artikels aus dem Darling Project Blog. Eine kleine Hilfe zu den verwendeten Konzepten: Darwin - ein Open-Source-Betriebssystem, das MacOS, iOS und anderen Betriebssystemen von Apple zugrunde liegt; Mach-O ist ein Binärformat von ausführbaren Dateien und Bibliotheken, die in Darwin verwendet werden. dyld - dynamischer Bootloader, der von Darwin zum Herunterladen von Mach-O-Dateien verwendet wird; dylib ist eine dynamisch ladbare Bibliothek (normalerweise mit der Erweiterung .dylib
).

Das Ziel des Darling-Projekts ist es, die Ausführung von MacOS-Anwendungen unter Linux zu ermöglichen. Die Möglichkeit, Binärdateien im Mach-O-Format herunterzuladen, ist einer der wichtigsten Schritte in Richtung dieses Ziels.
Ursprünglich basierte Darling auf seiner eigenen Implementierung des Mach-O-Loaders und der Idee, Anrufe zwischen der übergeordneten Darwin-API und ihren Linux-Gegenstücken zu übertragen. Seitdem hat sich unser Fokus darauf verlagert, Code in einem zunehmend isolierten Darwin-Container auszuführen. Seit wir Mach-O für die internen Komponenten von Darling verwendet haben, konnten wir Apples Original-Dyld verwenden und viele andere Open-Source-Darwin-Komponenten erstellen. Wir brauchen noch einen einfachen Mach-O-Bootloader, um dyld selbst zu booten.
Mach-O ist zusammen mit Mach selbst wahrscheinlich Darwins bemerkenswertestes Markenzeichen, und die verschiedenen von Apple bereitgestellten Bibliotheken und Frameworks nutzen viele undurchsichtige Funktionen des Mach-O-Formats in großem Umfang. Dies macht die Arbeit mit Mach-O zu einer der wichtigsten und bemerkenswertesten Aufgaben bei der Entwicklung von Darling. Von der Implementierung nativer Mach-O-Lader bis zur Montage von Darwin-Komponenten (zuerst in Form spezieller ELF-Dateien und jetzt in Form von echtem Mach-O) müssen wir die interne Struktur von Mach-O auf einer viel tieferen Ebene verstehen, als dies normalerweise von gewöhnlichen Personen verlangt wird Entwickler für die Darwin-Plattform.
Wir beenden diese Einführung und diskutieren einige der Tricks, die uns das Mach-O-Format bieten kann.
Installationsnamen
Unter Windows und Linux wird auf dynamische Bibliotheken mit ihrem Namen verwiesen (z. B. libc.so
). libc.so
besteht die Aufgabe des dynamischen Linkers darin, eine Bibliothek mit einem übereinstimmenden Namen in einem der Standardbibliotheksverzeichnisse zu finden. Darwin verwendet stattdessen sofort den (fast) vollständigen Pfad zur Bibliotheksdatei, der als Installationsname für diese Bibliothek bezeichnet wird. Vermutlich wurde dies getan, um die Substitution von Dylib [Dylib-Hijacking] zu verhindern - ein Angriff, bei dem eine gefälschte Dylib in einem Verzeichnis abgelegt wird, in dem sie von einem dynamischen Linker früher als dem realen gefunden wird, wodurch gefälschte Dylib beliebigen Code im Namen des Programms ausführen kann, was dies überzeugt Dylib herunterladen.
Nicht nur ausführbare Dateien und Bibliotheken speichern die vollständigen Installationsnamen ihrer Abhängigkeiten, sondern Mach-O-Abhängigkeiten selbst „kennen“ ihre eigenen Installationsnamen. Auf diese Weise erfährt der Linker, welche Installationsnamen für Abhängigkeiten verwendet werden sollen: Er liest sie aus den Abhängigkeiten selbst.
Wenn Sie dylib verknüpfen, geben Sie den Installationsnamen mit den Optionen ld -install_name
oder -dylib_install_name
:
$ ld -o libfoo.dylib foo.o -install_name /usr/local/lib/libfoo.dylib
Wenn Sie nun eine andere Mach-O-Datei (z. B. libbar.dylib
) mit libfoo.dylib
, schreibt ld den libfoo-Installationsnamen - /usr/local/lib/libfoo.dylib
- in die libbar
Abhängigkeitsliste, und dort befindet sich dyld sucht zur Laufzeit nach libfoo
.
Die Verwendung des vollständigen Pfads funktioniert gut genug für Systembibliotheken, die tatsächlich an zuvor bekannten Stellen im Dateisystem installiert sind. Bei Bibliotheken, die Teil der App-Bundles sind, gibt es jedoch ein Problem. Obwohl jede Anwendung davon ausgehen kann, dass sie in /Applications/AppName.app
installiert wird, bedeutet dies im Allgemeinen, dass Anwendungsassemblys portabel sind und frei im Dateisystem verschoben werden können, sodass der spezifische Bibliothekspfad in solchen Assemblys dies nicht kann im Voraus bekannt sein.
Als Lösung für dieses Problem lässt Darwin zu, dass Installationsnamen mit @executable_path
, @loader_path
oder @rpath
- das ist nicht absolut, sondern gibt den Bibliothekspfad relativ zum Pfad zur ausführbaren Hauptdatei, den Pfad zum "Laden" (ausführbare Datei oder Bibliothek) an. Dies hängt direkt von dieser Bibliothek ab) bzw. relativ zur Liste der Pfade, die von der ausführbaren Hauptdatei definiert werden. @executable_path
und @loader_path
funktionieren ohne zusätzliche Komplexität. Wenn jedoch mindestens eine Ihrer Abhängigkeiten (oder deren transitiven Abhängigkeiten) einen Setup-Namen mit @rpath
, müssen Sie @rpath
explizit @rpath
wenn Sie Ihre ausführbare Datei mit der Option ld -rpath
mal wie viel du brauchst:
$ ld -o runme -rpath @executable_path/../Frameworks -rpath @executable_path/../bin/lib
(Das rpath-Konzept zerstört bis zu einem gewissen Grad die ursprüngliche Idee bekannter Bibliothekspfade und eröffnet die Möglichkeit von Dylib-Spoofing-Angriffen. Wir können davon ausgehen, dass dies alles, was mit Installationsnamen zu tun hat, ziemlich nutzlos macht.)
Zyklische Abhängigkeiten
Wenn der Quellcode eines Projekts mehrere Dateien belegt, ist es völlig normal, dass diese Dateien voneinander abhängig sind. Dies funktioniert einwandfrei, solange alle diese Dateien zu einer einzigen Binärdatei kompiliert werden - einer ausführbaren Datei oder einer Bibliothek. Was nicht funktioniert, ist, wenn mehrere dynamische Bibliotheken voneinander abhängig sind.
Sie können argumentieren, dass es sich lohnt, die Projektarchitektur neu zu gestalten, anstatt zyklische Abhängigkeiten zwischen dynamischen Bibliotheken zu verwenden, und ich stimme Ihnen zu. Aber wenn es etwas Typisches für Apple gibt, dann ist es so, dass sie nie aufhören, über Dinge nachzudenken und es richtig zu machen. Stattdessen legen sie Krücken und Tricks übereinander. In diesem Fall müssen für Darling zirkuläre Abhängigkeiten funktionieren, da die verschiedenen libSystem- libSystem
wie libsystem_dyld
, libsystem_kernel
und libsystem_pthread
voneinander abhängig sind. (Bis vor kurzem mussten wir aufgrund der Implementierung von Core OpenGL in The Cocotron auch Cocoa-Frameworks wie AppKit, Core Graphics und Core OpenGL durchlaufen, aber wir haben die Architektur unserer Implementierung von Core OpenGL neu gestaltet und konnten diesen Zyklus beseitigen Abhängigkeiten.)
Grundsätzlich sollten zirkuläre Abhängigkeiten gut funktionieren: Ein dynamischer Linker weiß bereits, wie jede Bibliothek nur einmal geladen wird, sodass er keine Probleme mit der unendlichen Rekursion hat. Das Problem ist, dass solche Bibliotheken nicht einfach verknüpft werden können , da bei jedem Linkeraufruf nur eine Bibliothek erstellt wird und beim Verknüpfen von Binärdateien alle bereits verknüpften Abhängigkeiten auf den Linker übertragen werden müssen. Wir müssen zuerst eine unserer Bibliotheken verknüpfen, und in diesem Moment sind die anderen noch nicht fertig, sodass wir sie nicht auf den Linker übertragen können.
Der Trick hier besteht darin, einige (oder der Einfachheit halber alle) dieser Bibliotheken zweimal zu verknüpfen. Weisen Sie den Linker zum ersten Mal an, die fehlenden Abhängigkeiten zu ignorieren und die Abhängigkeiten nicht zu übergeben:
$ ld -o libfoo.dylib foo.o -flat_namespace -undefined suppress $ ld -o libbar.dylib bar.o -flat_namespace -undefined suppress
(Siehe unten für -flat_namespace
.)
Wenn Sie versuchen, die resultierenden Dylibs direkt zu verwenden, werden zur Laufzeit natürlich dynamische Verbindungsfehler angezeigt. Verknüpfen Sie diese Bibliotheken stattdessen ein zweites Mal und übergeben Sie die resultierenden Dylibs als Abhängigkeiten:
$ ld -o libfoo.dylib foo.o libbar.dylib $ ld -o libbar.dylib bar.o libfoo.dylib
Dieses Mal sieht der Linker alle Zeichen, sodass wir ihn nicht anweisen, die Fehler zu ignorieren (und wenn einige Zeichen wirklich fehlen, wird ein Fehler angezeigt).
Trotz der Tatsache, dass einige, wenn nicht alle Bibliotheken, die mit den „falschen“ Kopien ihrer Abhängigkeiten verknüpft sind, zur Laufzeit die richtigen Versionen von dyld sehen. Damit dies funktioniert, stellen Sie sicher, dass beide Kopien jeder Bibliothek denselben Installationsnamen haben.
Ein weiteres Detail ist hier die Initialisierungsreihenfolge. Jeder Code kann Initialisiererfunktionen mit dem Compiler Magic-Befehl __attribute__((constructor))
deklarieren (die Liste solcher Initialisierer fällt in den Abschnitt __mod_init_func
in der Mach-O-Datei). Diese Funktionen werden von dyld aufgerufen, wenn die Binärdatei geladen wird, in der sie sich befinden, bevor main()
aufgerufen wird. In der Regel werden die Initialisierer jeder Bibliothek nach den Initialisierern ihrer Abhängigkeiten ausgeführt, sodass jeder Initialisierer erwarten kann, dass die Abhängigkeitsbibliotheken bereits initialisiert und betriebsbereit sind. Dies kann natürlich nicht für zyklische Abhängigkeiten garantiert werden. dyld führt ihre Initialisierer in einer bestimmten Reihenfolge aus. Sie können Abhängigkeiten als Aufwärtsabhängigkeiten markieren, um diese Reihenfolge anzupassen. dyld initialisiert zuletzt Bibliotheken, die jemand als seine Abhängigkeit markiert hat. Um libfoo
nach libfoo
initialisieren, verknüpfen Sie sie wie libbar
:
$ ld -o libfoo.dylib foo.o libbar.dylib $ ld -o libbar.dylib bar.o -upward_library libfoo.dylib
Um alles noch komfortabler zu machen, haben wir eine Darling CMake-Funktion namens add_circular
, die sich um alle Schwierigkeiten kümmert und es Ihnen ermöglicht, sie einfach und deklarativ so zu verwenden:
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 )
Zweistufiger Zeichennamensraum
Symboltabellen in Mach-O speichern nicht nur Symbolnamen, sondern „merken“ sich auch, aus welcher Bibliothek (oder ausführbaren Datei) welches Symbol stammt. Mit anderen Worten, Symbolnamen existieren in Namespaces, die von der Binärdatei definiert werden. daher der „zweistufige Namespace“ (eine andere Ebene bedeutet Symbolnamen selbst).
Ein zweistufiger Namespace wurde eingeführt , um Symbolnamenkonflikte zu vermeiden. Wenn mehrere Bibliotheken Zeichen mit demselben Namen definieren, wird normalerweise beim Verknüpfen eine Fehlermeldung angezeigt. Dies funktioniert jedoch möglicherweise nicht, wenn Sie Bibliotheken zur Laufzeit laden (z. B. Plugins) oder wenn sich die Versionen der Bibliotheken zur Verbindungszeit und zur Laufzeit unterscheiden. Für Bibliotheken, die einen zweistufigen Namespace verwenden, ist dies kein Problem. Mehrere Bibliotheken können Zeichen mit demselben Namen definieren, ohne Konflikte zu verursachen.
Ein zweistufiger Namespace kann deaktiviert werden, indem wieder ein "flacher Namespace" verwendet wird (einer der Gründe dafür ist, dass die Verwendung eines zweistufigen Namespace impliziert, dass jedes Zeichen während der Verknüpfung zugelassen werden muss, sodass für -undefined_suppress
as ein flacher Namespace erforderlich ist wir haben oben gesehen). Ld verfügt über zwei Flags, mit denen ein zweistufiger Namespace während der Verknüpfung -flat_namespace
kann: -flat_namespace
, der nur eine Mach-O-Datei betrifft, und -force_flat_namespace
, der nur mit ausführbaren Dateien und nicht mit Bibliotheken funktioniert und den gesamten Prozess zur Verwendung einer Flat erzwingt Namespace. Darüber hinaus können Sie dyld zwingen, zur Laufzeit einen flachen Namespace zu verwenden, indem Sie die Umgebungsvariable DYLD_FORCE_FLAT_NAMESPACE
.
Ein Merkmal der Verwendung eines zweistufigen Namespace ist, dass Sie jedes Mach-O immer explizit mit allen Bibliotheken und Frameworks verknüpfen müssen, von denen es abhängt. Wenn Sie beispielsweise auf AppKit verlinken, können Sie Foundation nicht einfach verwenden. Sie müssen explizit auf sie verlinken. Eine weitere Funktion ist, dass Sie als Autor einer Bibliothek oder eines Frameworks die Implementierung des Symbols entlang der Abhängigkeitskette nicht frei nach unten verschieben können, wie Sie es möglicherweise gewohnt sind (z. B. können Sie Code nicht einfach von AppKit nach Foundation verschieben). Um dies zu ermöglichen, verfügen Mach-O, ld und dyld über mehrere zusätzliche Funktionen, nämlich Unterbibliotheken , Reexport von Zeichen und Metazeichen .
Unterbibliotheken
Unterbibliotheken - ein Mechanismus, mit dem eine Bibliothek (als Fassadenbibliothek oder Dachbibliothek bezeichnet) die Implementierung eines Teils ihrer Funktionalität an eine andere Bibliothek (als Unterbibliothek bezeichnet) delegieren kann; oder, wenn Sie es von der anderen Seite betrachten, der Bibliothek erlauben, die von einer anderen Bibliothek bereitgestellten Symbole öffentlich wieder zu exportieren.
Der Hauptort, an dem dies verwendet wird, ist wiederum libSystem
mit seinen Unterbibliotheken, die sich in /usr/lib/system
. Es kann jedoch mit jedem Bibliothekspaar verwendet werden:
$ ld -o libfoo.dylib foo.o -lobjc -sub_library libobjc
Das einzige, was dies im Vergleich zur Verknüpfung mit dieser Bibliothek beeinflusst, ist, dass der Befehl LC_REEXPORT_DYLIB
anstelle der üblichen LC_LOAD_DYLIB
in die resultierende Datei LC_LOAD_DYLIB
(einschließlich der Symbole aus der Unterbibliothek während der Verknüpfung werden nicht in die Umbrella-Bibliothek kopiert, sodass dies nicht einmal erforderlich ist Link, wenn später neue Zeichen zur Unterbibliothek hinzugefügt werden). Zur Laufzeit LC_REEXPORT_DYLIB
auch ähnlich wie LC_LOAD_DYLIB
: dyld lädt die Unterbibliothek und stellt ihre Zeichen dem Rest zur Verfügung (im Gegensatz zu LC_LOAD_DYLIB
stammen die Zeichen jedoch in Bezug auf den zweistufigen Namespace aus der Umbrella-Bibliothek).
Was sich an LC_REEXPORT_DYLIB
wirklich unterscheidet, ist, was ld tut, wenn Sie eine andere Bibliothek mit libfoo
: Anstatt nur nach den Symbolen in allen Objekt- und Dylib-Dateien zu suchen, die an LC_REEXPORT_DYLIB übergeben wurden, öffnet ld auch die erneut exportierte Unterbibliothek (in dieser) libobjc
Beispiel).
Woher weiß er, wo er nach ihr suchen muss? Das einzige, was in libfoo.dylib
ist, ist der Installationsname libobjc.dylib
erwartet ld, dass er dort gefunden wird. Dies bedeutet, dass die Bibliothek an ihrem Platz installiert werden muss, bevor sie als Unterbibliothek für andere Zwecke verwendet werden kann. Dies funktioniert gut für Systembibliotheken wie libobjc
, kann jedoch sehr unpraktisch oder völlig unmöglich sein, wenn Sie versuchen, Ihre eigene Unterbibliothek erneut zu exportieren.
Um dieses Problem zu lösen, bietet ld die Option -dylib_file
, mit der Sie einen anderen Dylib-Pfad angeben können, der beim Verknüpfen verwendet werden soll:
$ 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
Obwohl libSystem
und einige andere Systembibliotheken ihre Unterbibliotheken erneut exportieren, müssen Sie -dylib_file
nicht verwenden, wenn Sie jede der ausführbaren Dateien unter macOS verknüpfen. Dies liegt daran, dass Systembibliotheken bereits entsprechend ihrem Installationsnamen installiert sind. Aber wenn wir Darling unter Linux -dylib_file
, müssen wir jedem ld-Aufruf einige -dylib_file
Optionen (und andere allgemeine Argumente) übergeben. Wir tun dies mit einer speziellen Funktion , die automatisch angewendet wird, wenn add_darling_library
, add_darling_executable
und andere verwendet werden.
Zeichen erneut exportieren
Manchmal muss eine Bibliothek einige der Zeichen - aber nicht alle auf einmal - aus einer anderen Bibliothek erneut exportieren. Beispielsweise NSObject
Core Foundation aus Kompatibilitätsgründen NSObject
, das in neueren Versionen innerhalb der Objective-C-Laufzeit implementiert ist.
(Wenn Sie daran interessiert sind, warum NSObject
einmal in der Core Foundation statt in der Foundation war, liegt dies daran, wie die kostenlose Konvertierung [gebührenfreie Überbrückung, die Möglichkeit, direkt zwischen den entsprechenden Core Foundation- und Foundation-Typen ohne zu NSObject
Zusätzliche Konvertierung] erfordert, dass private Wrapper-Klassen über Typen aus Core Foundation (z. B. __NSCFString
) in Core Foundation implementiert werden. Als Objective-C-Objekte müssen sie von NSObject
geerbt NSObject
. Wahrscheinlich könnte dies alles von implementiert werden zu einem anderen, NSObject
mit all seinen Erben in der Stiftung und der Schleife verlassen esky verbindet die Core Foundation und die Foundation, aber Apple hat beschlossen, diese privaten NSObject
zusammen mit NSObject
auf die Core Foundation zu migrieren, und in Darling tun wir dies auf die gleiche Weise, um die Kompatibilität aufrechtzuerhalten.)
-reexported_symbols_list
Option -reexported_symbols_list
können Sie eine Liste von Zeichen übergeben, die -reexported_symbols_list
werden -reexported_symbols_list
:
$ echo .objc_class_name_NSObject > reexport_list.exp $ ld -o CoreFoundation CFFiles.o -lobjc -reexported_symbols_list reexport_list.exp
Obwohl das erneute Exportieren einiger Symbole dem erneuten Exportieren aller Symbole sehr ähnlich klingt, unterscheidet sich der Mechanismus, mit dem dies implementiert wird, stark von der Funktionsweise der Unterbibliotheken. Es wird kein spezieller LC_*_DYLIB
Befehl verwendet. Stattdessen wird ein spezielles indirektes Symbol (angezeigt durch das N_INDIR
Flag) in die Namenstabelle eingefügt und verhält sich wie das in dieser Bibliothek definierte Symbol. Wenn die Bibliothek selbst dieses Symbol verwendet, wird die zweite „unbestimmte“ Kopie des Symbols in der Namenstabelle angezeigt (wie dies ohne erneuten Export geschieht).
Eine wichtige Kleinigkeit, die Sie beim expliziten Reexport von Zeichen beachten sollten, ist, dass Sie höchstwahrscheinlich Zeichen mit unterschiedlichen Namen für unterschiedliche Architekturen erneut exportieren müssen. In der Tat unterscheiden sich die Namenskonvention [Name Mangling Convention] für Objective-C und die Objective-C-Binärschnittstelle [ABI] für i386 und x86-64. Auf i386 müssen Sie also nur .objc_class_name_NSObject
und x86-64 - .objc_class_name_NSObject
_OBJC_CLASS_$_NSObject
, _OBJC_IVAR_$_NSObject.isa
und _OBJC_METACLASS_$_NSObject
. Sie müssen bei der Verwendung von Unterbibliotheken nicht darüber nachdenken, da dort alle automatisch verfügbaren Symbole für jede Architektur automatisch erneut exportiert werden.
Die meisten Tools für die transparente Arbeit mit Mach-O verarbeiten „dicke“ oder universelle Binärdateien (Mach-O-Dateien, die mehrere Sub-Mach-O-Dateien für mehrere Architekturen enthalten). Clang kann universelle Binärdateien mit allen angeforderten Architekturen kompilieren, dyld wählt aus, welche Architektur von dylib geladen werden soll, welche Architekturen die ausführbare Datei unterstützt, und Tools wie ld, otool und nm arbeiten mit der Architektur, die der Architektur des Computers entspricht (d. H. . x86-64), es sei denn, Sie benötigen ausdrücklich eine andere Architektur mit einem speziellen Flag. Das einzige, was uns dennoch daran erinnert, dass mehrere Architekturen verarbeitet werden, ist, dass Sie beim Kompilieren zweimal Fehler und Warnungen erhalten, einmal für jede Architektur.
Die Notwendigkeit, zwei verschiedene Reexportlisten zu übertragen, zerstört diese Illusion. Ld verfügt nicht über eine integrierte Option zur Verwendung unterschiedlicher Listen für unterschiedliche Architekturen. Dies bedeutet, dass wir die Dylibs für jede Architektur separat verknüpfen und sie dann mit lipo kombinieren müssen:
$ 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
In Darling verwenden wir eine CMake-Funktion namens add_separated_framework
, die innerhalb einer separaten Verknüpfung abstrahiert und Lipo aufruft. Ein echtes CMake-Skript zum Erstellen von Core Foundation sieht also ungefähr so aus :
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/
.