Trucos para vincular y descargar archivos Mach-O

Les presento la traducción de mi artículo del blog del Proyecto Darling. Un poco de ayuda sobre los conceptos utilizados: Darwin: un sistema operativo de código abierto que subyace en macOS, iOS y otros sistemas operativos de Apple; Mach-O es un formato binario de archivos ejecutables y bibliotecas utilizados en Darwin; dyld: gestor de arranque dinámico utilizado por Darwin para descargar archivos Mach-O; dylib es una biblioteca cargable dinámicamente (generalmente tiene la extensión .dylib ).


Una imagen para llamar la atención.


El objetivo del Proyecto Darling es permitir que las aplicaciones macOS se ejecuten en Linux, y la capacidad de descargar archivos binarios en formato Mach-O es uno de los pasos clave hacia este objetivo.


Inicialmente, Darling se basó en su propia implementación del cargador Mach-O y la idea de transmitir llamadas entre la API de Darwin de alto nivel y sus contrapartes de Linux. Desde entonces, nuestro enfoque se ha desplazado a ejecutar código en un contenedor Darwin cada vez más aislado. Desde que cambiamos a usar Mach-O para los componentes internos de Darling , hemos podido usar el dyld original de Apple, así como también hemos creado muchos otros componentes Darwin de código abierto. Todavía necesitamos un simple gestor de arranque Mach-O para arrancar dyld.


Mach-O, junto con Mach en sí, son probablemente los sellos distintivos más notables de Darwin, y las diversas bibliotecas y marcos proporcionados por Apple hacen un uso extensivo de muchas características oscuras del formato Mach-O. Esto hace que trabajar con Mach-O sea una de las tareas más importantes y notables en el desarrollo de Darling. Desde la implementación de cargadores nativos de Mach-O hasta el ensamblaje de los componentes de Darwin (primero en forma de archivos ELF especiales y ahora en forma de Mach-O real), necesitamos comprender la estructura interna de Mach-O a un nivel mucho más profundo que el que normalmente se requiere del ordinario desarrolladores para la plataforma Darwin.


Terminamos esta introducción y pasamos a discutir algunos de los trucos que el formato Mach-O nos puede ofrecer.


Nombres de instalación


En Windows y Linux, se hace referencia a las bibliotecas dinámicas por su nombre (por ejemplo, libc.so ), después de lo cual la tarea del vinculador dinámico es encontrar una biblioteca con un nombre coincidente en uno de los directorios de biblioteca estándar. Darwin usa de inmediato la ruta (casi) completa al archivo de la biblioteca, que se denomina nombre de instalación para esta biblioteca. Presumiblemente, esto se hizo para evitar la sustitución de dylib [secuestro de dylib], un ataque en el que un dylib falso se coloca en un directorio en el que un enlazador dinámico lo encontrará antes que el real, lo que permite que dylib falso ejecute código arbitrario en nombre de un programa que convenció descarga dylib.


No solo los archivos ejecutables y las bibliotecas almacenan los nombres completos de instalación de sus dependencias, sino que las dependencias de Mach-O "conocen" sus propios nombres de instalación. En realidad, así es como el enlazador aprende qué nombres de instalación usar para las dependencias: los lee de las dependencias mismas.


Al vincular dylib, especifique su nombre de instalación utilizando las opciones ld -install_name o -dylib_install_name :


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

Ahora, cuando vincule otro archivo Mach-O (por ejemplo, libbar.dylib ) a libfoo.dylib , ld escribirá el nombre de instalación de libfoo - /usr/local/lib/libfoo.dylib - en la libbar dependencias de libbar , y ahí es donde está dyld. buscará libfoo en tiempo de ejecución.


El uso de la ruta completa funciona lo suficientemente bien para las bibliotecas del sistema, que, de hecho, se instalan en lugares previamente conocidos en el sistema de archivos; pero con las bibliotecas que vienen como parte de los paquetes de aplicaciones, hay un problema. Aunque cada aplicación podría suponer que se instalará en /Applications/AppName.app , en general, significa que los ensamblajes de aplicaciones son portátiles y se pueden mover libremente por el sistema de archivos, por lo que la ruta de la biblioteca específica dentro de dichos ensamblajes no puede ser conocido de antemano.


