Manual
do
Maker
.
com
Sistemas embarcados: Remontando um rootfs
A extração de dados de um arquivo cpio foi exemplificada nesse outro post sobre sistemas embarcados. Então, vamos iniciar esse post remontando o sistema que foi extraído.
Se o sistema extraído for baseado na mesma libc do seu sistema nativo (o sistema Linux que você deve estar utilizando para ler esse post, por exemplo), então com muita facilidade você poderá incluir programas na raiz do sistema que você extraiu do arquivo CPIO. Mas não basta copiar o arquivo binário, tem que carregar também todas as suas dependências. Para isso utiliza-se a ferramenta ldd.
Obviamente o pré-requisito básico é a já citada libc do sistema em questão. Utilizar o ldd em binários compilados com a uClibc em uma plataforma que utiliza a glibc gerará falsos positivos e/ou falsos negativos.
O ldd deve ser utilizado assim:
ldd programa
Mas esse apontamento deve ser feito diretamente sobre o binário. Para saber onde se encontra o binário, usa-se o comando wich:
which programa
Um exemplo para aplicar o ldd sobre o iptraf:
ldd $(which iptraf)
Uma lista de dependências será gerada nesse formato:
linux-vdso.so.1 => (0x00007fffe7f8c000)
libpanel.so.5 => /usr/lib/libpanel.so.5 (0x00007faa1301c000)
libncurses.so.5 => /lib/libncurses.so.5 (0x00007faa12dfb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faa12a59000)
libtinfo.so.5 => /lib/libtinfo.so.5 (0x00007faa12832000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007faa1262e000)
/lib64/ld-linux-x86-64.so.2 (0x00007faa13246000)
O campo 1 diz o nome da dependência, o campo 2 é o divisor e o campo 3 é a localização da dependência. Sabendo disso, a meneira mais simples de recolher o programa e suas dependências é esse:
mkdir iptraf
cd iptraf
cp $(which iptraf) . --parents
cp $(ldd `which iptraf`|awk '{print $3}'|egrep -v 'libc|grep(|^0-9|^
Depois disso, basta fazer a cópia do conteúdo do diretório iptraf para a raiz do rootfs com cp -rfv iptraf/* /caminho/do_rootfs.
Repare que a base que utilizei para esse exemplo é um sistema 64bits.
Esse método 'revolucionário' e prático é bastante útil para economizar tempo na compilação de uma aplicação específica e já foi bastante utilizada no Phantom. Porém, não serve para tudo. Haverão casos em que será necessário customizar um código, ou as libs do sistema alvo são incompatíveis com a versão do program contido no sistema nativo além de outros mais. Por isso você deve ter em mente que é fundamental aprender a construir seus próprios binários. Dito isso, vamos reconstruir o arquivo CPIO (supondo o rootfs em /building/rootfs):
cd /building/rootfs
(find . |cpio -o -H newc|xz -F lzma) >../output/initrd.lz
O arquivo initrd será criado um nível abaixo do rootfs. Nesse caso, a compressão está sendo feita com o magnífico lzma, mas é necessário que o kernel também esteja configurado para descomprimir esse formato de arquivo.
Se você pegou um sistema de initrd único, então bastará substituir o initrd da iso pelo seu. Para tal, utilize o programa isomaster.
Depois disso você poderá fazer um boot com o qemu para testá-lo, ou ainda você poderá utilizar o kernel desse sistema para fazer um boot no seu initrd antes mesmo de compilar sua iso. Para isso, leia o procedimento descrito nesse outro post.
Falamos sobre toolchains na parte 2 desse artigo. O toolchain adotado para seguir com os exemplos será o buildroot, que deve ser baixado daqui.
A versão mais atual até esse post é a 2012.05. As releases tem em média 3 meses de intervalo, sendo que a anterior foi lançada em fevereiro. Na atual versão entram diversos novos programas e inicialmente um bug para compilar o gnutls. Então, o primeiro passo após a descompressão do buildroot é entrar no respectivo diretório seguindo até o diretório de configuração do pacote em [buildroot_dir]/package/gnutls e editar o arquivo gnutls.mk, e simplesmente fazer uma substituição. Troque:
--with-libgcrypt-prefix=$(...)
Por:--without-libgcrypt-prefix
Parece bobinho, mas isso rendeu sofrimento por alguns dias; tenha essa dica como preciosa!
Leia a documentação no site do buildroot para compreender toda sua estrutura. Basicamente vou falar dos resultados da compilação. Da raiz do sistema, os diretório que trataremos são os seguintes descritos:
Diretório onde se encontrarã os pacotes gzipados que tenham sido baixados. Uma dica para isso; se o download ficar intermitente, você pode copiar a url de onde o buildroot está tentando baixar o pacote e fazê-lo manualmente com o wget utilizando a flag -c, para que possa ser interrompido e continuado o download. Tive que adotar essa prática em vários pacotes.
A saída de toda sua compilação deverá vir para cá. Dentro desse diretório haverão alguns diretórios de maior importância, como host, images e target. Dentro de images estarão os formatos escolhidos para a geração de imagem (pode ser escolhido mais que uma no menu), como rootfs.cpio, rootfs.tar, rootfs.iso9660, rootfs.jffs2, etc.
No diretório target estará o sistema criado, sendo possível acessá-lo via chroot, caso a arquitetura compilada também seja x86.
Mais uma vez recomendo a leitura da documentação, mas para demonstrar basicamente o processo, vou mostrar a compilação (ainda incompleta) do fsarchiver. O processo de criação, considerando que esteja na raiz do buildroot:
cd /package/fsarchiver
mkdir fsarchiver
vim Config.in
--------conteúdo-----------
config BR2_PACKAGE_FSARCHIVER
bool "FSArchiver"
select BR2_PACKAGE_E2FSPROGS
help
Backup system tool
http://url.net/dir/etc/
-------fim do conteúdo------
A opção select força a inclusão das dependências.
Um arquivo fsarchiver.mk deve ser criado com um conteúdo similar a esse:
##########################
# FSArchiver
##########################
#Versão a pegar, nome do pacote e URL
FSARCHIVER_VERSION = 0.6.15
FSARCHIVER_SOURCE = fsarchiver-.0.6.15.tar.gz
FSARCHIVER_SITE = http://url...
FSARCHIVER_DEPENDENCES =
FSARCHIVER_INSTALL_STAGING = YES
FSARCHIVER_INSTALL_TARGET = YES
FSARCHIVER_CONF_OPT = --program-prefix=''
$(eval $(call AUTOTARGETS,package,fsarchiver))
Para que o FSArchiver apareça no menu, deve-se editar o Config.in da raiz do diretório package e criar uma entrada tão simples quanto as que lá estão.
Se tudo fosse perfeito, isso compilaria de primeira, mas não está sendo tão simples até o momento em que escrevo este post. A cross-compilação do Partimage foi um verdadeiro parto, incluindo modificações em dois métodos e alguns outros lugares para que a compilação sucedesse sem erros. Consegui montar um pacote para ele, mas como é especifico para o Phantom, não vale a pena exemplificá-lo. Apesar de ter sido compilado, resta saber se funciona, ou seja, depois desse parto todo ainda preciso enviar o sistema compilado para depuração.
Porém, se for compilar os pacotes já contidos dentro do buildroot, pode ser bem menos doloroso - mas certamente haverá problema, pois como citado ao início, tive que mudar o construtor do gnutls para compilar adequadamente. Então para acessar o menu e fazer a sua compilação, basta:
make menuconfig
E fazer ai sua seleção.
Se quiser compilar algum pacote sem ter que procurá-lo pelos menus, basta primeiramente listar o diretório de pacotes dentro da raiz do buildroot, selecionando-o no diretório package. Depois basta fazer um make pacote_escolhido e aguardar a compilação.
Finalizando essa parte de pacotes, pode ser que alguma dependência seja reclamada, bastando preceder a compilação do programa escolhido pela compilação da dependência, que certamente estará dentro do diretório package.
No próximo post veremos como cross-compilar um programa manualmente, como examinar um binário, como examinar chamadas de sistema para fazer debugging e como analisar leak de memória, tudo isso com as ferramentas de cross-compilação da sua toolchain e alguns recursos extras como o hexdump, strings, ldd, strace e ltrace.
Se o fsarchiver estiver funcional até o próximo post, farei já os demais exclarecimentos. Até a próxima!
) . --parents
Depois disso, basta fazer a cópia do conteúdo do diretório iptraf para a raiz do rootfs com cp -rfv iptraf/* /caminho/do_rootfs
.
Repare que a base que utilizei para esse exemplo é um sistema 64bits.
Esse método 'revolucionário' e prático é bastante útil para economizar tempo na compilação de uma aplicação específica e já foi bastante utilizada no Phantom. Porém, não serve para tudo. Haverão casos em que será necessário customizar um código, ou as libs do sistema alvo são incompatíveis com a versão do program contido no sistema nativo além de outros mais. Por isso você deve ter em mente que é fundamental aprender a construir seus próprios binários. Dito isso, vamos reconstruir o arquivo CPIO (supondo o rootfs em /building/rootfs):
O arquivo initrd será criado um nível abaixo do rootfs. Nesse caso, a compressão está sendo feita com o magnífico lzma, mas é necessário que o kernel também esteja configurado para descomprimir esse formato de arquivo.
Se você pegou um sistema de initrd único, então bastará substituir o initrd da iso pelo seu. Para tal, utilize o programa isomaster.
Depois disso você poderá fazer um boot com o qemu para testá-lo, ou ainda você poderá utilizar o kernel desse sistema para fazer um boot no seu initrd antes mesmo de compilar sua iso. Para isso, leia o procedimento descrito nesse outro post.
No próximo post veremos como cross-compilar um programa manualmente, como examinar um binário, como examinar chamadas de sistema para fazer debugging e como analisar leak de memória, tudo isso com as ferramentas de cross-compilação da sua toolchain e alguns recursos extras como o hexdump, strings, ldd, strace e ltrace.
Se o fsarchiver estiver funcional até o próximo post, farei já os demais exclarecimentos. Até a próxima!
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.