Meu objetivo é ser capaz de desenvolver para Linux embarcado. Tenho experiência em sistemas embarcados usando o ARM.
Tenho algumas perguntas gerais sobre o desenvolvimento para diferentes alvos da CPU. Minhas perguntas são as seguintes:
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 '?
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'?
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 ' ?
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'?
Respostas:
Não. Os binários devem ser (re) compilados para a arquitetura de destino, e o Linux não oferece nada como binários gordos prontos para uso. O motivo é que o código é compilado no código da máquina para uma arquitetura específica e o código da máquina é muito diferente entre a maioria das famílias de processadores (por exemplo, o ARM e o x86 são muito diferentes).
EDIT: vale a pena notar que algumas arquiteturas oferecem níveis de compatibilidade com versões anteriores (e ainda mais rara, compatibilidade com outras arquiteturas); em CPUs de 64 bits, é comum ter compatibilidade com versões anteriores de edições de 32 bits (mas lembre-se: suas bibliotecas dependentes também devem ser de 32 bits, incluindo sua biblioteca padrão C, a menos que você esteja vinculado estaticamente ). Também vale mencionar o Itanium , onde foi possível executar o código x86 (somente 32 bits), embora muito devagar; a baixa velocidade de execução do código x86 era pelo menos parte do motivo de não ter tido muito sucesso no mercado.
Lembre-se de que você ainda não pode usar binários compilados com instruções mais recentes em CPUs mais antigas, mesmo em modos de compatibilidade (por exemplo, você não pode usar o AVX em um binário de 32 bits nos processadores Nehalem x86 ; a CPU simplesmente não o suporta.
Observe que os módulos do kernel devem ser compilados para a arquitetura relevante; além disso, os módulos do kernel de 32 bits não funcionarão nos kernels de 64 bits ou vice-versa.
Para obter informações sobre binários de compilação cruzada (para que você não precise ter uma cadeia de ferramentas no dispositivo ARM de destino), consulte a resposta abrangente do grochmal abaixo.
fonte
Elizabeth Myers está correta, cada arquitetura requer um binário compilado para a arquitetura em questão. Para construir binários para uma arquitetura diferente da executada pelo sistema, você precisa de
cross-compiler
.Na maioria dos casos, você precisa compilar um compilador cruzado. Eu só tenho experiência com
gcc
(mas acredito quellvm
, e outros compiladores, tenham parâmetros semelhantes). Umgcc
compilador cruzado é obtido adicionando--target
ao configure:Você precisa compilar
gcc
,glibc
ebinutils
com estes parâmetros (e fornecer os cabeçalhos do kernel do kernel na máquina de destino).Na prática, isso é consideravelmente mais complicado e erros de compilação diferentes aparecem em diferentes sistemas.
Existem vários guias sobre como compilar a cadeia de ferramentas GNU, mas eu recomendo o Linux From Scratch , que é mantido continuamente e faz um bom trabalho ao explicar o que os comandos apresentados fazem.
Outra opção é uma compilação de autoinicialização de um compilador cruzado. Graças à luta de compilar compiladores cruzados para diferentes arquiteturas em diferentes arquiteturas,
crosstool-ng
foi criado. Ele fornece uma inicialização sobre a cadeia de ferramentas necessária para criar um compilador cruzado.crosstool-ng
suporta vários trigêmeos de destino em diferentes arquiteturas, basicamente é um bootstrap em que as pessoas dedicam seu tempo para resolver os problemas que ocorrem durante a compilação de uma cadeia de ferramentas de compilador cruzado.Várias distribuições fornecem compiladores cruzados como pacotes:
arch
fornece um compilador cruzado mingw e um compilador cruzado eabi de braço fora da caixa. Além de outros compiladores cruzados no AUR.fedora
contém vários compiladores cruzados compactados .ubuntu
também fornece um compilador cruzado de braço .debian
tem um repositório inteiro de cadeias de ferramentas cruzadasEm outras palavras, verifique o que sua distribuição tem disponível em termos de compiladores cruzados. Se sua distribuição não tiver um compilador cruzado para suas necessidades, você sempre poderá compilá-la.
Referências:
Nota sobre os módulos do kernel
Se você estiver compilando seu compilador cruzado manualmente, terá tudo o que precisa para compilar os módulos do kernel. Isso ocorre porque você precisa dos cabeçalhos do kernel para compilar
glibc
.Porém, se você estiver usando um compilador cruzado fornecido por sua distribuição, precisará dos cabeçalhos do kernel que são executados na máquina de destino.
fonte
crosstool-ng
. Você pode adicionar isso à lista. Além disso, configurar e compilar um conjunto de ferramentas cruzadas GNU manualmente para qualquer arquitetura é incrivelmente envolvido e muito mais tedioso do que apenas--target
sinalizadores. Eu suspeito que seja parte do motivo pelo qual o LLVM está ganhando popularidade; Ele é arquitetado de tal maneira que você não precisa de uma reconstrução para atingir outra arquitetura - em vez disso, pode segmentar vários back-ends usando as mesmas bibliotecas de front-end e otimizador.Observe que, como último recurso (ou seja, quando você não possui o código fonte), você pode executar binários em uma arquitetura diferente usando emuladores como
qemu
,dosbox
ouexagear
. Alguns emuladores são projetados para emular sistemas diferentes do Linux (por exemplo,dosbox
projetados para executar programas do MS-DOS, e existem muitos emuladores para consoles de jogos populares). A emulação tem uma sobrecarga significativa de desempenho: os programas emulados são executados de 2 a 10 vezes mais lentos do que seus equivalentes nativos.Se você precisar executar módulos do kernel em uma CPU não nativa, terá que emular todo o sistema operacional, incluindo o kernel da mesma arquitetura. AFAIK é impossível executar código estrangeiro dentro do kernel do Linux.
fonte
Os binários não são apenas portáteis entre x86 e ARM, mas existem diferentes tipos de ARM .
O que você provavelmente encontrará na prática é o ARMv6 vs o ARMv7. Raspberry Pi 1 é ARMv6, versões posteriores são ARMv7. Portanto, é possível compilar código nos últimos que não funcionam no Pi 1.
Felizmente, um benefício do código aberto e do software livre é ter o código-fonte para que você possa reconstruí-lo em qualquer arquitetura. Embora isso possa exigir algum trabalho.
(O controle de versão do ARM é confuso, mas se houver um V antes do número, está falando sobre a arquitetura do conjunto de instruções (ISA). Se não houver, é um número de modelo como "Cortex M0" ou "ARM926EJS". Os números do modelo não têm nada a ver faça com os números ISA.)
fonte
Você sempre precisa segmentar uma plataforma. No caso mais simples, a CPU de destino executa diretamente o código compilado no binário (isso corresponde aproximadamente aos executáveis COM do MS DOS). Vamos considerar duas plataformas diferentes que acabei de inventar - Armistice e Intellio. Nos dois casos, teremos um programa simples olá mundo que gera 42 na tela. Também assumirei que você está usando uma linguagem de várias plataformas de uma maneira independente de plataforma, portanto o código-fonte é o mesmo para os dois:
No Armistice, você tem um driver de dispositivo simples que cuida da impressão de números; portanto, tudo o que você precisa fazer é enviar para uma porta. Em nossa linguagem assembly portátil, isso corresponderia a algo assim:
No entanto, ou o sistema Intellio não possui tal coisa, ele precisa passar por outras camadas:
Opa, já temos uma diferença significativa entre os dois, antes mesmo de chegarmos ao código da máquina! Isso corresponderia aproximadamente ao tipo de diferença que você tem entre Linux e MS DOS, ou um PC IBM e um X-Box (mesmo que ambos possam usar a mesma CPU).
Mas é para isso que servem os sistemas operacionais. Vamos supor que temos um HAL que garante que todas as diferentes configurações de hardware sejam tratadas da mesma maneira na camada de aplicativos - basicamente, usaremos a abordagem Intellio mesmo no Armistice, e nosso código de "montagem portátil" termina o mesmo. Isso é usado pelos sistemas modernos do tipo Unix e pelo Windows, geralmente mesmo em cenários incorporados. Bom - agora podemos ter o mesmo código de montagem verdadeiramente portátil no Armistice e Intellio. Mas e os binários?
Como assumimos, a CPU precisa executar o binário diretamente. Vejamos a primeira linha do nosso código
mov a, 10h
, no Intellio:Oh. Acontece que
mov a, constant
é tão popular que tem sua própria instrução, com seu próprio código de operação. Como o Armistice lida com isso?Hmm. Existe o opcode para
mov.reg.imm
, então precisamos de outro argumento para selecionar o registro ao qual estamos atribuindo. E a constante é sempre uma palavra de 2 bytes, na notação big endian - foi assim que o Armistice foi projetado; de fato, todas as instruções no Armistice têm 4 bytes de comprimento, sem exceções.Agora imagine executar o binário do Intellio no Armistice: a CPU começa a decodificar a instrução, encontra o opcode
20h
. No armistício, isso corresponde, digamos, àand.imm.reg
instrução. Ele tenta ler a constante da palavra de 2 bytes (que10XX
já é um problema), e depois o número do registro (outroXX
). Estamos executando a instrução errada, com os argumentos errados. E pior, a próxima instrução será falsa, porque na verdade comemos outra instrução, pensando que eram dados.O aplicativo não tem chance de funcionar e provavelmente trava ou trava quase imediatamente.
Agora, isso não significa que um executável sempre precise dizer que é executado no Intellio ou no Armistice. Você só precisa definir uma plataforma que seja independente da CPU (como
bash
no Unix), ou da CPU e do SO (como Java ou .NET e, atualmente, até mesmo com JavaScript). Nesse caso, o aplicativo pode usar um executável para todas as CPUs e sistemas operacionais diferentes, enquanto houver algum aplicativo ou serviço no sistema de destino (que tenha como alvo a CPU e / ou SO corretos diretamente) que traduza o código independente da plataforma em algo que CPU pode realmente executar. Isso pode ou não resultar em desempenho, custo ou capacidade.CPUs geralmente vêm em famílias. Por exemplo, todas as CPUs da família x86 têm um conjunto comum de instruções que são codificadas exatamente da mesma maneira, para que cada CPU x86 possa executar todos os programas x86, desde que não tente usar nenhuma extensão (por exemplo, operações de ponto flutuante ou operações vetoriais). No x86, os exemplos mais comuns hoje são Intel e AMD, é claro. A Atmel é uma empresa bem conhecida que projeta CPUs na família ARM, bastante popular para dispositivos embarcados. A Apple também possui CPUs ARM próprias, por exemplo.
Mas o ARM é totalmente incompatível com o x86 - eles têm requisitos de design muito diferentes e têm muito pouco em comum. As instruções têm códigos de operação totalmente diferentes, são decodificadas de maneira diferente, os endereços de memória são tratados de maneira diferente ... Pode ser possível criar um binário que seja executado em uma CPU x86 e em uma CPU ARM, usando algumas operações seguras para distinguir entre os dois e pular para dois conjuntos de instruções completamente diferentes, mas ainda significa que você tem instruções separadas para as duas versões, com apenas um bootstrapper que escolhe o conjunto correto em tempo de execução.
fonte
É possível relançar essa pergunta em um ambiente que possa ser mais familiar. Por analogia:
"Tenho um programa Ruby que desejo executar, mas minha plataforma possui apenas um intérprete Python. Posso usar o intérprete Python para executar meu programa Ruby ou preciso reescrever meu programa em Python?"
Uma arquitetura de conjunto de instruções ("destino") é uma linguagem - uma "linguagem de máquina" - e diferentes CPUs implementam idiomas diferentes. Portanto, pedir a uma CPU ARM para executar um binário Intel é como tentar executar um programa Ruby usando um interpretador Python.
fonte
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 ).
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:
-march=core2
)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
egcc --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.
(
/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.Não com o ELF, ele não permitirá que você faça isso.
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. )
Não, a ELF não permitirá que você faça isso.
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:Chame isso de "wozbin" e configure-o com algo como:
Isso registra os
.woz
arquivos com o kernel; owozbin
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 umtest.woz
arquivo ZIP com uma hierarquia de diretórios para: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.fonte
baga bota, resolver alguns dos seus problemas .. mas não resolver novamente o problema de como executar no braço hf, normall / regullAr linux distro para x86-32 / 64bit.
Eu acho que deve ser embutido no isolinux (boatloader linux on usb) algum conversor ao vivo que possa reconhecer distro regular e em passeio / conversão ao vivo para hf.
Por quê? Porque se cada linux puder ser convertido por berry boot para trabalhar no arm-hf, ele poderá criar um mecanismo de inicialização do bery para isolinux o que inicializamos usando para cada exemplo ou disco de inicialização do ubuntu creat.
fonte