Como solución a este problema, Darwin permite que los nombres de instalación comiencen con @executable_path , @loader_path o @rpath ; es decir, no es absoluto, pero indica la ruta de la biblioteca relativa a la ruta al archivo ejecutable principal, la ruta a la "carga" (archivo o biblioteca ejecutable, que depende directamente de esta biblioteca) o en relación con la lista de rutas definidas por el archivo ejecutable principal, respectivamente. @executable_path y @loader_path funcionan sin complejidad adicional, pero si al menos una de sus dependencias (o sus dependencias transitivas) tiene un nombre de configuración usando @rpath , debe establecer explícitamente @rpath al vincular su archivo ejecutable utilizando la opción ld -rpath veces cuánto necesitas:


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

(El concepto rpath destruye hasta cierto punto la idea original de rutas de biblioteca conocidas y abre la posibilidad de ataques de falsificación de dylib. Podemos suponer que esto hace que todo lo relacionado con los nombres de instalación sea bastante inútil).


Dependencias cíclicas


Cuando el código fuente de un proyecto ocupa varios archivos, es completamente normal si estos archivos dependen mutuamente entre sí. Esto funciona bien siempre que todos estos archivos se compilen en un solo binario: un archivo ejecutable o una biblioteca. Lo que no funciona es cuando varias bibliotecas dinámicas dependen unas de otras.


Puede argumentar que en lugar de utilizar dependencias cíclicas entre bibliotecas dinámicas, vale la pena rediseñar la arquitectura del proyecto, y estoy de acuerdo con usted. Pero si hay algo típico de Apple, es que nunca se detienen a pensar las cosas y hacerlo bien; en cambio, ponen muletas y trucos uno encima del otro. En este caso, para Darling, necesitamos hacer que funcionen las dependencias circulares, porque las diversas libSystem libsystem_dyld , como libsystem_dyld , libsystem_kernel y libsystem_pthread , son dependientes entre sí. (Hasta hace poco, también teníamos que vincular cíclicamente los marcos de Cocoa como AppKit, Core Graphics y Core OpenGL, debido a la forma en que Core OpenGL se implementó en The Cocotron, pero rediseñamos la arquitectura de nuestra implementación de Core OpenGL y pudimos deshacernos de este cíclico dependencias)


En principio, las dependencias circulares deberían funcionar bien: un vinculador dinámico ya sabe cómo cargar cada biblioteca solo una vez, por lo que no tendrá problemas de recursión infinita. El problema es que tales bibliotecas no se pueden vincular simplemente, ya que cada llamada del vinculador crea solo una biblioteca, y al vincular cualquier binario, todas sus dependencias que ya están vinculadas deben transferirse al vinculador. Primero debemos vincular una de nuestras bibliotecas, y en este momento las otras aún no están listas, por lo que no podremos transferirlas al vinculador.


El truco aquí es vincular algunas (o por simplicidad, todas) de estas bibliotecas dos veces . Por primera vez, dígale al enlazador que ignore las dependencias que faltan, y realmente, no pase las dependencias:


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

(Consulte a continuación para -flat_namespace ).


Por supuesto, si intenta utilizar directamente los dylibs resultantes, obtendrá errores de enlace dinámico en tiempo de ejecución. En su lugar, vincule estas bibliotecas por segunda vez, pasando las dylibs resultantes como dependencias:


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

Esta vez, el vinculador ve todos los caracteres, por lo que no le decimos que ignore los errores (y si faltan algunos caracteres, obtendrá un error).


A pesar de que algunas, si no todas, las bibliotecas vinculadas a las copias "incorrectas" de sus dependencias, dyld verá las versiones correctas en tiempo de ejecución. Para que esto funcione, asegúrese de que ambas copias de cada biblioteca tengan el mismo nombre de instalación.


Otro detalle aquí es el orden de inicialización. Cualquier código puede declarar funciones de inicializador utilizando el comando mágico del compilador __attribute__((constructor)) (la lista de dichos inicializadores cae en la sección __mod_init_func en el archivo Mach-O). Dyld llama a estas funciones al cargar el binario en el que se encuentran antes de llamar a main() . Por lo general, los inicializadores de cada biblioteca se ejecutan después de los inicializadores de sus dependencias, por lo que cada inicializador puede esperar que las bibliotecas de dependencias ya estén inicializadas y listas para funcionar. Por supuesto, esto no puede garantizarse para dependencias cíclicas; dyld ejecutará sus inicializadores en algún orden. Puede marcar dependencias como dependencias ascendentes para personalizar este orden; dyld inicializará las bibliotecas que alguien ha marcado como su dependencia hasta el final. Entonces, para hacer que libfoo inicialice después de libbar , libbar así:


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

