Site icon Embarcados – Sua fonte de informações sobre Sistemas Embarcados

Introdução a cross-compiling com a Raspberry Pi

cross compiling x86 64bits raspi

Devido à contínua evolução tecnológica e acessibilidade a hardware com cada vez mais “poder de fogo”, os sistemas embarcados estão cada vez mais contando com sistemas operacionais completos. Destes, o Linux embarcado (e suas mais diversas distribuições) se destaca em popularidade e aceitação. Um dos grandes motivos para isso é a capacidade de rodar muito bem em hardwares baratos, com poucos recursos computacionais, o que implica em redução de custo de projeto final.

Dado este cenário, há uma situação delicada: devido à restrição de recursos computacionais, o desenvolvimento (principalmente a compilação) de projetos grandes nos próprios sistemas embarcados é inviável, ou até mesmo impossível. Neste caso, como é feito o desenvolvimento e compilação de projetos para estes sistemas? É exatamente isso que este artigo irá mostrar.

Primeiro, algumas definições

Antes de prosseguir com o artigo, é necessário deixar claro as seguintes definições:

Compilação de um projeto – modalidades

A compilação de qualquer código-fonte / projeto pode ser dividida em duas modalidades:

  1. Compilação nativa: é a modalidade de compilação onde o host é da mesma arquitetura (utiliza mesmo processador ou linha de processadores) do(s) target(s);
  2. Compilação cruzada (ou cross-compiling): nesta modalidade, o host não é da mesma arquitetura do(s) target(s).

Na modalidade de compilação nativa, intuitivamente nota-se que não há segredos. Como as arquiteturas do host e do(s) target(s) são iguais (ou, ao menos, compatíveis), o executável do projeto será gerado com código de máquina perfeitamente executável por qualquer uma das máquinas-alvo. No mundo “não embarcado” (não se tratando de sistemas embarcados) esta modalidade é, de longe, a mais comum. Um grande exemplo disso é o desenvolvimento de jogos para computador, os quais normalmente são compilados em computadores da mesma arquitetura. Já na modalidade de compilação cruzada, as arquiteturas do host e target(s) são distintas. Portanto, se for feita uma simples compilação de um projeto no host, o executável do projeto não funcionará no target.

E agora, como proceder para compilar algo para um target de arquitetura diferente do host?

Para isso, é adotada a técnica de cross-compiling (em português, compilação cruzada). Nesta técnica, no host é utilizado um conjunto de ferramentas especial para compilação chamado toolchain. Dessa forma, o host será capaz de gerar um executável do projeto compatível com a arquitetura do(s) target(s).

Pareceu complicado? Veja a seguinte analogia: imagine que você fala somente português e precisa se comunicar com uma pessoa que fala somente inglês. Naturalmente, se você falar somente em português, a outra pessoa não irá compreender uma só palavra. Mas, se você utilizar um dicionário português-inglês e traduzir suas frases para o inglês, a outra pessoa será capaz de entender o que você quis dizer, mesmo o inglês não sendo sua língua nativa.

Analisando com calma, constatamos duas coisas nesta situação:

Portanto, em poucas palavras, um toolchain funciona como um dicionário. Este dicionário é capaz de “traduzir” a compilação de um host de forma que seja gerado um executável do projeto compatível com a arquitetura do(s) target(s). Em termos simples, o que foi explicado aqui é o processo de cross-compiling.

Exemplo prático de cross-compiling

Até agora foi dada uma noção de cross-compiling, sobretudo do que ele se trata e porque ele é necessário. Agora, será visto na prática um exemplo de cross-compiling, utilizando um computador (host, de arquitetura x86-64 bits) para compilar um programa simples em C para uma Raspberry Pi (target, de arquitetura ARM), modelo 3B ou Zero W. Segue abaixo mais informações do ambiente utilizado:

Primeiro passo: código-fonte

Primeiramente, precisamos de algo a ser compilado. Para isso, vamos utilizar um código-fonte muito simples: o bom e velho Hello World.

