Truques para vincular e baixar arquivos Mach-O

Apresento a você a tradução do meu artigo no blog do Projeto Darling. Uma pequena ajuda nos conceitos utilizados: Darwin - um sistema operacional de código aberto que sustenta o macOS, iOS e outros sistemas operacionais da Apple; Mach-O é um formato binário de arquivos executáveis ​​e bibliotecas usados ​​em Darwin; dyld - gerenciador de inicialização dinâmico usado por Darwin para baixar arquivos Mach-O; O dylib é uma biblioteca carregável dinamicamente (geralmente possui a extensão .dylib ).


Uma imagem para atrair a atenção


O objetivo do Projeto Darling é permitir que aplicativos do MacOS sejam executados no Linux, e a capacidade de baixar arquivos binários no formato Mach-O é uma das principais etapas para esse objetivo.


Inicialmente, o Darling foi construído em torno de sua própria implementação do carregador Mach-O e da idéia de transmitir chamadas entre a API de alto nível da Darwin e suas contrapartes do Linux. Desde então, nosso foco mudou para a execução de código em um contêiner Darwin cada vez mais isolado. Desde que passamos a usar o Mach-O para os componentes internos do Darling , pudemos usar o dyld original da Apple, além de criar muitos outros componentes de código aberto do Darwin. Ainda precisamos de um simples carregador de inicialização Mach-O para inicializar o próprio dyld.


O Mach-O, junto com o próprio Mach, é provavelmente a marca mais notável de Darwin, e as várias bibliotecas e estruturas fornecidas pela Apple fazem uso extensivo de muitos recursos obscuros do formato Mach-O. Isso torna o trabalho com o Mach-O uma das tarefas mais importantes e notáveis ​​no desenvolvimento do Darling. Desde a implementação dos carregadores Mach-O nativos até a montagem dos componentes de Darwin (primeiro na forma de arquivos ELF especiais e agora na forma de Mach-O real) - precisamos entender a estrutura interna do Mach-O em um nível muito mais profundo do que o habitualmente exigido pelos usuários comuns. desenvolvedores para a plataforma Darwin.


Terminamos esta introdução e passamos a discutir alguns dos truques que o formato Mach-O pode nos oferecer.


Nomes de instalação


No Windows e Linux, as bibliotecas dinâmicas são referenciadas por seus nomes (por exemplo, libc.so ), após o qual a tarefa do vinculador dinâmico é encontrar uma biblioteca com um nome correspondente em um dos diretórios de biblioteca padrão. O Darwin usa imediatamente o caminho (quase) completo para o arquivo da biblioteca, que é chamado o nome da instalação para esta biblioteca. Presumivelmente, isso foi feito para impedir a substituição do dylib [seqüestro de dylib] - um ataque no qual um dylib falso é colocado em um diretório no qual ele será encontrado por um vinculador dinâmico anterior ao real, que permite que o dylib execute código arbitrário em nome do programa, o que convenceu isso download do dylib.


Não apenas executáveis ​​e bibliotecas armazenam os nomes completos de instalação de suas dependências, mas as próprias dependências do Mach-O "conhecem" seus próprios nomes de instalação. Na verdade, é assim que o vinculador aprende quais nomes de instalação usar para dependências: ele os lê das próprias dependências.


Ao vincular o dylib, você especifica seu nome de instalação usando as opções ld -install_name ou -dylib_install_name :


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

Agora, quando você vincular outro arquivo Mach-O (digamos libbar.dylib ) ao libfoo.dylib , o ld gravará o nome da instalação do libfoo - /usr/local/lib/libfoo.dylib - na libbar dependências libbar , e é aí que dyld está procurará libfoo em tempo de execução.


O uso do caminho completo funciona bem o suficiente para bibliotecas do sistema, que, de fato, são instaladas em locais previamente conhecidos no sistema de arquivos; mas com bibliotecas que fazem parte dos pacotes de aplicativos, há um problema. Embora cada aplicativo possa assumir que ele será instalado em /Applications/AppName.app , em geral, isso significa que os assemblies de aplicativos são portáteis e podem ser movidos livremente pelo sistema de arquivos, portanto, o caminho específico da biblioteca dentro desses assemblies não pode ser conhecido com antecedência.