Para que todo sea más conveniente, tenemos una función Darling CMake llamada add_circular , que se ocupa de todas las dificultades y le permite usarla así de manera simple y declarativa:


 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 ) 

Espacio de nombres de caracteres de dos niveles


Las tablas de símbolos en Mach-O no solo almacenan nombres de símbolos, sino que también "recuerdan" de qué biblioteca (o archivo ejecutable) se toma el símbolo. En otras palabras, los nombres de símbolos existen en espacios de nombres definidos por los cuales el binario los define; de ahí el "espacio de nombres de dos niveles" (otro nivel significa nombres de símbolos en sí mismos).


Se introdujo un espacio de nombres de dos niveles para evitar conflictos de nombres de símbolos. Por lo general, si varias bibliotecas definen caracteres con el mismo nombre, recibirá un error durante el enlace; pero esto puede no funcionar si carga bibliotecas en tiempo de ejecución (por ejemplo, complementos) o cuando las versiones de las bibliotecas en tiempo de enlace y tiempo de ejecución difieren. Para las bibliotecas que usan un espacio de nombres de dos niveles, esto no es un problema: permite que varias bibliotecas definan caracteres con el mismo nombre sin crear conflictos.


Un espacio de nombres de dos niveles se puede desactivar volviendo a usar un "espacio de nombres plano" (una de las razones para esto es que usar un espacio de nombres de dos niveles implica que cada carácter debe estar permitido durante la vinculación, por lo que se requiere un espacio de nombres plano para -undefined_suppress , como vimos arriba). Ld tiene dos indicadores que permiten desactivar el espacio de nombres de dos niveles durante el enlace: -flat_namespace , que afecta solo a un archivo Mach-O, y -force_flat_namespace , que funciona solo con archivos ejecutables, no bibliotecas, y obliga a todo el proceso a usar un plano espacio de nombres Además, puede forzar a dyld a usar un espacio de nombres plano en tiempo de ejecución configurando la variable de entorno DYLD_FORCE_FLAT_NAMESPACE .


Una característica del uso de un espacio de nombres de dos niveles es que siempre debe vincular explícitamente cada Mach-O a todas las bibliotecas y marcos de los que depende. Por ejemplo, si vincula a AppKit, no puede simplemente usar Foundation; tienes que vincularla explícitamente con ella. Otra característica es que, como autor de una biblioteca o marco, no puede mover libremente la implementación del símbolo "hacia abajo" a lo largo de la cadena de dependencia, como podría acostumbrarse a hacer (por ejemplo, no puede simplemente mover el código de AppKit a Foundation). Para permitir esto, Mach-O, ld y dyld tienen varias características adicionales, a saber, sub-bibliotecas , reexportación de caracteres y metacaracteres .


Sub bibliotecas


Subbibliotecas: un mecanismo que permite que una biblioteca (llamada biblioteca de fachada [biblioteca paraguas] o biblioteca paraguas [biblioteca paraguas]) delegue la implementación de parte de su funcionalidad a otra biblioteca (llamada su biblioteca secundaria [biblioteca secundaria]); o, si lo mira desde el otro lado, permitiendo que la biblioteca vuelva a exportar públicamente los símbolos proporcionados por otra biblioteca.


El lugar principal donde se usa esto es, nuevamente, libSystem con sus sub-bibliotecas que están en /usr/lib/system ; pero se puede usar con cualquier par de bibliotecas:


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

Lo único que afecta esto en comparación con solo vincular a esa biblioteca es que el comando LC_REEXPORT_DYLIB se escribe en el archivo resultante en lugar del LC_LOAD_DYLIB habitual (incluidos los símbolos de la sub-biblioteca durante el enlace no se copian en la biblioteca general, por lo que ni siquiera tiene que enlace si se agregan nuevos caracteres a la subbiblioteca más adelante). En tiempo de ejecución, LC_REEXPORT_DYLIB también funciona de manera similar a LC_LOAD_DYLIB : dyld cargará la sub-biblioteca y hará que sus caracteres estén disponibles para el resto (pero a diferencia de LC_LOAD_DYLIB , en términos del espacio de nombres de dos niveles, los caracteres vendrán de la biblioteca paraguas).


Lo que realmente difiere de LC_REEXPORT_DYLIB es lo que hace ld cuando vincula otra biblioteca a libfoo : en lugar de solo buscar los símbolos en todos los objetos y archivos dylib que se le pasaron, ld también se abrirá y verá la subbiblioteca reexportada (en este ejemplo de libobjc ).


