Cross-compilation: toolchains e sysroots
1. Fundamentos da Cross-compilation
Cross-compilation é o processo de gerar código executável em uma plataforma (host) para ser executado em uma plataforma diferente (target). Diferentemente da compilação nativa, onde o código é compilado e executado no mesmo sistema, a compilação cruzada envolve arquiteturas distintas — como compilar para ARM a partir de um sistema x86_64.
A motivação principal reside na impossibilidade ou inconveniência de compilar diretamente no dispositivo alvo. Sistemas embarcados frequentemente possuem recursos limitados de CPU, memória e armazenamento, tornando inviável executar toolchains completas. Além disso, cenários como distribuição multiplataforma (Linux para ARM, RISC-V, MIPS) e manutenção de sistemas legados exigem a capacidade de gerar binários para múltiplas arquiteturas a partir de um único ambiente de desenvolvimento.
As diferenças fundamentais entre compilação nativa e cruzada incluem:
- Toolchain distinta: compilador, linker e assembler específicos para o target
- Sysroot separado: cabeçalhos e bibliotecas do sistema alvo
- Configuração explícita: flags de compilação e linkagem devem ser fornecidas manualmente
2. Anatomia de uma Toolchain
Uma toolchain de cross-compilation é composta por três componentes essenciais:
- Compilador (arm-linux-gnueabi-gcc, aarch64-linux-gnu-gcc): traduz código C para assembly da arquitetura alvo
- Assembler (arm-linux-gnueabi-as): converte assembly para código objeto
- Linker (arm-linux-gnueabi-ld): combina objetos em executáveis ou bibliotecas
Os prefixos de target seguem a convenção arch-vendor-os-abi:
arm-linux-gnueabi- # ARM 32 bits, Linux, EABI com glibc
aarch64-linux-gnu- # ARM 64 bits, Linux, glibc
riscv64-unknown-elf- # RISC-V 64 bits, bare-metal (sem SO)
mipsel-linux-musl- # MIPS little-endian, Linux, musl libc
Variantes importantes de toolchain incluem:
- Bare-metal vs. Linux: bare-metal não possui sistema operacional subjacente
- glibc vs. musl vs. uclibc: diferentes implementações da biblioteca C, com trade-offs entre compatibilidade e tamanho
3. Configuração e Uso de Toolchains
A instalação de toolchains pré-compiladas simplifica o processo. Provedores como Linaro e Bootlin oferecem toolchains prontas para ARM e AArch64. Ferramentas como crosstool-NG permitem construir toolchains customizadas.
Exemplo de instalação e uso com toolchain Linaro:
# Baixar e extrair toolchain ARM
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
tar xf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
export PATH=$PWD/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin:$PATH
# Compilar um programa simples
arm-linux-gnueabihf-gcc -o hello_arm hello.c
Para compilação manual com flags do GCC:
# Compilação cruzada explícita
arm-linux-gnueabi-gcc -march=armv7-a -mfloat-abi=hard -mfpu=neon \
--sysroot=/path/to/sysroot -o programa programa.c
Wrappers e scripts de ambiente são comuns em projetos maiores:
#!/bin/bash
export CC=arm-linux-gnueabi-gcc
export CXX=arm-linux-gnueabi-g++
export AR=arm-linux-gnueabi-ar
export LD=arm-linux-gnueabi-ld
export RANLIB=arm-linux-gnueabi-ranlib
4. Sysroots: O Sistema de Arquivos Alvo
O sysroot é uma réplica do sistema de arquivos do target, contendo cabeçalhos (/usr/include), bibliotecas compartilhadas (/usr/lib, /lib) e arquivos de configuração necessários para compilação. Sem ele, o compilador não teria acesso às definições e bibliotecas do sistema alvo.
Estrutura típica de um sysroot para Linux ARM:
sysroot/
├── usr/
│ ├── include/
│ │ ├── stdio.h
│ │ ├── stdlib.h
│ │ └── ...
│ └── lib/
│ ├── libc.so -> libc-2.31.so
│ ├── libc-2.31.so
│ ├── libm.so
│ └── ...
└── lib/
├── ld-linux-armhf.so.3
└── ...
Geração de sysroot com debootstrap e qemu-debootstrap:
# Criar sysroot ARM via debootstrap com QEMU
sudo qemu-debootstrap --arch=armhf --variant=buildd \
--include=libc6-dev bullseye /path/to/sysroot \
http://deb.debian.org/debian
Alternativamente, usando rsync a partir de um dispositivo real:
# Copiar sysroot de dispositivo alvo
rsync -avz root@192.168.1.100:/usr/ /path/to/sysroot/usr/
rsync -avz root@192.168.1.100:/lib/ /path/to/sysroot/lib/
rsync -avz root@192.168.1.100:/etc/ /path/to/sysroot/etc/
5. Cross-compilação com Autotools e CMake
Autotools
O sistema Autotools oferece suporte nativo a cross-compilation através de variáveis:
# Configurar para cross-compilation ARM
./configure --host=arm-linux-gnueabihf \
--build=x86_64-linux-gnu \
--with-sysroot=/path/to/sysroot \
PKG_CONFIG_SYSROOT_DIR=/path/to/sysroot
make
A variável --host especifica a plataforma onde o binário será executado, enquanto --build indica onde está sendo compilado.
CMake
CMake utiliza toolchain files para configurar cross-compilation:
# toolchain_arm.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_FIND_ROOT_PATH /path/to/sysroot)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Uso:
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain_arm.cmake /path/to/source
make
Meson
Meson utiliza cross files:
# arm_cross.txt
[binaries]
c = 'arm-linux-gnueabihf-gcc'
cpp = 'arm-linux-gnueabihf-g++'
ar = 'arm-linux-gnueabihf-ar'
strip = 'arm-linux-gnueabihf-strip'
pkgconfig = 'arm-linux-gnueabihf-pkg-config'
[host_machine]
system = 'linux'
cpu_family = 'arm'
cpu = 'armv7hl'
endian = 'little'
[properties]
sys_root = '/path/to/sysroot'
pkg_config_libdir = '/path/to/sysroot/usr/lib/pkgconfig'
6. Resolução de Dependências e Bibliotecas
O pkg-config deve ser configurado para usar o sysroot:
export PKG_CONFIG_LIBDIR=/path/to/sysroot/usr/lib/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=/path/to/sysroot
Ao compilar bibliotecas dependentes, a ordem de flags é crucial:
arm-linux-gnueabihf-gcc -o programa programa.c \
-I/path/to/sysroot/usr/include \
-L/path/to/sysroot/usr/lib \
-lz -lpng \
--sysroot=/path/to/sysroot
Bibliotecas estáticas (.a) e dinâmicas (.so) no sysroot:
# Verificar bibliotecas disponíveis no sysroot
ls /path/to/sysroot/usr/lib/*.a
ls /path/to/sysroot/usr/lib/*.so
# Forçar linkagem estática
arm-linux-gnueabihf-gcc -static -o programa programa.c
7. Depuração e Testes no Alvo
Depuração remota com GDB:
# No target (executar gdbserver)
gdbserver :2345 ./programa
# No host (conectar)
aarch64-linux-gnu-gdb programa
(gdb) target remote 192.168.1.100:2345
(gdb) continue
Execução de binários cruzados com QEMU:
# Executar binário ARM em x86_64
qemu-arm -L /path/to/sysroot ./programa
# Para AArch64
qemu-aarch64 -L /path/to/sysroot ./programa
Verificação de linked libraries:
# Listar bibliotecas dinâmicas necessárias
arm-linux-gnueabihf-readelf -d programa | grep NEEDED
# Verificar linked libraries (usando sysroot)
arm-linux-gnueabihf-ldd --root /path/to/sysroot programa
8. Boas Práticas e Armadilhas Comuns
Cuidados com endianness: arquiteturas como ARM e MIPS podem operar em little-endian ou big-endian. Verifique com:
echo | arm-linux-gnueabihf-gcc -dM -E - | grep __BYTE_ORDER__
ABI e variantes float: hard-float vs soft-float afetam a passagem de argumentos em ponto flutuante:
# Compilação hard-float (armhf)
arm-linux-gnueabihf-gcc -mfloat-abi=hard -mfpu=vfpv3-d16
# Compilação soft-float (armel)
arm-linux-gnueabi-gcc -mfloat-abi=soft
Reprodutibilidade: versionamento de toolchain e sysroot é crítico. Containers (Docker) oferecem ambientes reproduzíveis:
# Dockerfile para cross-compilation
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y \
gcc-arm-linux-gnueabihf \
libc6-dev-armhf-cross
Tamanho de tipos: sizeof(int) e sizeof(long) variam entre arquiteturas. Use tipos de tamanho fixo:
#include <stdint.h>
uint32_t valor; // Garantido 32 bits em qualquer arquitetura
Referências
- GCC Cross-Compiler Documentation — Documentação oficial do GCC sobre compilação cruzada, flags e configuração de toolchains
- crosstool-NG Project — Ferramenta para construção de toolchains customizadas, com suporte a múltiplas arquiteturas e variantes de libc
- Bootlin Toolchains — Repositório de toolchains pré-compiladas para ARM, AArch64, RISC-V e outras arquiteturas
- CMake Cross-Compilation Guide — Guia oficial do CMake para configuração de cross-compilation via toolchain files
- QEMU User Mode Emulation — Documentação do QEMU para execução de binários de arquiteturas diferentes no host
- Autotools Cross-Compilation Tutorial — Manual do Autoconf sobre variáveis de host e build para cross-compilation
- Debian Cross-Compilation Guide — Guia da comunidade Debian para cross-compilation, incluindo uso de debootstrap e sysroots