Como solução para esse problema, o Darwin permite que os nomes de instalação iniciem com @executable_path , @loader_path ou @rpath - ou seja, não é absoluto, mas indica o caminho da biblioteca relativo ao caminho para o arquivo executável principal, o caminho para o "carregamento" (arquivo ou biblioteca executável, que depende diretamente dessa biblioteca) ou em relação à lista de caminhos definidos pelo arquivo executável principal, respectivamente. @executable_path e @loader_path funcionam sem complexidade adicional, mas se pelo menos uma de suas dependências (ou suas dependências transitivas) tiver um nome de instalação usando @rpath , você deverá definir explicitamente @rpath ao vincular seu arquivo executável usando a opção ld -rpath vezes quanto você precisa:


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

(O conceito rpath, em certa medida, destrói a idéia original de caminhos de biblioteca conhecidos e abre a possibilidade de ataques de falsificação de dylib. Podemos supor que isso torne tudo inútil relacionado a nomes de instalação.)


Dependências cíclicas


Quando o código-fonte de um projeto ocupa vários arquivos, é completamente normal se esses arquivos dependem mutuamente. Isso funciona bem desde que todos esses arquivos sejam compilados em um único binário - um arquivo executável ou uma biblioteca. O que não funciona é quando várias bibliotecas dinâmicas dependem uma da outra.


Você pode argumentar que, em vez de usar dependências cíclicas entre bibliotecas dinâmicas, vale a pena redesenhar a arquitetura do projeto, e eu concordo com você. Mas se há algo típico da Apple, é que eles nunca param para pensar sobre as coisas e fazem as coisas direito; em vez disso, eles colocam muletas e truques uns sobre os outros. Nesse caso, para o Darling, precisamos fazer as dependências circulares funcionarem, porque as várias libSystem libsystem_dyld , como libsystem_dyld , libsystem_kernel e libsystem_pthread , são todas dependentes uma da outra. (Até recentemente, também tínhamos de vincular ciclicamente estruturas Cocoa como AppKit, Core Graphics e Core OpenGL, devido à maneira como o Core OpenGL foi implementado no The Cocotron, mas redesenhamos a arquitetura de nossa implementação do Core OpenGL e conseguimos nos livrar desse cíclico dependências.)


Em princípio, as dependências circulares devem funcionar bem: um vinculador dinâmico já sabe carregar cada biblioteca apenas uma vez, para não ter problemas com recursão infinita. O problema é que essas bibliotecas não podem simplesmente ser vinculadas , pois cada chamada do vinculador cria apenas uma biblioteca e, ao vincular binários, todas as suas dependências já vinculadas devem ser transferidas para o vinculador. Primeiro devemos vincular uma de nossas bibliotecas e, neste momento, as outras ainda não estão prontas, para que não possamos transferi-las para o vinculador.


O truque aqui é vincular algumas (ou por simplicidade, todas) essas bibliotecas duas vezes . Pela primeira vez, diga ao vinculador que ignore as dependências ausentes e, na verdade, não passe as dependências:


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

(Veja abaixo para -flat_namespace .)


Obviamente, se você tentar usar diretamente os dylibs resultantes, obterá erros de link dinâmico em tempo de execução. Em vez disso, vincule essas bibliotecas uma segunda vez, passando os dylibs resultantes como dependências:


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

Desta vez, o vinculador vê todos os caracteres, portanto, não pedimos que ele ignore os erros (e se alguns caracteres estiverem realmente ausentes, você receberá um erro).


Apesar do fato de que algumas, se não todas, as bibliotecas vinculadas às cópias "erradas" de suas dependências, o dyld verá as versões corretas no tempo de execução. Para que isso funcione, verifique se as duas cópias de cada biblioteca têm o mesmo nome de instalação.


Outro detalhe aqui é a ordem de inicialização. Qualquer código pode declarar funções do inicializador usando o comando mágico do compilador __attribute__((constructor)) (a lista desses inicializadores se enquadra na seção __mod_init_func no arquivo Mach-O). Essas funções são chamadas por dyld ao carregar o binário em que estão localizadas antes de chamar main() . Normalmente, os inicializadores de cada biblioteca são executados após os inicializadores de suas dependências, para que cada inicializador possa esperar que as bibliotecas de dependências já estejam inicializadas e prontas para trabalhar. Obviamente, isso não pode ser garantido para dependências cíclicas; O dyld executará seus inicializadores em alguma ordem. Você pode marcar dependências como dependências ascendentes para personalizar esse pedido; O dyld inicializará as bibliotecas que alguém marcou como sua dependência, por último. Portanto, para fazer o libfoo inicializar após o libbar , vincule-os assim:


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