¿Cómo sabe dónde buscarla? Lo único guardado en libfoo.dylib es el nombre de instalación libobjc.dylib , por lo que es allí donde ld espera encontrarlo. Esto significa que la biblioteca debe instalarse en su lugar antes de que pueda usarse como una sub-biblioteca para cualquier otra cosa; esto funciona bien para bibliotecas del sistema como libobjc , pero puede ser muy inconveniente o completamente imposible si está intentando reexportar su propia subbiblioteca.


Para resolver este problema, ld proporciona la opción -dylib_file , que le permite especificar una ruta diferente de dylib para usar durante el enlace:


 $ 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 

Aunque libSystem y algunas otras bibliotecas del sistema reexportan sus sub-bibliotecas, no es necesario que use -dylib_file al vincular cada uno de los archivos ejecutables en macOS; Esto se debe a que las bibliotecas del sistema ya están instaladas de acuerdo con su nombre de instalación. Pero cuando construimos Darling en Linux, tenemos que pasar algunas opciones -dylib_file (y otros argumentos comunes) a cada llamada ld. Hacemos esto con una función especial que se aplica automáticamente cuando se utiliza add_darling_library , add_darling_executable y otros.


Reexportando personajes


A veces, una biblioteca puede necesitar reexportar algunos de los caracteres, pero no todos a la vez, desde otra biblioteca. Por ejemplo, Core Foundation está NSObject , que en versiones recientes se implementa dentro del tiempo de ejecución de Objective-C, por compatibilidad.


(Si está interesado en NSObject por qué NSObject estuvo una vez en Core Foundation en lugar de Foundation, es porque la conversión gratuita [puente gratuito, la capacidad de emitir directamente entre los tipos Core Foundation y Foundation correspondientes sin conversión adicional], requiere que se implementen clases privadas de envoltura sobre tipos de Core Foundation (por ejemplo, __NSCFString ) en Core Foundation, y como objetos Objective-C, deben ser heredados de NSObject . Probablemente, todo esto podría ser realizado por a otro, dejando a NSObject con todos sus herederos en la Fundación y el bucle esky vinculando Core Foundation y Foundation, pero Apple decidió migrar estas clases privadas auxiliares junto con NSObject a Core Foundation, y en Darling lo hacemos de la misma manera para mantener la compatibilidad).


Puede pasar una lista de caracteres para -reexported_symbols_list utilizando su opción -reexported_symbols_list :


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

Aunque la reexportación de algunos símbolos suena muy similar a la reexportación de todos los símbolos, el mecanismo por el cual se implementa esto es muy diferente de cómo funcionan las subbibliotecas. No se utiliza LC_*_DYLIB comando especial LC_*_DYLIB ; en su lugar, se inserta un símbolo indirecto especial (indicado por el indicador N_INDIR ) en la tabla de nombres, y se comporta como el símbolo definido en esta biblioteca. Si la biblioteca misma usa este símbolo, la segunda copia "indefinida" del símbolo aparecerá en la tabla de nombres (como ocurre sin ninguna reexportación).


Una pequeña cosa importante a tener en cuenta al usar la reexportación explícita de caracteres es que lo más probable es que necesite reexportar caracteres con diferentes nombres para diferentes arquitecturas. De hecho, la convención de nomenclatura [convención de cambio de nombre] para Objective-C y la interfaz binaria Objective-C [ABI] para i386 y x86-64 son diferentes, por lo que en i386 solo necesita .objc_class_name_NSObject , y en x86-64 - _OBJC_CLASS_$_NSObject , _OBJC_IVAR_$_NSObject.isa y _OBJC_METACLASS_$_NSObject . No tiene que pensar en ello cuando use sub-bibliotecas, ya que todos los símbolos disponibles automáticamente para cada arquitectura se reexportan automáticamente.


La mayoría de las herramientas para trabajar con Mach-O tratan de forma transparente con binarios "gruesos" o universales (archivos Mach-O que contienen varios sub-Mach-O para varias arquitecturas). Clang puede compilar binarios universales con todas las arquitecturas solicitadas, dyld elige qué arquitectura cargar desde dylib, observa qué arquitecturas admite el ejecutable y herramientas como ld, otool y nm funcionan con la arquitectura correspondiente a la arquitectura de la computadora (p. Ej. x86-64), a menos que requiera explícitamente una arquitectura diferente con un indicador especial. , - , – , , .


. 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/es417507/


All Articles