O gcc usa os termos '' arquitetura '' para significar o '' conjunto de instruções '' de uma CPU específica, e "target" abrange a combinação de CPU e arquitetura, junto com outras variáveis, como ABI, libc, endian-ness e mais (possivelmente incluindo "bare metal"). Um compilador típico possui um conjunto limitado de combinações de destinos (provavelmente uma ABI, uma família de CPU, mas possivelmente ambas de 32 e 64 bits). Um compilador cruzado geralmente significa um compilador com um destino diferente do sistema em que ele é executado ou um com vários destinos ou ABIs (consulte também isso ).
Os binários são portáteis em diferentes arquiteturas de CPU?
Em geral, não. Um binário em termos convencionais é o código de objeto nativo para uma CPU ou família de CPU específica. Mas há vários casos em que eles podem ser moderadamente a altamente portáteis:
- uma arquitetura é um superconjunto de outra (normalmente os binários x86 têm como alvo i386 ou i686 em vez do x86 mais recente e melhor, por exemplo
-march=core2
)
- uma arquitetura fornece emulação nativa ou tradução de outra (você pode ter ouvido falar de Crusoe ) ou fornece coprocessadores compatíveis (por exemplo, PS2 )
- o sistema operacional e o tempo de execução oferecem suporte a multiarch (por exemplo, capacidade de executar binários x86 de 32 bits no x86_64) ou tornar a VM / JIT integrada (Android usando Dalvik ou ART )
- existe suporte para binários "gordos" que contêm essencialmente código duplicado para cada arquitetura suportada
Se você, de alguma maneira, conseguir resolver esse problema, o outro problema binário portátil das inúmeras versões da biblioteca (glibc eu estou olhando para você) se apresentará. (A maioria dos sistemas embarcados evita esse problema em particular.)
Se você ainda não o fez, agora é uma boa hora para correr gcc -dumpspecs
e gcc --target-help
ver o que você está enfrentando.
Binários gordos têm várias desvantagens , mas ainda têm usos potenciais ( EFI ).
Há duas considerações adicionais ausentes nas outras respostas: o ELF e o interpretador ELF e o suporte do kernel Linux para formatos binários arbitrários . Não entrarei em detalhes sobre binários ou bytecode para processadores não reais aqui, embora seja possível tratá-los como "nativos" e executar binários Java ou Python bytecode compilados , esses binários são independentes da arquitetura de hardware (mas dependem na versão da VM relevante, que finalmente executa um binário nativo).
Qualquer sistema Linux contemporâneo usará binários ELF (detalhes técnicos neste PDF ); no caso de binários dinâmicos ELF, o kernel é responsável por carregar a imagem na memória, mas é o trabalho do '' intérprete '' definido no ELF cabeçalhos para fazer o trabalho pesado. Normalmente, isso envolve garantir que todas as bibliotecas dinâmicas dependentes estejam disponíveis (com a ajuda da seção '' Dinâmico '', que lista as bibliotecas e algumas outras estruturas que listam os símbolos necessários) - mas essa é quase uma camada indireta de uso geral.
$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses \
shared libs), stripped
$ readelf -p .interp /bin/ls
String dump of section '.interp':
[ 0] /lib/ld-linux.so.2
( /lib/ld-linux.so.2
também é um binário ELF, não possui um intérprete e é um código binário nativo.)
O problema com o ELF é que o cabeçalho no binário ( readelf -h /bin/ls
) o marca para uma arquitetura específica, classe (32 ou 64 bits), endian-ness e ABI (binários de gordura "universais" da Apple) usam um formato binário alternativo Mach-O em vez disso, que resolve esse problema, ele se originou no NextSTEP). Isso significa que um executável ELF deve corresponder ao sistema no qual ele deve ser executado. Uma saída de escape é o intérprete, que pode ser qualquer executável (incluindo um que extrai ou mapeia subseções específicas da arquitetura do binário original e as invoca), mas você ainda está limitado pelo (s) tipo (s) de ELF que seu sistema permitirá executar . (O FreeBSD possui uma maneira interessante de lidar com arquivos Linux ELF , brandelf
modificando o campo ELF ABI.)
Existe (usando binfmt_misc
) suporte para Mach-O no Linux , há um exemplo que mostra como criar e executar um binário gordo (32 e 64 bits).Garfos de recursos / ADS , como originalmente feito no Mac, podem ser uma solução alternativa, mas nenhum sistema de arquivos Linux nativo suporta isso.
Mais ou menos a mesma coisa se aplica aos módulos do kernel, .ko
arquivos também são ELF (embora não tenham nenhum conjunto de intérpretes). Nesse caso, há uma camada extra que usa a versão do kernel ( uname -r
) no caminho de pesquisa, algo que teoricamente poderia ser feito em ELF com versionamento, mas com alguma complexidade e pouco ganho, suspeito.
Como observado em outro lugar, o Linux não suporta nativamente binários de gordura, mas há um projeto ativo de binários de gordura: FatELF . Já existe há anos , nunca foi integrado ao kernel padrão em parte devido a preocupações de patente (agora expiradas). No momento, ele requer suporte ao kernel e ao conjunto de ferramentas. Ele não usa a binfmt_misc
abordagem, isso evita problemas no cabeçalho do ELF e também permite módulos de kernel gordo.
- Se eu tiver um aplicativo compilado para executar em um 'destino x86, versão do SO Linux xyz', posso executar o mesmo binário compilado em outro sistema 'destino ARM, versão do SO Linux xyz'?
Não com o ELF, ele não permitirá que você faça isso.
- Se acima não for verdade, a única maneira é fazer o código-fonte do aplicativo reconstruir / recompilar usando a cadeia de ferramentas relevante 'por exemplo, arm-linux-gnueabi'?
A resposta simples é sim. (As respostas complicadas incluem emulação, representações intermediárias, tradutores e JIT; exceto no caso de "rebaixar" um binário i686 para usar apenas os códigos opcionais i386, eles provavelmente não são interessantes aqui, e os consertos da ABI são potencialmente tão difíceis quanto traduzir código nativo. )
- Da mesma forma, se eu tiver um módulo do kernel carregável (driver de dispositivo) que funcione em um 'destino x86, versão do sistema operacional linux xyz', posso carregar / usar o mesmo .ko compilado em outro sistema 'destino ARM, versão do sistema operacional linux xyz' ?
Não, a ELF não permitirá que você faça isso.
- Se acima não for verdade, a única maneira é fazer o código-fonte do driver reconstruir / recompilar usando a cadeia de ferramentas relevante 'por exemplo, arm-linux-gnueabi'?
A resposta simples é sim. Acredito que o FatELF permite criar uma .ko
arquitetura que é multi-arquitetura, mas em algum momento uma versão binária para cada arquitetura suportada deve ser criada. Coisas que requerem módulos do kernel geralmente vêm com a fonte e são construídas conforme necessário, por exemplo, o VirtualBox faz isso.
Esta já é uma resposta longa, apenas um desvio. O kernel já possui uma máquina virtual embutida, embora dedicada: a VM BPF, que é usada para corresponder aos pacotes. O filtro legível por humanos "host foo e não a porta 22") é compilado em um bytecode e o filtro de pacotes do kernel o executa . O novo eBPF não é apenas para pacotes, na teoria de que o código da VM é portátil em qualquer linux contemporâneo, e o llvm o suporta, mas por razões de segurança, provavelmente não será adequado para nada além de regras administrativas.
Agora, dependendo de sua generosidade com a definição de um executável binário, você pode (ab) usar binfmt_misc
para implementar suporte binário gordo com um script de shell e arquivos ZIP como um formato de contêiner:
#!/bin/bash
name=$1
prog=${1/*\//} # basename
prog=${prog/.woz/} # remove extension
root=/mnt/tmpfs
root=$(TMPDIR= mktemp -d -p ${root} woz.XXXXXX)
shift # drop argv[0], keep other args
arch=$(uname -m) # i686
uname_s=$(uname -s) # Linux
glibc=$(getconf GNU_LIBC_VERSION) # glibc 2.17
glibc=${glibc// /-} # s/ /-/g
# test that "foo.woz" can unzip, and test "foo" is executable
unzip -tqq "$1" && {
unzip -q -o -j -d ${root} "$1" "${arch}/${uname_s}/${glibc}/*"
test -x ${root}/$prog && (
export LD_LIBRARY_PATH="${root}:${LD_LIBRARY_PATH}"
#readlink -f "${root}/${prog}" # for the curious
exec -a "${name}" "${root}/${prog}" "$@"
)
rc=$?
#rm -rf -- "${root}/${prog}" # for the brave
exit $rc
}
Chame isso de "wozbin" e configure-o com algo como:
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
printf ":%s:%s:%s:%s:%s:%s:%s" \
"woz" "E" "" "woz" "" "/path/to/wozbin" "" > /proc/sys/fs/binfmt_misc/register
Isso registra os .woz
arquivos com o kernel; o wozbin
script é chamado em vez disso, com seu primeiro argumento definido no caminho de um .woz
arquivo invocado .
Para obter um .woz
arquivo portátil (gordo) , basta criar um test.woz
arquivo ZIP com uma hierarquia de diretórios para:
i686/
\- Linux/
\- glibc-2.12/
armv6l/
\- Linux/
\- glibc-2.17/
Em cada diretório arch / OS / libc (uma escolha arbitrária), coloque o test
binário e os componentes específicos da arquitetura , como .so
arquivos. Quando você o invoca, o subdiretório necessário é extraído para um sistema de arquivos em memória tmpfs ( /mnt/tmpfs
aqui) e invocado.