Segue abaixo o código-fonte. Salve-o como helloworld.c.

Segundo passo: obtenção do toolchain para cross-compilar para Raspberry Pi

Um toolchain pode ser obtido de duas maneiras:

  1. Fazer seu próprio toolchain (algo que pode ser realmente complicado dependendo do target, além de ser algo fora do escopo deste artigo);
  2. Utilizar um toolchain pronto (opção disponível na grande maioria das vezes, seja em repositórios-padrão ou site/SDK das plataformas de desenvolvimento).

Neste caso, vamos seguir na opção número 2. No caso da Raspberry Pi, os toolchains necessários estão disponíveis gratuitamente no repositório GitHub deste link. Portanto, primeiramente é preciso clonar o repositório. Para termos um norte neste artigo (padronizarmos os paths, sem criar confusões e facilitar seu entendimento), na sequência de comandos dada, será criado dentro do seu diretório home o diretório RaspPiCrossCompiling, e nele será clonado o repositório. Portanto, a sequência de comandos para isso fica assim:

O repositório é grande (pois contém muita coisa além do toolchain que usaremos), portanto o processo de clone do mesmo pode demorar alguns minutos.

Feito o clone do repositório, o próximo passo será adicionarmos à variável de ambiente PATH o caminho para o toolchain que usaremos. Isso fará com que a cada chamada do toolchain, o Linux procure na pasta em que ele está de fato (sem isso, teríamos que referenciar o path todo até o toolchain a cada processo de cross-compiling, o que deixaria os comandos de compilação desnecessariamente longos). Para isso, execute o comando abaixo:

Está feito! O toolchain agora está instalado. Para termos certeza que ele está sendo corretamente chamado, utilize o comando abaixo para verificar sua versão:

Este comando retorna informações gerais do toolchain, como pode ser visto abaixo:

Se este comando não retornar nenhum erro, significa que o toolchain está pronto para utilização. 

Terceiro passo: cross-compiling e análise do resultado do processo

É chegada a hora de cross-compilar o código-fonte. Para isso, utilize o seguinte comando:

Observe que a sintaxe é a mesma do GCC “padrão”. A compilação deve acontecer sem problemas.

Agora vem a questão: como verificar que a compilação foi feita realmente para ARM? Felizmente, há uma maneira bem fácil de verificar isso, com um comando nativo do Linux, o comando file. Este comando tem como função informar o tipo de qualquer arquivo e, no caso de executáveis, informa inclusive a arquitetura para o qual foi compilado (ou seja, arquitetura do target).

A resposta a esse comando é a seguinte:

Logo, está claro que o executável está compilado para ARM. Para ter ainda mais certeza, tente rodá-lo na sua máquina (o host do cross-compiling, de arquitetura diferente do target). A execução falhará (erro mostrado abaixo), reforçando ainda mais que o processo de cross-compiling foi um sucesso.

Quarto passo: teste, na Raspberry Pi, do programa cross-compilado

Agora, basta passar o executável (código compilado) para a Raspberry Pi (via SSH, SFTP, enfim, da forma que desejar) e executar os comandos abaixo, os quais dão permissão de execução e executam o programa:

A execução ocorrerá sem problemas, produzindo a seguinte saída:

Figura 1 – Saída do programa cross-compilado, rodando na Raspberry Pi (target). Aqui, o acesso à Raspberry Pi foi feito via SSH

Portanto, o processo de cross-compiling foi feito com sucesso!

Conclusão

Neste artigo, foram abordados os conceitos básicos de cross-compiling, algo muito comum (e extremamente necessário) no desenvolvimento de sistemas embarcados em geral. Como exemplo prático, foi visto como se faz a obtenção e instalação de um toolchain para a Raspberry Pi (aplicável aos modelos 3B e Zero W), além do processo de cross-compiling em si.

Saiba mais sobre cross-compiling

GNU Cross-toolchain – Processo de build

Série “GNU ARM Cross-toolchain”

Referências