Je vous présente la traduction de mon article du blog du Darling Project. Un peu d'aide sur les concepts utilisés: Darwin - un système d'exploitation open source qui sous-tend macOS, iOS et d'autres systèmes d'exploitation d'Apple; Mach-O est un format binaire de fichiers exécutables et de bibliothèques utilisés dans Darwin; dyld - chargeur de démarrage dynamique utilisé par Darwin pour télécharger des fichiers Mach-O; dylib est une bibliothèque chargeable dynamiquement (a généralement l'extension .dylib
).

L'objectif du projet Darling est de permettre aux applications macOS de s'exécuter sur Linux, et la possibilité de télécharger des fichiers binaires au format Mach-O est l'une des étapes clés vers cet objectif.
Initialement, Darling a été construit autour de sa propre implémentation du chargeur Mach-O et de l'idée de diffuser des appels entre l'API Darwin de haut niveau et ses homologues Linux. Depuis lors, nous nous sommes concentrés sur l'exécution de code dans un conteneur Darwin de plus en plus isolé. Depuis que nous sommes passés à l’utilisation de Mach-O pour les composants internes de Darling , nous avons pu utiliser le colorant original d’Apple, ainsi que créer de nombreux autres composants Darwin open source. Nous avons encore besoin d'un simple chargeur de démarrage Mach-O pour démarrer le colorant lui-même.
Mach-O, avec Mach lui-même, sont probablement les caractéristiques les plus remarquables de Darwin, et les diverses bibliothèques et frameworks fournis par Apple utilisent largement de nombreuses fonctionnalités obscures du format Mach-O. Cela fait du travail avec Mach-O l'une des tâches les plus importantes et les plus notables du développement de Darling. De l'implémentation de chargeurs Mach-O natifs à l'assemblage de composants Darwin (d'abord sous la forme de fichiers ELF spéciaux, et maintenant sous la forme de vrais Mach-O) - nous devons comprendre la structure interne de Mach-O à un niveau beaucoup plus profond que ce qui est habituellement exigé de l'ordinaire développeurs pour la plate-forme Darwin.
Nous terminons cette introduction et passons à l'examen de certaines des astuces que le format Mach-O peut nous offrir.
Noms d'installation
Sous Windows et Linux, les bibliothèques dynamiques sont référencées par leur nom (par exemple, libc.so
), après quoi la tâche de l'éditeur de liens dynamiques est de trouver une bibliothèque avec un nom correspondant dans l'un des répertoires de bibliothèque standard. Darwin utilise immédiatement le chemin d'accès (presque) complet au fichier de bibliothèque, qui est appelé le nom d'installation de cette bibliothèque. Vraisemblablement, cela a été fait pour empêcher la substitution de dylib [détournement de dylib] - une attaque dans laquelle un faux dylib est placé dans un répertoire dans lequel il sera trouvé par un éditeur de liens dynamique plus tôt que le vrai, ce qui permet à un faux dylib d'exécuter du code arbitraire au nom du programme, ce qui l'a donc convaincu téléchargement de dylib.
Non seulement les exécutables et les bibliothèques stockent les noms d'installation complets de leurs dépendances, mais les dépendances Mach-O elles-mêmes «connaissent» leurs propres noms d'installation. En fait, c'est ainsi que l'éditeur de liens apprend quels noms d'installation utiliser pour les dépendances: il les lit à partir des dépendances elles-mêmes.
Lors de la liaison de dylib, vous spécifiez son nom d'installation à l'aide des options ld -install_name
ou -dylib_install_name
:
$ ld -o libfoo.dylib foo.o -install_name /usr/local/lib/libfoo.dylib
Maintenant, lorsque vous liez un autre fichier Mach-O (par exemple libbar.dylib
) à libfoo.dylib
, ld écrira le nom d'installation de libfoo - /usr/local/lib/libfoo.dylib
- dans la libbar
dépendances de libbar
, et c'est là que se trouve dyld recherchera libfoo
lors de l'exécution.
L'utilisation du chemin complet fonctionne assez bien pour les bibliothèques système, qui, en fait, sont installées à des emplacements précédemment connus du système de fichiers; mais avec les bibliothèques qui font partie des ensembles d'applications, il y a un problème. Bien que chaque application puisse supposer qu'elle sera installée dans /Applications/AppName.app
, d'une manière générale, cela signifie que les assemblys d'application sont portables et peuvent être librement déplacés dans le système de fichiers, de sorte que le chemin de bibliothèque spécifique à l'intérieur de ces assemblys ne peut pas être connu à l'avance.
Pour résoudre ce problème, Darwin permet aux noms d'installation de commencer par @executable_path
, @loader_path
ou @rpath
- qui n'est pas absolu, mais indique le chemin de la bibliothèque par rapport au chemin d'accès au fichier exécutable principal, le chemin d'accès au "chargement" (fichier exécutable ou bibliothèque, qui dépend directement de cette bibliothèque) ou par rapport à la liste des chemins définis respectivement par le fichier exécutable principal. @executable_path
et @loader_path
fonctionnent sans complexité supplémentaire, mais si au moins une de vos dépendances (ou leurs dépendances transitives) a un nom de configuration utilisant @rpath
, vous devez explicitement définir @rpath
lors de la liaison de votre fichier exécutable en utilisant -rpath
option ld -rpath
fois combien vous avez besoin:
$ ld -o runme -rpath @executable_path/../Frameworks -rpath @executable_path/../bin/lib
(Le concept rpath détruit dans une certaine mesure l'idée originale de chemins de bibliothèque bien connus et ouvre la possibilité d'attaques d'usurpation de dylib. Nous pouvons supposer que cela rend tout ce qui concerne les noms d'installation assez inutile.)
Dépendances cycliques
Lorsque le code source d'un projet occupe plusieurs fichiers, il est tout à fait normal que ces fichiers soient mutuellement dépendants les uns des autres. Cela fonctionne bien tant que tous ces fichiers sont compilés en un seul binaire - un fichier exécutable ou une bibliothèque. Ce qui ne fonctionne pas, c'est lorsque plusieurs bibliothèques dynamiques dépendent les unes des autres.
Vous pouvez affirmer qu'au lieu d'utiliser des dépendances cycliques entre bibliothèques dynamiques, il vaut la peine de repenser l'architecture du projet, et je suis d'accord avec vous. Mais s'il y a quelque chose de typique chez Apple, c'est qu'ils n'arrêtent jamais de réfléchir et de bien faire les choses; au lieu de cela, ils posent des béquilles et des tours les uns sur les autres. Dans ce cas, pour Darling, nous devons faire fonctionner les dépendances circulaires, car les différentes libSystem
libsystem_dyld
, telles que libsystem_dyld
, libsystem_kernel
et libsystem_pthread
, dépendent toutes les unes des autres. (Jusqu'à récemment, nous devions également lier cycliquement les frameworks Cocoa tels que AppKit, Core Graphics et Core OpenGL, en raison de la façon dont Core OpenGL a été implémenté dans The Cocotron, mais nous avons repensé l' architecture de notre implémentation de Core OpenGL et avons pu nous débarrasser de cette cyclique dépendances.)
En principe, les dépendances circulaires devraient fonctionner correctement: un éditeur de liens dynamique ne sait déjà comment charger chaque bibliothèque qu'une seule fois, il n'aura donc pas de problèmes de récursion infinie. Le problème est que de telles bibliothèques ne peuvent pas être simplement liées , car chaque appel de l'éditeur de liens crée une seule bibliothèque et lors de la liaison de binaires, toutes ses dépendances déjà liées doivent être transférées vers l'éditeur de liens. Nous devons d'abord lier une de nos bibliothèques, et pour le moment, les autres ne sont pas encore prêtes, nous ne pourrons donc pas les transférer vers l'éditeur de liens.
L'astuce ici est de lier deux (ou pour plus de simplicité, toutes) ces bibliothèques deux fois . Pour la première fois, dites à l'éditeur de liens d'ignorer les dépendances manquantes et, en réalité, ne transmettez pas les dépendances:
$ ld -o libfoo.dylib foo.o -flat_namespace -undefined suppress $ ld -o libbar.dylib bar.o -flat_namespace -undefined suppress
(Voir ci-dessous pour -flat_namespace
.)
Bien sûr, si vous essayez d'utiliser directement les dylibs résultants, vous obtiendrez des erreurs de lien dynamique au moment de l'exécution. Au lieu de cela, liez ces bibliothèques une deuxième fois, en passant les dylibs résultantes en tant que dépendances:
$ ld -o libfoo.dylib foo.o libbar.dylib $ ld -o libbar.dylib bar.o libfoo.dylib
Cette fois, l'éditeur de liens voit tous les caractères, nous ne lui demandons donc pas d'ignorer les erreurs (et si certains caractères sont vraiment manquants, vous obtiendrez une erreur).
Malgré le fait que certaines, sinon toutes, des bibliothèques liées aux «mauvaises» copies de leurs dépendances, dyld verra les versions correctes au moment de l'exécution. Pour que cela fonctionne, assurez-vous que les deux copies de chaque bibliothèque ont le même nom d'installation.
Un autre détail ici est l'ordre d'initialisation. Tout code peut déclarer des fonctions d'initialisation à l'aide de la commande magique du compilateur __attribute__((constructor))
(la liste de ces initialiseurs tombe dans la section __mod_init_func
du fichier Mach-O). Ces fonctions sont appelées par dyld lors du chargement du binaire dans lequel elles se trouvent avant d'appeler main()
. En règle générale, les initialiseurs de chaque bibliothèque sont exécutés après les initialiseurs de ses dépendances, de sorte que chaque initialiseur peut s'attendre à ce que les bibliothèques de dépendances soient déjà initialisées et prêtes à fonctionner. Bien sûr, cela ne peut pas être garanti pour les dépendances cycliques; dyld exécutera leurs initialiseurs dans un certain ordre. Vous pouvez marquer les dépendances comme des dépendances ascendantes pour personnaliser cet ordre; dyld initialisera en dernier les bibliothèques que quelqu'un a marquées comme sa dépendance. Donc, pour faire initialiser libfoo
après libbar
, liez-les comme ceci:
$ ld -o libfoo.dylib foo.o libbar.dylib $ ld -o libbar.dylib bar.o -upward_library libfoo.dylib
Pour le rendre plus pratique, nous avons une fonction Darling CMake appelée add_circular
, qui s'occupe de toutes les difficultés et vous permet de l'utiliser comme ça simplement et de manière déclarative:
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 )
Espace de noms de caractères à deux niveaux
Les tables de symboles dans Mach-O ne stockent pas seulement les noms de symboles, elles «se souviennent» également de quelle bibliothèque (ou fichier exécutable) quel symbole est extrait. En d'autres termes, les noms de symboles existent dans des espaces de noms définis par quel binaire les définit; d'où «l'espace de noms à deux niveaux» (un autre niveau signifie les noms de symboles eux-mêmes).
Un espace de noms à deux niveaux a été introduit pour éviter les conflits de noms de symboles. Habituellement, si plusieurs bibliothèques définissent des caractères avec le même nom, vous obtiendrez une erreur lors de la liaison; mais cela peut ne pas fonctionner si vous chargez des bibliothèques au moment de l'exécution (par exemple, des plug-ins) ou lorsque les versions des bibliothèques au moment du lien et à l'exécution diffèrent. Pour les bibliothèques qui utilisent un espace de noms à deux niveaux, ce n'est pas un problème - cela permet à plusieurs bibliothèques de définir des caractères avec le même nom sans créer de conflits.
Un espace de noms à deux niveaux peut être désactivé en revenant à l'utilisation d'un "espace de noms plat" (l'une des raisons en est que l'utilisation d'un espace de noms à deux niveaux implique que chaque caractère doit être autorisé pendant la liaison, donc un espace de noms plat est requis pour -undefined_suppress
, comme nous avons vu ci-dessus). Ld a deux indicateurs qui permettent de désactiver l'espace de noms à deux niveaux lors de la liaison: -flat_namespace
, qui affecte un seul fichier Mach-O, et -force_flat_namespace
, qui fonctionne uniquement avec les fichiers exécutables, pas les bibliothèques, et force l'ensemble du processus à utiliser un flat espace de noms. De plus, vous pouvez forcer dyld à utiliser un espace de noms plat lors de l'exécution en définissant la variable d'environnement DYLD_FORCE_FLAT_NAMESPACE
.
L'une des caractéristiques de l'utilisation d'un espace de noms à deux niveaux est que vous devez toujours lier explicitement chaque Mach-O à toutes les bibliothèques et infrastructures dont il dépend. Par exemple, si vous vous connectez à AppKit, vous ne pouvez pas simplement utiliser Foundation; vous devez établir un lien explicite avec elle. Une autre caractéristique est que, en tant qu'auteur d'une bibliothèque ou d'un framework, vous ne pouvez pas déplacer librement l'implémentation du symbole "vers le bas" le long de la chaîne de dépendance, comme vous pourriez vous y habituer (par exemple, vous ne pouvez pas simplement déplacer du code d'AppKit vers Foundation). Pour permettre cela, Mach-O, ld et dyld ont plusieurs fonctionnalités supplémentaires, à savoir les sous-bibliothèques , la réexportation des caractères et les méta-caractères .
Sous-bibliothèques
Sous-bibliothèques - un mécanisme qui permet à une bibliothèque (appelée bibliothèque de façade [bibliothèque parapluie] ou bibliothèque parapluie [bibliothèque parapluie]) de déléguer la mise en œuvre d'une partie de ses fonctionnalités à une autre bibliothèque (appelée sa sous-bibliothèque [sous-bibliothèque]); ou, si vous le regardez de l'autre côté, permettre à la bibliothèque de réexporter publiquement les symboles fournis par une autre bibliothèque.
L'endroit principal où cela est utilisé est, encore une fois, libSystem
avec ses sous-bibliothèques qui sont dans /usr/lib/system
; mais il peut être utilisé avec n'importe quelle paire de bibliothèques:
$ ld -o libfoo.dylib foo.o -lobjc -sub_library libobjc
La seule chose qui affecte cela par rapport à la simple liaison à cette bibliothèque est que la commande LC_REEXPORT_DYLIB
est écrite dans le fichier résultant au lieu de la LC_LOAD_DYLIB
habituelle (y compris les symboles de la sous-bibliothèque lors de la liaison ne sont pas copiés dans la bibliothèque parapluie, donc elle n'a même pas à lien si de nouveaux caractères sont ajoutés à la sous-bibliothèque ultérieurement). Au moment de l'exécution, LC_REEXPORT_DYLIB
fonctionne également de la même manière que LC_LOAD_DYLIB
: dyld chargera la sous-bibliothèque et mettra ses caractères à la disposition du reste (mais contrairement à LC_LOAD_DYLIB
, en termes d'espace de noms à deux niveaux, les caractères proviendront de la bibliothèque parapluie).
Ce qui diffère vraiment à propos de LC_REEXPORT_DYLIB
c'est ce que ld fait lorsque vous liez une autre bibliothèque à libfoo
: au lieu de simplement rechercher les symboles dans tous les fichiers objet et dylib qui lui ont été transmis, ld ouvrira et affichera également la sous-bibliothèque réexportée (dans ce exemple libobjc
).
Comment sait-il où la chercher? La seule chose enregistrée dans libfoo.dylib
est le nom d'installation libobjc.dylib
, c'est donc là que ld s'attend à le trouver. Cela signifie que la bibliothèque doit être installée à sa place avant de pouvoir être utilisée comme sous-bibliothèque pour autre chose; cela fonctionne bien pour les bibliothèques système comme libobjc
, mais cela peut être très gênant ou complètement impossible si vous essayez de réexporter votre propre sous-bibliothèque.
Pour résoudre ce problème, ld fournit l'option -dylib_file
, qui vous permet de spécifier un chemin dylib différent à utiliser lors de la liaison:
$ 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
Bien que libSystem
et certaines autres bibliothèques système réexportent leurs sous-bibliothèques, vous n'avez pas besoin d'utiliser -dylib_file
lors de la liaison de chacun des fichiers exécutables sur macOS; cela est dû au fait que les bibliothèques système sont déjà installées en fonction de leur nom d'installation. Mais lors de la construction de Darling sur Linux, nous devons passer quelques options -dylib_file
(et d'autres arguments courants) à chaque appel ld. Nous le faisons avec une fonction spéciale qui est automatiquement appliquée lors de l'utilisation de add_darling_library
, add_darling_executable
et autres.
Réexporter des personnages
Parfois, une bibliothèque peut avoir besoin de réexporter certains des caractères - mais pas tous en même temps - à partir d'une autre bibliothèque. Par exemple, Core Foundation NSObject
, qui, dans les versions récentes, est implémenté dans l'environnement d'exécution Objective-C, pour des raisons de compatibilité.
(Si vous êtes intéressé par la raison pour laquelle NSObject
était une fois dans la Core Foundation au lieu de la Foundation, c'est parce que la conversion gratuite [pontage sans frais, la possibilité de diffuser directement entre les types de Core Foundation et Foundation correspondants sans conversion supplémentaire], nécessite que les classes wrapper privées sur les types de Core Foundation (par exemple, __NSCFString
) soient implémentées dans Core Foundation; et en tant qu'objets Objective-C, elles doivent être héritées de NSObject
. Probablement, tout cela pourrait être réalisé par à un autre, laissant NSObject
avec tous ses héritiers dans la Fondation et la boucle esky reliant la Core Foundation et la Foundation, mais Apple a décidé de migrer ces classes privées d'assistance avec NSObject
vers la Core Foundation, et dans Darling, nous procédons de la même manière pour maintenir la compatibilité.)
Vous pouvez transmettre à ld une liste de caractères à -reexported_symbols_list
aide de son option -reexported_symbols_list
:
$ echo .objc_class_name_NSObject > reexport_list.exp $ ld -o CoreFoundation CFFiles.o -lobjc -reexported_symbols_list reexport_list.exp
Bien que la réexportation de certains symboles semble très similaire à la réexportation de tous les symboles, le mécanisme par lequel cela est implémenté est très différent de la façon dont les sous-bibliothèques fonctionnent. Aucune commande LC_*_DYLIB
n'est utilisée; à la place, un symbole indirect spécial (indiqué par l'indicateur N_INDIR
) est inséré dans la table de noms et il se comporte comme le symbole défini dans cette bibliothèque. Si la bibliothèque elle-même utilise ce symbole, la deuxième copie «indéfinie» du symbole apparaîtra dans la table des noms (comme cela se produit sans aucune réexportation).
Une petite chose importante à garder à l'esprit lors de l'utilisation de la réexportation explicite de caractères est que vous devez probablement réexporter des caractères avec des noms différents pour différentes architectures. En effet, la convention de dénomination [convention de dénomination des noms] pour Objective-C et l'interface binaire Objective-C [ABI] pour i386 et x86-64 sont différentes, donc sur i386 vous n'avez qu'à .objc_class_name_NSObject
, et sur x86-64 - _OBJC_CLASS_$_NSObject
, _OBJC_IVAR_$_NSObject.isa
et _OBJC_METACLASS_$_NSObject
. Vous n'avez pas à y penser lorsque vous utilisez des sous-bibliothèques, car tous les symboles disponibles automatiquement pour chaque architecture sont automatiquement réexportés.
La plupart des outils pour travailler avec Mach-O traitent de manière transparente les binaires «épais» ou universels (fichiers Mach-O contenant plusieurs sous-Mach-O pour plusieurs architectures). Clang peut compiler des binaires universels avec toutes les architectures demandées, dyld sélectionne l'architecture à charger à partir de dylib, en examinant les architectures prises en charge par l'exécutable et des outils comme ld, otool et nm fonctionnent avec l'architecture correspondant à l'architecture de l'ordinateur (c.-à-d. . x86-64), sauf si vous avez explicitement besoin d'une architecture différente avec un indicateur spécial. , - , – , , .
. 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/
.