Incorporando num executável o conteúdo de um arquivo

Tem momentos em que é melhor incorporar o conteúdo de um arquivo a um programa do que copiar o arquivo junto com o executável. Isso é legal quando o arquivo não for enorme e não tiver que ser modificado durante o uso do programa. Um bom exemplo é a imagem pra um splash screen.1

Toda plataforma tem sua maneira de fazer isso: o RC do Windows, os Bundles do Mac OS, o Resource System no Qt, GResource no Gnome… Mas o que eu quero é a forma mais simples e pura: o conteúdo do arquivo na imagem executável e um ponteiro pra acessá-lo.2

GNU binutils

É muito tranquilo fazer isso no Linux usando o comando objcopy, parte do GNU binutils.

objcopy --input binary \
        --output elf64-x86-64 \
        picture.png picture.o

Isso cria um objeto para ser ligado ao executável. No resto do código, o conteúdo do arquivo pode ser acessado usando nomes como binary_picture_png_start e binary_picture_png_size. O Mac OS não tem GNU binutils, mas a gente consegue instalar usando o Homebrew. Resolvido o problema? Claro que não. Se você tentar o comando equivalente ao do Linux, não vai funcionar.

$ objcopy --input binary \
          --output mach-o-x86-64 \
          picture.png picture.o
$ cc main.o picture.o -o app
...
ld: object file imagem.o was built for different x86_64 
sub-type (-2147483645) than link command line (3) for 
architecture x86_64

Eu adoro uma mensagem de erro com número porque a chance de encontrar alguma pista no Google é grande. Um defeito do binutils relatado em 2018 se parece um pouco com meu caso mas, quando tentei reproduzir o problema, funcionou. Será que eu encontrei um irmão do defeito original?

Não me interessa.

Me ajuda, assembler!

Em vez de perder tempo lutando contra uma ferramenta que nem faz parte da instalação padrão do meu sistema, melhor usar o assembler, que obviamente faz parte. A diretiva .incbin resolve quase tudo; fica faltando só demarcar o conteúdo com rótulos e exportá-los, tomando o cuidado de usar a convenção do formato Mach-O, prefixando os nomes com traço baixo.3

.global _picture_start
.global _picture_end

_picture_start:
.incbin "picture.png"
_picture_end:

Resolvido. Basta declarar os nomes como externos no arquivo C onde eles forem usados e tudo funciona.

extern uint8_t* picture_start;
extern uint8_t* picture_end;

int main(int argc, char** argv) 
{
  uint8_t* i = picture_start;
  while (i != picture_end) {
    ...
    i++;
  }
}
  1. É tão comum essa necessidade que o formato de imagens XPM, usado no X Window System, é código fonte C. Pra mandar a imagem pra dentro de um executável, é só dar um #include nela. 

  2. Ou seja, eu quero o jeitinho UNIX

  3. Listando os nomes indefinidos no objeto que vai consumir a imagem, vemos que eles são prefixados com traço baixo.

    $ nm -undefined-only main.o
    _picture_size
    _picture_start
    

    Vestígio da pré-história, esse traço baixo era usado pra evitar que os nomes exportados por um objeto C conflitassem com nomes iguais definidos nas bibliotecas em assembly que já existiam. Formatos mais recentes, como o ELF, de 1988, abandonaram essa prática, mas até hoje o GCC ainda tem o flag -fleading-underscore para produzir objetos compatíveis com esse ritual antigo. O formato Mach-O, de 1985 (?), é anterior a essa reforma.