Como eu compilo um kernel para diferentes arquitecturas (Cross Compilation)?

O que é Cross Compilation?

Cross Compilation, ou compilações entre e para diferentes arquitecturas, é quando um compilador produz um executável para correr num sistema com uma plataforma ou arquitectura diferente daquela presente no computador onde foi compilado. Um exemplo prático.

Exemplo de vida real

A Jane quer compilar o kernel para um dispositivo que corre embedded Linux. Vamos supor que este dispositivo é baseado em PowerPC (PPC). Ela descobre que este dispositivo já tem Linux instalado com várias aplicações. No entanto, quando a Jane tenta actualizar a versão do kernel compilando a ultima versão stable, ela repara que o dispositivo, apesar de eficaz para aquilo que foi pensado, é muito limitado em termos de recursos, e que algo intensivo em recursos com a compilação do kernel, iria demorar várias horas até estar completo.

Visto que ela tem uma máquina (baseada em AMD64) já a correr GNU/Linux e com grandes capacidades de processamento, ela decide compilar o kernel na sua estação de trabalho. Infelizmente, repara que o kernel foi compilado para a arquitectura do computador dela (AMD64), e que por isso não seria possível correr esse mesmo kernel no seu dispositivo (que é baseado em PPC).

Para resolver este problema é necessário um Cross Compiler - Algo que corra na arquitectura A mas que compile para a arquitectura B.

Eu normalmente faço-o para compilar o kernel para o meu NAS da Linksys. O NSLU2, que utiliza a arquitectura ARM.

GCC e binutils em ambiente de Cross Compilation

Para compilar o kernel, é necessário o GCC e o binutils. O GCC é um software inteligente, que é capaz de compilar software numa vasta variedade de arquitecturas, no entanto, é necessário que este seja compilado com essas instruções (para suportar CrossCompilation).

O binutils é uma suite de aplicações presentes numa meta-package chamado de binutils. Exemplo de algumas aplicações:

  1. [om@turyxsrv ~/work]$ rpm -qf /usr/bin/as
  2. binutils-2.16.91.0.6-4
  3. [om@turyxsrv ~/work]$ rpm -ql binutils-2.16.91.0.6-4
  4. ...
  5. /usr/bin/ar
  6. /usr/bin/as
  7. /usr/bin/c++filt
  8. /usr/bin/gprof
  9. /usr/bin/ld
  10. /usr/bin/nm
  11. /usr/bin/objcopy
  12. /usr/bin/objdump
  13. /usr/bin/ranlib
  14. /usr/bin/readelf
  15. /usr/bin/size
  16. ...

Caso o seu sistema seja baseado em Debian (como por exemplo o Ubuntu), o seguinte comando vai-lhe informar de quais os ficheiros pertencentes a este pacote:

dpkg -L binutils

Obtendo um cross compiler

Pré-compilados

Existem vários na Internet, includindo o ELDK (DENX's Embedded Linux Development Kit). Está disponível para várias plataformas, como o PPC, ARM, etc.

Construindo o nosso cross compiler

Crosstool é um conjunto de scripts que nos ajudam a construir um cross compiler de raíz. Estes scripts também produzem uma correspondente glibc, que nos permite compilar ferramentas de utilizador (user land tools) da mesma forma que kernel.

Compiladores 64/32 Bit

O GCC é capaz de gerar código tanto para 32 como para 64 bit (exemplo: AMD64 e IA32 ou PPC ou PPC64), usando uma opção (-m32/-m64).

Quando você faz participa no desenvolvimento do kernel, é boa prática compilar os seus patches tanto para 32 como para 64 bit. Esta forma de compilação (cross compilation) também nos pode ajudar nesta altura. Em vez de tentar alterar o makefile do kernel para produzir output de 32 bit usando um compilador de 64 bit (ou vice versa), devemos utilizar esta técnica para compilar o kernel com output tanto para 32 como para 64 bit na mesma plataforma.

Nota:(Funciona no Ubuntu 8.04.1 AMD64) Para compilar um kernel de 32 bit para x86 numa máquina x86_64, corra a aplicação i386 (que é um link para /usr/bin/setarch), e depois continue com a compilação normalmente (make config; make).

Testando

Uma vez que já temos o nosso cross compiler a funcionar, podemos testar num pequeno e simples hello.c, para garantir que a compilação funciona.

  1. [om@turyxsrv ~/prg/tmp]$ export cross=i686-unknown-linux-gnu-
  2. [om@turyxsrv ~/prg/tmp]$ echo $cross
  3. i686-unknown-linux-gnu-
  4. [om@turyxsrv ~/prg/tmp]$ ${cross}gcc mathtest.c -o math
  5. [om@turyxsrv ~/prg/tmp]$ file math
  6. math: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.4.3, dynamically linked (uses shared libs), for GNU/Linux 2.4.3, not stripped

Como funcionou, podemos passar a algo maior, como o kernel:

  1. [om@turyxsrv ~]$ cd src/i386-linux-2.6
  2. [om@turyxsrv /home/src/i386-linux-2.6]$ uname -a
  3. Linux turyxsrv 2.6.18-rc3 #3 SMP Sun Jul 30 11:54:32 PDT 2006 x86_64 x86_64 x86_64 GNU/Linux
  4. [om@turyxsrv /home/src/i386-linux-2.6]$ make CROSS_COMPILE=i686-unknown-linux-gnu- ARCH=i386 xconfig
  5. [om@turyxsrv /home/src/i386-linux-2.6]$ make CROSS_COMPILE=i686-unknown-linux-gnu- ARCH=i386 V=1 -j4 all

Utilizei V=1 para fazer com que a compilação desse mais output (mais verbose) e -j4 para utilizar os 4 CPU do computador.

Depois da compilação, o make deu esta mensagem:

Kernel: arch/i386/boot/bzImage is ready  (#1)

Se quiser ainda mais garantias de que a compilação correu com sucesso:

  1. [om@turyxsrv /home/src/i386-linux-2.6]$ file vmlinux
  2. vmlinux: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped

Compilei em AMD64 um kernel para correr numa máquina i386.

Caso queira compilar módulos para a máquina de destino (neste caso era a máquina i386), é recomendado utilizar o seguinte comando para garantir que depois pode copiar os módulos para o computador de destino:

[om@turyxsrv /home/src/i386-linux-2.6]$ make INSTALL_MOD_PATH=<WhereTheModulesShallGo> modules_install

Gerando ficheiros pré-processados

O kernel permite-nos gerar ficheiros pré-processados. É util quando suspeitamos que há algo de errado com os macros. Nos dias do 2.4, podíamos utilizar a opção -c para redireccionar o resultado do gcc preprocessor para um ficheiro. No 2.6, é algo que faz parte do kernel.

Se quisesse gerar para o kernel/dma.c:

  1. [om@kartel /space/kernel.org/linux-exp]$ make kernel/dma.i
  2. CHK include/linux/version.h
  3. CHK include/linux/utsrelease.h
  4. CALL scripts/checksyscalls.sh
  5. CPP kernel/dma.i

Abra kernel/dma.i para ver o que o preprocessor fez ao dma.c.

Isto está disponível para um módulo (não faz parte do kernel):

  1. [root@hydra1 linuxdriver]# make -C /lib/modules/2.6.18-92.el5/build/ M=$(pwd)/src hxge_main.i
  2. make: Entering directory `/usr/src/kernels/2.6.18-92.el5-x86_64'
  3. CPP [M] /root/hydra-src/linuxdriver/src/hxge_main.i
  4. make: Leaving directory `/usr/src/kernels/2.6.18-92.el5-x86_64'