Para tornar tudo mais conveniente, temos uma função Darling CMake chamada add_circular , que cuida de todas as dificuldades e permite que você a use dessa maneira de maneira simples e 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 ) 

Namespace de caracteres de dois níveis


As tabelas de símbolos no Mach-O não apenas armazenam nomes de símbolos, mas também “lembram” de qual biblioteca (ou arquivo executável) qual símbolo é utilizado. Em outras palavras, os nomes de símbolos existem nos espaços de nomes definidos pelos quais os binários os definem; portanto, o "espaço para nome de dois níveis" (outro nível significa os nomes dos símbolos).


Um espaço para nome de dois níveis foi introduzido para evitar conflitos de nome de símbolo. Geralmente, se várias bibliotecas definem caracteres com o mesmo nome, você receberá um erro durante a vinculação; mas isso pode não funcionar se você carregar bibliotecas no tempo de execução (por exemplo, plug-ins) ou quando as versões das bibliotecas no tempo e no tempo do link diferem. Para bibliotecas que usam um espaço para nome de dois níveis, isso não é um problema - ele permite que várias bibliotecas definam caracteres com o mesmo nome sem criar conflitos.


Um espaço para nome de dois níveis pode ser desativado revertendo para o uso de um "espaço para nome simples" (uma das razões para isso é que o uso de um espaço para nome em dois níveis implica que cada caractere deve ser permitido durante a vinculação, portanto, é necessário um espaço para nome simples para -undefined_suppress , como vimos acima). O Ld possui dois sinalizadores que permitem desativar o namespace de dois níveis durante a vinculação: -flat_namespace , que afeta apenas um arquivo Mach-O, e -force_flat_namespace , que funciona apenas com arquivos executáveis, não bibliotecas, e força todo o processo a usar um flat namespace. Além disso, você pode forçar o dyld a usar um espaço para nome simples no tempo de execução, configurando a variável de ambiente DYLD_FORCE_FLAT_NAMESPACE .


Um recurso do uso de um namespace de dois níveis é que você sempre deve vincular explicitamente cada Mach-O a todas as bibliotecas e estruturas das quais depende. Por exemplo, se você vincular ao AppKit, não poderá usar apenas o Foundation; você tem que ligar explicitamente para ela. Outro recurso é que, como autor de uma biblioteca ou estrutura, você não pode mover livremente a implementação do símbolo "para baixo" ao longo da cadeia de dependência, como você costuma se acostumar (por exemplo, você não pode simplesmente mover o código do AppKit para o Foundation). Para permitir isso, Mach-O, ld e dyld têm vários recursos adicionais, como sub-bibliotecas , reexportação de caracteres e meta-caracteres .


Sub-bibliotecas


Sub-bibliotecas - um mecanismo que permite que uma biblioteca (chamada biblioteca de fachada [biblioteca guarda-chuva] ou biblioteca guarda-chuva [biblioteca guarda-chuva]) delegue a implementação de parte de sua funcionalidade a outra biblioteca (denominada sub-biblioteca [sub-biblioteca]); ou, se você olhar do outro lado, permitindo que a biblioteca reexporte publicamente os símbolos fornecidos por outra biblioteca.


O principal local onde isso é usado é, novamente, libSystem com suas sub-bibliotecas que estão em /usr/lib/system ; mas pode ser usado com qualquer par de bibliotecas:


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

A única coisa que afeta isso em comparação à vinculação apenas a essa biblioteca é que o comando LC_REEXPORT_DYLIB é gravado no arquivo resultante em vez do usual LC_LOAD_DYLIB (incluindo os símbolos da sub-biblioteca durante a vinculação não são copiados para a biblioteca guarda-chuva, portanto, ele nem precisa link se novos caracteres forem adicionados à sub-biblioteca posteriormente). No tempo de execução, LC_REEXPORT_DYLIB também funciona de maneira semelhante a LC_LOAD_DYLIB : dyld carregará a sub-biblioteca e disponibilizará seus caracteres para o restante (mas, diferentemente de LC_LOAD_DYLIB , em termos de espaço para nome de dois níveis, os caracteres virão da biblioteca guarda-chuva).


O que realmente difere de LC_REEXPORT_DYLIB é o que o ld faz quando você vincula outra biblioteca ao libfoo : em vez de apenas procurar os símbolos em todos os objetos e arquivos dylib que foram passados ​​para ele, o ld também abrirá e exibirá a sub-biblioteca reexportada (neste exemplo libobjc ).


