Gerando e usando toolchains em Linux embarcado
- por Sergio Prado
No último artigo vimos os principais conceitos de toolchain em Linux embarcado: o que é um toolchain, os tipos de toolchain e seus componentes básicos.
Neste artigo iremos aprender a gerar e utilizar um toolchain para cross-compilar aplicações e projetos open-source para Linux embarcado.
ONDE POSSO ENCONTRAR UM TOOLCHAIN?
Você tem duas opções: usar um toolchain pronto ou gerar (compilar) seu próprio toolchain. Cada uma destas opções tem suas vantagens e desvantagens.
Usar um toolchain pronto é fácil. Normalmente vem junto com o BSP (Board Support Package) da sua plataforma de desenvolvimento. Existem também versões gratuitas disponibilizadas por empresas como a Code Sourcery (que foi adquirida pela Mentor Graphics). Mas usar um toolchain pronto engessa sua solução. Como o toolchain depende das bibliotecas do sistema, você vai precisar adaptar seu produto às bibliotecas de sistema usadas no toolchain. Se o toolchain não tem um compilador C++, você não conseguirá desenvolver aplicações nesta linguagem. Conclusão: em vez da ferramenta se adaptar às suas necessidades, você é que vai precisar se adaptar à ferramenta. O que na maioria das vezes não é muito bom, certo?
A segunda opção, gerar um toolchain, pode dar um pouco mais de trabalho, mas te traz toda a flexibilidade necessária para adaptar a ferramenta às necessidades do seu projeto. É o que faremos a seguir.
GERANDO SEU PRÓPRIO TOOLCHAIN
O processo de geração de um toolchain, como você deve imaginar, é bem complicado. Mas existem ferramentas que automatizam este processo.
Boa parte das ferramentas de Build System, como o OpenEmbedded e o Buildroot, fazem isso. Mas existem algumas ferramentas específicas para gerar toolchains. Dentre elas, a mais famosa é o crosstool-ng, que usaremos para gerar um toolchain para cross-compilar aplicações Linux para ARM.
O crosstool-ng pode ser baixado da página do projeto em http://crosstool-ng.org/. Crie uma pasta de trabalho na sua máquina, baixe e descompacte o crosstool-ng:
$ cd /opt/toolchain $ wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.13.1.tar.bz2 $ tar jxfv crosstool-ng-1.13.1.tar.bz2 $ cd crosstool-ng-1.13.1/ |
Primeiramente, precisamos configurar e compilar o crosstool-ng:
$ ./configure --local $ make && make install |
O processo acima deve ter gerado o script "ct-ng", que será usado para configurar o toolchain.
O crosstool-ng vem com algumas configurações de toolchain por padrão, que podem ser visualizadas com o comando abaixo:
$ ./ct-ng list-samples Sample name Status alphaev56-unknown-linux-gnu [L X] alphaev67-unknown-linux-gnu [L X] arm-bare_newlib_cortex_m3_nommu-eabi [L X] arm-cortex_a15-linux-gnueabi [L X] arm-cortex_a8-linux-gnueabi [L ] arm-davinci-linux-gnueabi [L ] armeb-unknown-eabi [L ] armeb-unknown-linux-gnueabi [L X] armeb-unknown-linux-uclibcgnueabi [L X] arm-iphone-linux-gnueabi [L X] ... |
A listagem acima esta exibindo apenas uma parte das configurações disponíveis por padrão no crosstool-ng. Ele é capaz de gerar toolchains para ARM, MIPS, PPC, x86 e AVR, dentre outras arquiteturas e plataformas.
Você pode se aventurar a criar uma configuração do zero, mas o mais comum é usar uma das configurações pré-definidas, e então trabalhar em cima dela. No nosso caso, usaremos o “arm-unknown-linux-uclibcgnueabi” e faremos algumas pequenas modificações.
Portanto, carregue esta configuração com o comando abaixo:
$ ./ct-ng arm-unknown-linux-uclibcgnueabi |
Vamos agora abrir o menu de configuração do crosstool-ng e fazer alguns ajustes:
$ ./ct-ng menuconfig |
Entre na opção “Paths and misc options” e configure a quantidade de threads de execução. Essa configuração ajuda a diminuir o tempo de compilação e geração do toolchain. Multiplique a quantidade de núcleos da sua CPU por dois:
(2) Number of parallel jobs
Ainda neste menu, configure o diretório de instalação do toolchain na opção "Prefix directory":
(/opt/toolchain/x-tools/${CT_TARGET}) Prefix directory
Volte à tela principal e entre na opção “Toolchain options” para configurar o "alias":
(arm-linux) Tuple's alias
Configurando "arm-linux" como alias, todas as ferramentas começarão com o prefixo "arm-linux". Por exemplo, o gcc do toolchain terá o nome "arm-linux-gcc".
Salve e saia. Agora é só iniciar a geração do toolchain com o comando abaixo:
$ ./ct-ng build |
Obs: No momento em que escrevo este artigo, o site do kernel do Linux não esta 100% no ar (os caras tiveram alguns problemas de segurança que tiraram o site do ar durante um bom tempo). Por este motivo, o crosstool-ng deu um erro ao tentar baixar o kernel do Linux (arquivo linux-2.6.33.19.tar.gz). Neste caso, vá para a página do github do kernel, baixe a versão requisitada pelo crosstool-ng, e salve-a no diretório ".build/tarballs/".
O processo de compilação pode demorar um pouco. No final, verifique o toolchain gerado no diretório abaixo:
$ ls /opt/toolchain/x-tools/arm-unknown-linux-uclibcgnueabi/bin arm-linux-addr2line arm-linux-g++ arm-linux-ldd arm-linux-size arm-unknown-linux-uclibcgnueabi-c++filt arm-unknown-linux-uclibcgnueabi-gdb arm-unknown-linux-uclibcgnueabi-ranlib arm-linux-ar arm-linux-gcc arm-linux-nm arm-linux-strings arm-unknown-linux-uclibcgnueabi-cpp arm-unknown-linux-uclibcgnueabi-gprof arm-unknown-linux-uclibcgnueabi-readelf arm-linux-as arm-linux-gcc-4.4.3 arm-linux-objcopy arm-linux-strip arm-unknown-linux-uclibcgnueabi-ct-ng.config arm-unknown-linux-uclibcgnueabi-ld arm-unknown-linux-uclibcgnueabi-run arm-linux-c++ arm-linux-gccbug arm-linux-objdump arm-unknown-linux-uclibcgnueabi-addr2line arm-unknown-linux-uclibcgnueabi-g++ arm-unknown-linux-uclibcgnueabi-ldd arm-unknown-linux-uclibcgnueabi-size arm-linux-cc arm-linux-gcov arm-linux-populate arm-unknown-linux-uclibcgnueabi-ar arm-unknown-linux-uclibcgnueabi-gcc arm-unknown-linux-uclibcgnueabi-nm arm-unknown-linux-uclibcgnueabi-strings arm-linux-c++filt arm-linux-gdb arm-linux-ranlib arm-unknown-linux-uclibcgnueabi-as arm-unknown-linux-uclibcgnueabi-gcc-4.4.3 arm-unknown-linux-uclibcgnueabi-objcopy arm-unknown-linux-uclibcgnueabi-strip arm-linux-cpp arm-linux-gprof arm-linux-readelf arm-unknown-linux-uclibcgnueabi-c++ arm-unknown-linux-uclibcgnueabi-gccbug arm-unknown-linux-uclibcgnueabi-objdump arm-linux-ct-ng.config arm-linux-ld arm-linux-run arm-unknown-linux-uclibcgnueabi-cc arm-unknown-linux-uclibcgnueabi-gcov arm-unknown-linux-uclibcgnueabi-populate |
Sempre que você for usar o toolchain, vai precisar configurar a variável de ambiente PATH:
$ export PATH=/opt/toolchain/x-tools/arm-unknown-linux-uclibcgnueabi/bin:$PATH |
Agora teste o toolchain gerado:
$ arm-linux-gcc arm-linux-gcc: no input files |
USANDO O TOOLCHAIN
Usar o toolchain para cross-compilar uma aplicação é muito fácil. Crie um programa simples em C:
1 2 3 4 5 6 7 8 |
// main.c #include "stdio.h" int main() { printf("Hello Embedded Universe!\n"); return 0; } |
Se você quisesse compilar nativamente sua aplicação, bastaria usar o gcc conforme abaixo:
$ gcc main.c -o main |
Para confirmar que a aplicação foi compilada nativamente, use o comando "file":
$ file main main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped |
Para cross-compilar, basta trocar o gcc pelo compilador do toolchain, que no nosso caso é o arm-linux-gcc:
$ arm-linux-gcc main.c -o main |
Obs: Lembre-se de que a variável PATH deve estar configurada corretamente para o bash encontrar as ferramentas do toolchain.
Use o comando “file” para confirmar que a aplicação foi compilada para ARM:
$ file main main: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped |
Fácil, não?
CROSS-COMPILANDO APLICAÇÕES MAIS COMPLEXAS
Mas Sergio, compilar um arquivo ".c" é fácil. E no caso de um projeto mais complexo, com diversos arquivos-fonte?
Um projeto deste porte é normalmente baseado em um ou mais Makefiles, ou em um conjunto de ferramentas chamadas de autotools.
Para um projeto baseado em Makefile, você pode abrir o arquivo de Makefile, encontrar a definição das variáveis que definem as ferramentas de compilação (AR, AS, LD, CC, GCC, CPP, CXX, STRIP) e substituir pelas do seu toolchain. Não se esqueça de que a variável PATH também deve estar configurada corretamente.
Para um projeto baseado em autotools, o procedimento comum para compilar nativamente a aplicação é este:
$ ./configure $ make $ make install |
Mas para cross-compilar um projeto baseado em autotools, alguns cuidados adicionais devem ser tomados, como configurar o compilador através da variável de ambiente CC, e indicar o "host" ao executar a ferramenta "configure".
Para estudar este processo, vamos cross-compilar o lighttpd, um web server bastante usado em Linux embarcado, e cujo projeto é baseado em autotools.
Baixe e descompacte o pacote:
$ cd /opt/toolchain/ $ wget http://download.lighttpd.net/lighttpd/releases-1.4.x/lighttpd-1.4.29.tar.gz $ tar zxfv lighttpd-1.4.29.tar.gz $ cd lighttpd-1.4.29/ |
Para cross-compilar usando o toolchain que geramos, basta executar os comandos abaixo:
$ export PATH=/opt/toolchain/x-tools/arm-unknown-linux-uclibcgnueabi/bin:$PATH $ export CC=arm-linux-gcc $ ./configure --host=arm-linux --without-pcre --without-zlib --without-bzip2 --disable-ipv6 $ make |
Na primeira linha configuramos a variável de ambiente PATH. Na segunda linha configuramos o compilador do toolchain através da variável de ambiente CC. Na terceira linha configuramos o software indicando qual é o host (máquina que irá executar a aplicação), e desabilitando algumas bibliotecas que não temos disponíveis. Por último, executamos o make para compilar a aplicação.
Confirme se o lighttpd foi compilado para ARM:
$ file ./src/lighttpd ./src/lighttpd: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped |
Percebam que a geração e o uso de toolchains não envolve nenhum tipo de "magia negra". Quanto menos uma ferramenta for uma "caixa preta" para você, melhor. Você será mais produtivo, e resolverá eventuais problemas muito mais rapidamente.
Um abraço,
Sergio Prado