Como ele sabe onde procurá-la? A única coisa salva no libfoo.dylib é o nome da instalação libobjc.dylib , e é aí que o ld espera encontrá-lo. Isso significa que a biblioteca deve ser instalada em seu lugar antes de poder ser usada como uma sub-biblioteca para qualquer outra coisa; isso funciona bem para bibliotecas do sistema como libobjc , mas pode ser muito inconveniente ou completamente impossível se você estiver tentando reexportar sua própria sub-biblioteca.


Para resolver esse problema, o ld fornece a opção -dylib_file , que permite especificar um caminho dylib diferente para usar durante a vinculação:


 $ 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 

Embora o libSystem e algumas outras bibliotecas do sistema reexportem suas sub-bibliotecas, você não precisa usar -dylib_file ao vincular cada um dos arquivos executáveis ​​no macOS; isso ocorre porque as bibliotecas do sistema já estão instaladas de acordo com o nome da instalação. Porém, ao criar o Darling no Linux, temos que passar algumas opções -dylib_file (e outros argumentos comuns) para cada chamada ld. Fazemos isso com uma função especial aplicada automaticamente ao usar add_darling_library , add_darling_executable e outros.


Reexportação de caracteres


Às vezes, uma biblioteca pode precisar reexportar alguns dos caracteres - mas não todos de uma vez - de outra biblioteca. Por exemplo, o Core Foundation está NSObject , que nas versões recentes é implementado no tempo de execução do Objective-C, para compatibilidade.


(Se você está interessado em saber por que o NSObject já esteve na Core Foundation em vez da Foundation, é porque, como a conversão gratuita [ponte gratuita, a capacidade de converter diretamente entre os tipos Core Foundation e Foundation correspondentes sem conversão adicional], requer que classes de wrapper privadas sobre tipos do Core Foundation (por exemplo, __NSCFString ) sejam implementadas no Core Foundation e, como objetos Objective-C, elas devem ser herdadas do NSObject . Provavelmente, tudo isso pode ser realizado por para outro, deixando o NSObject com todos os seus herdeiros na Fundação e no ciclo esky vinculando a Core Foundation e a Foundation, mas a Apple decidiu migrar essas classes privadas auxiliares junto com o NSObject para a Core Foundation e, em Darling, fazemos o mesmo para manter a compatibilidade.)


Você pode passar uma lista de caracteres a serem -reexported_symbols_list usando a opção -reexported_symbols_list :


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

Embora a reexportação de alguns símbolos pareça muito semelhante à reexportação de todos os símbolos, o mecanismo pelo qual isso é implementado é muito diferente de como as sub-bibliotecas funcionam. Nenhum comando especial LC_*_DYLIB é usado; em vez disso, um símbolo indireto especial (indicado pelo sinalizador N_INDIR ) é inserido na tabela de nomes e se comporta como o símbolo definido nesta biblioteca. Se a própria biblioteca usar esse símbolo, a segunda cópia "indefinida" do símbolo aparecerá na tabela de nomes (como acontece sem nenhuma reexportação).


Uma coisa importante a ter em mente ao usar a reexportação explícita de caracteres é que você provavelmente precisará reexportar caracteres com nomes diferentes para diferentes arquiteturas. De fato, a convenção de nomenclatura [name mangling convention] para Objective-C e a interface binária Objective-C [ABI] para i386 e x86-64 são diferentes; portanto, no i386, você só precisa .objc_class_name_NSObject e x86-64 - _OBJC_CLASS_$_NSObject , _OBJC_IVAR_$_NSObject.isa e _OBJC_METACLASS_$_NSObject . Você não precisa pensar nisso ao usar sub-bibliotecas, pois todos os símbolos disponíveis automaticamente para cada arquitetura são reexportados automaticamente.


A maioria das ferramentas para trabalhar com o Mach-O lida de forma transparente com binários "grossos" ou universais (arquivos Mach-O contendo vários sub-Mach-O para várias arquiteturas). O Clang pode compilar binários universais com todas as arquiteturas solicitadas, o dyld seleciona qual arquitetura carregar do dylib, observando quais arquiteturas os suportes executáveis ​​e ferramentas como ld, otool e nm funcionam com a arquitetura correspondente à arquitetura do computador (ou seja, x86-64), a menos que você precise explicitamente de uma arquitetura diferente com um sinalizador 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/pt417507/


All Articles