Discuta este tópico no fórum

Se este conteúdo te ajudou, deixe um presente!

Mostrando postagens com marcador compilação cruzada. Mostrar todas as postagens
Mostrando postagens com marcador compilação cruzada. Mostrar todas as postagens

quinta-feira, 16 de março de 2017

OpenWRT/LEDE: Debugando código dentro do roteador (e corrigindo bugs)

Mais um artigo da série OpenWRT/LEDE.

Fazer o debug de um programa muda a forma de desenvolvimento de um código. É imensamente melhor do que usar printf ao longo do código. Você pode ver os valores de qualquer variável, adicionar paradas condicionais, ver onde ele engasgou, etc. Para tudo isto, basta compilar seu código com os símbolos de debug, acesso ao código fonte (ajuda) e um debugger.

Uma das coisas que eu acho fantástico no mundo Linux é, diante de um problema, instalar os símbolos, código fonte e disparar um debugger. Isto com o processo rodado! Apesar dos programas instalados não conterem os símbolos, normalmente a distribuição fornece estes símbolos em um pacote a parte. Eu parto de um processo problemático em execução para um passo a passo dentro do código em poucos minutos. Fantástico!

Agora voltamos ao tema do artigo: como posso "debugar" dentro do OpenWRT/LEDE? Temos os símbolos nos executáveis? Não, são removidos antes de serem empacotados pelo sstrip. Mesmo que não fossem, normalmente você não teria espaço no roteador para instalar um executável com os símbolos de debug e mais o código fonte (ao menos que tenha expandido o disco com uma unidade externa). O gdb seria até pequeno, mas é sem símbolos e o fonte ele não tem muita utilidade. Mesmo se tivesse tudo isso, ainda debugar pelo gdb remotamente não é a forma mais amigável para este trabalho. Para coisas maiores, uma interface gráfica pode deixar o trabalho mais agradável.

Felizmente o OpenWRT/LEDE já está preparado para esta interação. Vamos tomar como exemplo o sane-backends, pacote que mantenho no OpenWRT/LEDE. Ele estava com um comportamento engraçado. O scanner aparecia na listagem mas sempre falhava ao escanear. Rodando localmente um escaneamento, eu obtinha:
root@router.lan3:~# scanimage >/dev/null
Trace/breakpoint trap
Parei o xinetd (que estava aguardando pelos pedidos do saned) e rodei o saned em modo debug. Novamente, o famigerado erro:
root@router.lan3:~# saned -d 128

[saned] main: starting debug mode (level 2)
[saned] saned (AF-indep+IPv6) from sane-backends 1.0.25 starting up
[saned] do_bindings: [0] bind failed: Address in use
[saned] check_host: access by remote host: ::ffff:192.168.3.20
[saned] init: access granted to luizluca@::ffff:192.168.3.20
Trace/breakpoint trap
Não fui apresentado, mas pelo nome, parece algo explicitamente introduzido. Alguma verificação falhou e abortou o programa. Sem mais informações por meio de mensagens, resta o debug do código.

O primeiro passo é ter os fontes e os códigos compilados. Provavelmente tudo isso se aplica caso opte por usar o SDK, mas vou, a princípio considerar que você está usando a árvore completa do projeto. Você precisa dos fontes e configurar o ambiente para compilar, ao menos, o sane-backend. Esta preparação já foi feita em outro artigo sobre compilação a partir dos fontes.

No roteador, eu irei debugar o programa saned, que não contém informações de debug. No pacote, ele é bem reduzido (menos que 33 kbytes):
$ tar --to-stdout -xzf bin/packages/mips_24kc/packages/sane-daemon_1.0.25-2_mips_24kc.ipk ./data.tar.gz | tar tzv ./usr/sbin/saned

-rwxr-xr-x root/root     32900 2017-03-12 22:40 ./usr/sbin/saned
Porém, antes de ser limpo pelo sstrip, ele tinha:
$ stat -c "%A %U %G %y %s %n" staging_dir/target-mips_24kc_musl-1.1.16/root-ar71xx/usr/sbin/saned
-rwxr-xr-x luizluca deluca 2017-03-12 22:40:49.510354194 -0300 44200 staging_dir/target-mips_24kc_musl-1.1.16/root-ar71xx/usr/sbin/saned
Agora vamos recompilar o pacote com os símbolos. Você pode fazer isso adicionando a opção no menuconfig para incluir os símbolo de debug em toda a árvore. Eu prefiro adicionar pontualmente os símbolos em alguns pacotes. Para isto, basta incluir a configuração extra na chamada do make:
$ make -j5 package/sane-backends/{clean,compile} CONFIG_DEBUG=y
Adicione ainda um V=s se quiser ver muitas letrinhas passando (ou precisar resolver problemas de compilação). Se fizer, observe um -g3 a mais nos argumentos do gcc.

O resultado é um executável relativamente maior:
$ stat -c "%A %U %G %y %s %n" staging_dir/target-mips_24kc_musl-1.1.16/root-ar71xx/usr/sbin/saned -rwxr-xr-x luizluca deluca 2017-03-12 23:56:52.157169597 -0300 188296 staging_dir/target-mips_24kc_musl-1.1.16/root-ar71xx/usr/sbin/saned
Mas o ipkg continua exatamente igual, pois os símbolos ainda foram removidos antes do empacotamento.

No meu caso, uso o driver hplip. Então, incluí em todo o processo anterior este pacote. Além de adicionar no menuconfig, o make ficou assim:
$ make -j5 package/{sane-backends,hplip}/clean package/{sane-backends,hplip}/compile CONFIG_DEBUG=y
Tudo pronto, agora vamos ao roteador. Vou instalar o gdbserver. Como o nome já diz, é um servidor do gdb, permitindo que um gdb na minha máquina use um executável rodando em uma máquina remota.
root@router.lan3:~# opkg install gdbserver
E agora, repito o comando do saned mas pelo gdbserver:

root@router.lan3:~# gdbserver :9000 /usr/sbin/saned -d 128Process /usr/sbin/saned created; pid = 4391Listening on port 9000
A princípio, ele inicia o processo parado, aguardando a conexão do gdb e sua autorização para continuar. No lado do computador, o OpenWRT/LEDE já fornece um "facilitador" para configurar o gdb remoto:
$ ./scripts/remote-gdb router:9000 staging_dir/target-mips_24kc_musl-1.1.16/root-ar71xx/usr/sbin/saned
Pronto! Estou com o debugger na mão. Peço ao gdb para parar ao receber qualquer sinal ("catch signal all"). Depois, libero a execução ("cont") até chegar em algo interessante.
Thread 1 "saned" hit Catchpoint 1 (signal SIGTRAP), memcpy (__n=270, __os=0x77dc3a76 <ms+770086>, __od=0x77dc3a74 <ms+770084>)    at /home/luizluca/prog-local/lede/17.01/staging_dir/toolchain-mips_24kc_gcc-5.4.0_musl-1.1.16/include/fortify/string.h:4848 __builtin_trap();
(gdb) bt#0  memcpy (__n=270, __os=0x77dc3a76 <ms+770086>, __od=0x77dc3a74 <ms+770084>) at /home/luizluca/prog-local/lede/17.01/staging_dir/toolchain-mips_24kc_gcc-5.4.0_musl-1.1.16/include/fortify/string.h:48#1  device_id (fd=1,     buffer=0x77dc3a74 <ms+770084> "\001\020MFG:HP;MDL:Officejet J4660 series;CMD:MLC,PCL,PML,DW-PCL,DESKJET,DYN;1284.4DL:4d,4e,1;CLS:PRINTER;DES:CB786A;SN:BR145GF09J052W;S:038000C484001021002c1f0005cc2d8002b;J:", ' ' <repete 20 vezes>, ";Z:0102,050"..., size=1024) at io/hpmud/musb.c:773#2  0x77cbe5cd in musb_open (pd=<optimized out>) at io/hpmud/musb.c:1160#3  0x77cb8067 in hpmud_open_device (uri=<optimized out>, iomode=HPMUD_DOT4_MODE, dd=0x41b0a4) at io/hpmud/hpmud.c:539#4  0x77ebbc51 in sclpml_open (device=0x418a96 "/usb/Officejet_J4660_series?serial=BR145GF09J052W", pHandle=0x7ffffaf4) at scan/sane/sclpml.c:2016#5  0x77eb02f7 in sane_hpaio_open (devicename=0x418a96 "/usb/Officejet_J4660_series?serial=BR145GF09J052W", pHandle=0x7ffffaf4) at scan/sane/hpaio.c:267#6  0x77f3f673 in sane_dll_open (full_name=<optimized out>, meta_handle=0x0) at dll.c:1201#7  0x77f31e97 in sane_open (name=<optimized out>, h=<optimized out>) at dll-s.c:23#8  0x00403391 in process_request (w=0x418198 <wire>) at saned.c:1916#9  0x00403a99 in handle_connection (fd=<optimized out>) at saned.c:2283#10 0x00402031 in run_standalone (argv=<optimized out>, argc=3) at saned.c:3167#11 main (argc=<optimized out>, argv=<optimized out>) at saned.c:3345

Hum... fortify... O LEDE introduziu opções de compilação que restringem alguns comportamentos inseguros (_FORTITY_SOURCE), "fortificando" o programa contra ataques. Pela pilha, isso foi dentro do hplip. Acho que ele tem algum problema...

A chamada em questão foi em io/hpmud/musb.c, linha 773:

 771     if (len > 2)
 772         len -= 2;
 773     memcpy(buffer, buffer+2, len);    /* remove length */
 774     buffer[len]=0;
 775     DBG("read actual device_id successfully fd=%d len=%d\n", fd, len);

Opa! Copiando o buffer sobre ele mesmo para tirar os dois primeiros bytes? OK, mas não usando memcpy! Muito feio! Isto nunca era para dar certo... O memcpy simplesmente copia de um lado para outro, desconsiderando sobreposição. Isto quer dizer, pode copiar do começo ao fim (ou o inverso), em blocos de vários bytes, etc. Foi sorte ter funcionado até agora (e funcionou?). A alternativa correta é o memmove, que verifica possíveis sobreposições e decide se copia de trás para frente, ou do início ao fim. Caso não tenha sobreposições, simplesmente chama o memcpy. E  claro que temos sobreposições aqui!

Vamos tentar a última versão 3.16.11 do hplip? Não, ainda está lá o memcpy. Vamos precisar fazer um patch.

A melhor forma de trabalhar com patches dos programas empacotados no OpenWRT/LEDE é pelo quilt. Ele gerencia os patches de forma menos dolorida, em especial para atualizar os endereços depois de uma mudança de versão, onde os "hunks" funcionaram. Como acabei de atualizar a versão do hplip, devem ocorrer mudanças nos fontes e os patches existentes precisam ser atualizados:
$ make package/hplip/{clean,prepare} QUILT=y
$ cd build_dir/target-mips_24kc_musl-1.1.16/hplip-3.16.11
$ quilt push
Aplicando caminho 010-libusb_fix.patch
patching file configure.in
Hunk #1 succeeded at 561 (offset 38 lines).
Agora no caminho 010-libusb_fix.patch
 
$ quilt refresh # atualizar o endereço do patch
Patch atualizado 010-libusb_fix.patch
Tudo certo até aqui. Patch atualizado sem esforço.
$ quilt push
Aplicando caminho 020-remove_cups_dep_on_scan.patch
patching file scan/sane/hpaio.c
Hunk #2 FAILED at 172.
Hunk #3 FAILED at 232.
Hunk #4 FAILED at 259.
3 out of 4 hunks FAILED -- rejects in file scan/sane/hpaio.c
patching file Makefile.am
Hunk #1 succeeded at 60 with fuzz 1 (offset 1 line).
Caminho 020-remove_cups_dep_on_scan.patch não aplica (force com -f)
Hum... conflito. Preciso intervir manualmente.
$ quilt push -fAplicando caminho 020-remove_cups_dep_on_scan.patchpatching file scan/sane/hpaio.cHunk #2 FAILED at 172.Hunk #3 FAILED at 232.Hunk #4 FAILED at 259.3 out of 4 hunks FAILED -- saving rejects to file scan/sane/hpaio.c.rejpatching file Makefile.amHunk #1 succeeded at 60 with fuzz 1 (offset 1 line).Caminho 020-remove_cups_dep_on_scan.patch aplicado (forçado; necessita ser atualizado)
$ quilt edit scan/sane/hpaio.c<faz a mágica para resolver o conflito...>$ quilt refresh
Agora vamos, enfim, fazer o novo patch:
$ quilt new 030-replace_unsafe_memcpy_with_memmove.patch$ quilt edit io/hpmud/musb.c<troca o memcpy por memmove>$ quilt refresh
De volta a raiz do projeto, pedimos que ele atualize os patches: 
make package/hplip/update QUILT=y
E, por fim, limpar e recompilar:
make package/hplip/{clean,compile} CONFIG_DEBUG=y
Tudo compilado, só copiar, instalar e testar

$ scp bin/packages/mips_24kc/packages/hplip-* root@router:/tmp
root@router.lan3:~# opkg install /tmp/*hplip*
Upgrading hplip-common on root from 3.15.7-1 to 3.16.11-1...
Upgrading hplip-sane on root from 3.15.7-1 to 3.16.11-1...
Configuring hplip-common.
Configuring hplip-sane.
root@router.lan3:~# 
E...

Funcinou!


Infelizmente, o "funcionar" é não mostram uma mensagem de erro e não abortar o processo. Não faz muito sentido colar a saída.

Agora, para a cereja do bolo, bug submetido ao hplip.

Até a próxima. Happy Hacking!

domingo, 12 de março de 2017

OpenWRT/LEDE: Compilando a partir dos fontes

Mais um artigo da série OpenWRT/LEDE.

Dificilmente alguém vai precisar compilar o seu sistema operacional. Isto se aplica também aos ambientes embarcados. O OpenWRT/LEDE fornece o acréscimo de programas por pacotes, a personalização dos pacotes de uma firmware, ou mesmo um ambiente de desenvolvimento para compilar pacotes individuais. Tudo isso evita em muito a necessidade de recompilar todo o ambiente. Todavia, às vezes é inevitável.

Sempre que a mudança não é em "um pacote", vai precisar recompilar a árvore. Mudança de um parâmetro do kernel? Parâmetro do gerenciador de boot? Alguma outra coisa? Pode se preparar para recompilar tudo. E tudo é bastante coisa. O ambiente não só recompila o que vai no roteador mas compiladores, ligadores, ferramentas de geração de firmware, sistema de arquivos, etc. Tudo para ser o mais independente possível do sistema operacional onde está sendo compilado.

Como eu começo? Primeiro precisa de algumas ferramentas básicas, como o git, o gcc (para compilar o gcc do OpenWRT/LEDE, o make e algumas bibliotecas de desenvolvimento para compilar o menuconfig. Já existem algumas linhas de comando prontas para instalar estes requisitos no site do LEDE. Vale a olhada. Depois, só baixar os fontes. Vou pegar o atual ramo estável do LEDE:
git clone --branch lede-17.01 https://github.com/lede-project/source.git
E depois configurar o ambiente:
make menuconfig
No menuconfig, você pode configurar todo seu ambiente. Normalmente você terá que configurar, ao menos, o alvo (target) e, preferencialmente, o modelo do roteador (por padrão, ele gera firmware para todos!). Este é o local para adicionar os pacotes. A simbologia é:
  • [*] significa que o pacote estará na firmware gerada ou uma opção foi selecionada;
  • [M] significa que o pacote será gerado (ipk) mas não estará na firmware;
  • [ ] significa que o pacote não será compilado ou opção não selecionada.
Saindo e salvando, basta compilar:
make
Ou, se tiver processadores sobrando, deixar o make usar diversos processadores:
make -j9
Não achei no menu um pacote! Busque apertando "/" no menuconfig. Ainda não o Luci! A maioria dos pacotes estão nos feeds adicionais, por padrão sem pacotes incluídos. Para incluir o "meu-pacote-querido":
./scripts/feeds update
./scripts/feeds install meu-pacote-querido
E ele aparecerá no menu (previamente ainda não selecionado).

Ao terminar o processo do make, sua firmware estará em:
  • bin/targets/<alvo configurado>/generic/
Os pacotes estarão em:
  • bin/targets/<alvo configurado>/generic/packages/ (para os específicos deste alvo)
  • bin/packages/<arquitetura>/luci (para pacotes da arquitetura que o <alvo configurado> pertence)
Você está pronto para se divertir. Só gravar a nova firmware ou copiar e instalar os novos pacotes.

Quer mudar algo depois? Mexer em um pacote? As receitas estarão em packages ou em feeds. Se mudar um pacote, como o sane-backends, pode recompilá-lo individualmente:
make package/meu-pacote-querido/compile
Ou rodar simplesmente o make geral e deixar ele encontrar o que falta fazer (vai demorar mais). Se tiver problemas, pode mostrar as informações da compilação:
make package/meu-pacote-querido/compile V=s
É isso pessoal. Espero que esta informação ajude nos primeiros passos de desenvolvimento do OpenWRT/LEDE. Quem sabe não ajude a revelar um novo desenvolvedor para o projeto?

Happy hacking!

quinta-feira, 2 de abril de 2015

OpenWRT: Criando novos pacotes - construção de um pacote

Mais um artigo da série sobre o OpenWRT.
 artigo anterior, apresentei o problema da compilação de um novo programa para o OpenWRT. Neste vamos colocar a mão na massa.


Vou pegar um exemplo simples: o figlet. Ele faz isto:

$ figlet LuizLuca
 _          _     _                    
| |   _   _(_)___| |   _   _  ___ __ _ 
| |  | | | | |_  / |  | | | |/ __/ _` |
| |__| |_| | |/ /| |__| |_| | (_| (_| |
|_____\__,_|_/___|_____\__,_|\___\__,_|
                                       

Fantástico, não é? (nem sei como vivi sem ele até hoje) O que o programa faz não importa neste caso.

Você tem duas opções para montar o ambiente para gerar seus pacotes: recompilar todo o OpenWRT ou utilizar o SDK disponível no mesmo local onde estão as firmwares. O primeiro caso vai utilizar mais espaço e demorar mais mas traz a vantagem de isolar problemas do seu ambiente de problemas do seu pacote. Afinal de contas, se compilou todo o OpenWRT e não o meu pacote, o problema provavelmente é comigo. Fora isto, os fontes locais podem facilitar o uso destes como inspiração ou modelo para seu próprio pacote. Problemas de compilação cruzada se repetem aos montes e espiar o vizinho ajuda muito. Também, se seu desejo é integrar o pacote de volta ao OpenWRT para que outros usuários também possam aproveitar seu trabalho, o ideal é trabalhar em conjunto com todo o OpenWRT. 
A segunda opção é mais econômica. Serve mais para compilações isoladas. Como é o caso deste exemplo, foi optar por esta alternativa.

Vou pegar a família ar71xx, muito comum em roteadores, para este exemplo. O SDK desta família está aqui. Baixe de descompacte:

$ wget https://downloads...OpenWRT-SDK-ar71xx-for-linux-...tar.bz2
$ tar -xjvf OpenWRT-SDK...tar.bz2
$ cd OpenWrt-SDK-...

Tem um problema nesta versão do SDK. Ele está com o uso do ccache ativado. Caso isto seja um problema para você, desative-o editando o arquivo Config-build.in nesta parte:

config CCACHE
    bool
    default n

Estamos prontos para criar o primeiro pacote. crie um subdiretório em package. No caso, criarei o figlet (OpenWrt-SDK-.../package/figlet/. Dentro deste criaremos um arquivo Makefile descrevendo o pacote. Makefile? Mas isto não é usado depois na compilação? Sim, o OpenWRT (ab)usou o make para montar todo seu ambiente de compilação. Este Makefile não é o mesmo gerado pelo ./configure mas um padrão do OpenWRT que descreve as informações do seu pacote, como compilar, instalar e metainformações, assim como ocorre nos arquivos spec do RPM. Sim, pode ser feio, nem lembra um Makefile comum, mas funciona. Fora o Makefile existem outros arquivos opcionais. Abaixo do subdiretório "patches" ficam eventuais correções para consertar ou adaptar problemas nos fontes do programa. Caso necessite adicionar algum arquivo não gerado pelo ou presente nos fontes, como scripts de disparo de serviços, estes podem ficam em um outro subdiretório "files". E os fontes? Isto o OpenWRT baixa para você. Você só precisa dizer de onde no Makefile.

O arquivo Makefile do exemplo deste artigo ficaria em package/figlet/Makefile. Inicialmente é assim (em destaque o que alterei):

#
# Copyright (C) 2017 Luiz Angelo Daros de Luca
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=figlet
PKG_VERSION:=2.2.5
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=ftp://ftp.figlet.org/pub/figlet/program/unix/
PKG_MD5SUM:=d88cb33a14f1469fff975d021ae2858e
PKG_MAINTAINER:=Luiz Angelo Daros de Luca <luizluca@gmail.com>
PKG_LICENSE:=BSD-3-Clause
PKG_LICENSE_FILES:=LICENSE
PKG_INSTALL:=1

include $(INCLUDE_DIR)/package.mk

define Package/figlet
  SECTION:=xxx
  CATEGORY:=Utilities
  TITLE:=FIGlet
  URL:=http://www.figlet.org/
  MAINTAINER:=Claudio Matsuoka <cmatsuoka@gmail.com>
  DEPENDS:=
endef

define Package/figlet/description
  FIGlet is a program for making large letters out of ordinary text
endef

# Eh obrigatorio ser tabs antes das linhas abaixo
define Package/figlet/install
 $(INSTALL_DIR) $(1)/usr/bin
 $(CP) $(PKG_INSTALL_DIR)/usr/bin/* $(1)/usr/bin
 $(INSTALL_DIR) $(1)/usr/share/figlet/
 $(CP) $(PKG_INSTALL_DIR)/usr/share/figlet/* $(1)/usr/share/figlet/
endef

$(eval $(call BuildPackage,figlet))

O cabeçalho do arquivo, comentado, é meramente informativo. Os PKG_* descrevem dados para os pacotes gerados por este Makefile (sim, pode ser mais de um mas não é o caso deste exemplo). Os nomes são bem auto-explicativos. NAME é o nome do pacote, VERSION (do programa) e RELEASE (do pacote), em conjunto, formarão a versão do pacote. SOURCE_URL/SOURCE indicam de onde baixar e o MD5SUM o hash do arquivo. O campo MAINTAINER é o "dono" deste pacote. O PKG_INSTALL pede para que o "make install" seja executado após a compilação.

O "define Package/figlet" efetivamente declara o pacote. Em especial, observe o campo DEPENDS, que possui uma sintaxe própria e é usado para definir as dependências entre os pacotes. Este pacote de exemplo não tem dependências conhecidas (até este momento). "Package/figlet/description" é apenas um texto descritivo.

A compilação ocorre em uma série de etapas.
  • Build/Prepare:  descompacta os fontes, e aplica os patches (Build/Patches)
  • Build/Configure: roda o ./configure (ou equivalente)
  • Build/Compile: roda o make
  • Build/Install: se PKG_INSTALL for 1, roda o "make install", instalando em $(PKG_INSTALL_DIR)
Normalmente você não irá querer mudar essas etapas. Se não definidas, elas irão utilizar um valor padrão, de uma macro com o mesmo nome e sufixo /Default, que normalmente funcionará. Ex: se Build/Prepare não existir no pacote, será utilizado o Build/Prepare/Default. Você tem liberdade de mudar qualquer coisa dessas etapas, chamando ou não o respectivo valor /Default. Porém, normalmente os ajustes necessários são feitos mudando as variáveis usadas (ex: CONFIGURE_ARGS). Se quiser ver o conteúdo, olhe em https://github.com/openwrt/openwrt/blob/master/include/package-defaults.mk.

O "define Package/figlet/install" é um passo importante. Ele preenche a árvore que formará os arquivos do pacote. Este caminho é passado como argumento $(1). Então, se criar um $(1)/usr/yyy, o pacote será o conteúdo /usr/yyy. Normalmente o Package/xxx/install é uma sequência de chamadas ao comando install, que é equivalente uma composição dos comandos mkdir, cp, chmod, chown. Se o "make install" foi utilizado, o Package/xxx/install irá copiar arquivos de $(PKG_INSTALL_DIR) para $(1). Ao invés de usar diretamente o comando install ou ainda o cp/mkdir, o recomendado é utilizar o INSTALL_DIR, INSTALL_BIN, INSTALL_CONFIG, INSTALL_DATA (definidos em https://github.com/openwrt/openwrt/blob/master/rules.mk#L257)

E vamos a compilação:

$ make
Collecting package info: done
#
# configuration written to .config
#
 make[1] world
 make[2] package/compile
 make[3] -C package/figlet compile
make -r world: build failed. Please re-run make with V=s to see what's going on

Erro?! Mas o que aconteceu? O OpenWRT não mostra o erro por padrão. Como sugerido, rode novamente com o V=s para ver o que está acontecendo (vou omitir linhas não relevantes):

$ make V=s
...
mips-openwrt-linux-uclibc-ld  -o figlet figlet.o zipio.o crc.o inflate.o utf8.o
mips-openwrt-linux-uclibc-ld: warning: cannot find entry symbol __start; defaulting to 00000000004000b0
figlet.o:OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/figlet.c:293: undefined reference to `stderr'
figlet.o:OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/figlet.c:293: undefined reference to `stderr'
figlet.o:OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/figlet.c:293: undefined reference to `fprintf'
...

Não achando stderr? fprintf? __start? Isto é da libc! Quem já programou um pouco de C sabe que normalmente não precisamos indicar manualmente a libc para o ligador (linker). E por que ele não achou, então? Bem, o figlet é um dos exemplos onde o desenvolvedor optou por não usar o ./configure (autoconf). Ele escreveu o Makefile manualmente. O problema é que algumas coisas são classicamente configuradas no configure, como o prefixo do programa e parâmetros do compilador e do ligador. Outro problema é assumir coisas que nem sempre são verdades. O problema acima, por exemplo, ocorre porque o ligador (ld) não está ligando a libc ao programa caso isto não seja solicitado explicitamente. E por que funciona para o desenvolvedor? Como ele não viu isto? Na maioria dos casos, o ld disponível no Linux adiciona a libc por padrão e não é necessário explicitá-la. No caso do OpenWRT, o linker não faz isto. Se usasse o ./configure, provavelmente o ligador seria configurado apropiadamente (adicionando a libc). Como já enfrentei este problema anteriormente, conheço uma solução de contorno: usar o gcc como ligador. Isto pode ser feito substituindo a variável ambiente LD do make pelo compilador do alvo. Só adicionar isto ao Makefile anterior:

MAKE_FLAGS += \
    LD="$(TARGET_CC)"

E compilar novamente:

$ make V=s
...
cp figlet chkfont figlist showfigfonts /home/luizluca/Downloads/OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/ipkg-install/usr/local/bin
...
cp: cannot stat '/home/luizluca/Downloads/OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/ipkg-install/usr/bin/*': No such file or directory

Erro novamente?! Ao menos ele compilou. Por que ele não achou os arquivos? Porque ele instalou o figlet abaixo de /usr/local e não de /usr, como é o padrão do OpenWRT (e da maioria das distribuições). Esta configuração do prefixo é, na maioria dos casos, configurada pelo script ./configure. Se quisesse, poderia alterar o nosso Makefile para usar o /usr/local. Porém, este não é o caminho padrão para este tipo de arquivo. Novamente, a opção por criar manualmente o Makefile prejudicou a compilação cruzada. Caso usasse o ./configure, o OpenWRT iria definir o prefixo automaticamente. Solução? A mesma usada anteriormente: outra variável ambiente para reconfigurar o Makefile do figlet.

MAKE_FLAGS += \
    LD="$(TARGET_CC)" \
    prefix="$(CONFIGURE_PREFIX)"

E agora sim:

$ make
Collecting package info: done
#
# configuration written to .config
#
 make[1] world
 make[2] package/compile
 make[3] -C package/figlet compile
 make[2] package/index

Sucesso! Seu pacote estará em bin/ar71xx/packages/base/figlet_2.2.5-1_ar71xx.ipk.

Copiando para um roteador e instalado. Não é que ele funciona?

$ scp bin/ar71xx/packages/base/figlet_2.2.5-1_ar71xx.ipk root@router:/tmp 
$ ssh root@router
root@router:~# opkg install /tmp/figlet_2.2.5-1_ar71xx.ipk 
Installing figlet (2.2.5-1) to root...
Configuring figlet.
root@router:~# figlet "Funciona!"
 _____                 _                   _ 
|  ___|   _ _ __   ___(_) ___  _ __   __ _| |
| |_ | | | | '_ \ / __| |/ _ \| '_ \ / _` | |
|  _|| |_| | | | | (__| | (_) | | | | (_| |_|
|_|   \__,_|_| |_|\___|_|\___/|_| |_|\__,_(_)
                                             

O Makefile final ficou:


#
# Copyright (C) 2017 Luiz Angelo Daros de Luca
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=figlet
PKG_VERSION:=2.2.5
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=ftp://ftp.figlet.org/pub/figlet/program/unix/
PKG_MD5SUM:=d88cb33a14f1469fff975d021ae2858e
PKG_MAINTAINER:=Luiz Angelo Daros de Luca <luizluca@gmail.com>
PKG_LICENSE:=BSD-3-Clause
PKG_LICENSE_FILES:=LICENSE
PKG_INSTALL:=1

include $(INCLUDE_DIR)/package.mk

MAKE_FLAGS += \
   LD="$(TARGET_CC)" \
   prefix="$(CONFIGURE_PREFIX)"

define Package/figlet
 SECTION:=xxx
 CATEGORY:=Utilities
 TITLE:=FIGlet
 URL:=http://www.figlet.org/
 MAINTAINER:=Claudio Matsuoka <cmatsuoka@gmail.com>
 DEPENDS:=
endef

define Package/figlet/description
 FIGlet is a program for making large letters out of ordinary text
endef

# Eh obrigatorio ser tabs antes das linhas abaixo
define Package/figlet/install
   $(INSTALL_DIR) $(1)/usr/bin
   $(CP) $(PKG_INSTALL_DIR)/usr/bin/* $(1)/usr/bin
   $(INSTALL_DIR) $(1)/usr/share/figlet/
   $(CP) $(PKG_INSTALL_DIR)/usr/share/figlet/* $(1)/usr/share/figlet/
endef

$(eval $(call BuildPackage,figle
t))


Nem sempre preparar um pacote de algo mais simples será mais fácil. Às vezes, a simplificação do programa pode levar o desenvolvedor a não utilizar práticas padrão de desenvolvimento. Nem cheguei a tratar dependências de bibliotecas. Quando o programa fonte é bem feito, normalmente é questão de adicionar a biblioteca necessária na lista de dependências e pronto. O OpenWRT, inclusive, verifica automaticamente se você não esqueceu de uma delas.

Se fizer algo interessante para o OpenWRT e quiser compartilhar, o desenvolvimento dos pacotes "extras" do OpenWRT está bem produtivo. Ele migrou para https://github.com/openwrt/packages.

Se precisar de uma ajuda, crie um tópico no fórum do blog ou no tópico deste artigo.
Até a próxima.

Atualização em 2017-05-11: adicionado informações sobre etapas de compilação e mais informação sobre o Package/xxx/install; Adicionado o PKG_INSTALL para evitar chamada dentro  do Package/xxx/install.

quinta-feira, 19 de março de 2015

OpenWRT: Criando novos pacotes - introdução

Mais um artigo da série sobre o OpenWRT.

Para os que acompanha este blog, fica claro o grande espectro de possibilidades que se abrem ao trocar a firmware do seu roteador. Você transforma um sistema tipo "caixa preta", fechado, limitado, e normalmente muito mal feito, em uma distribuição Linux aberta com toda uma gama de programas para aumentar o potencial do seu equipamento.

Tudo muito bonito, tudo muito bem e então você pensa que seria muito legal rodar aquele programa xyz (nome hipotético) no seu roteador. Ele é leve, feito em C e já funciona em Linux. Opa deve funcionar no OpenWRT (que é Linux). Provavelmente sim. Então, instalar novos programas já sabemos fazer...

root@router:~# opkg update
root@router:~# opkg install xyz...
Unknown package 'xyz'.
Collected errors:
 * opkg_install_cmd: Cannot install package xyz.

Pacote xyz desconhecido? Sim, ninguém empacotou este programa. Apesar do repositório do OpenWRT constar com milhares de pacotes, muitos programas nunca foram empacotados para o OpenWRT. O que fazer se você realmente queria o xyz?

Que tal um "faça você mesmo"? Esta é a temática deste artigo.

É recomendável que você tenha compilado algum programa UNIX a partir dos fontes. Caso contrário, alguns passos intermediários importantes do que será visto aqui pode passar batido.

Compilar programas a partir do fonte no UNIX, normalmente passa por estes passos:
  1. Você baixa os fontes e descompacta;
    1. Deve existir um arquivo texto documentando o que você deve fazer (ou não);
  2. Na maioria dos casos, você vai encontrar um arquivo configure nos fontes;
    1. Se não existir, normalmente ele é criado com o autoconf (ou não);
  3. Você roda o ./configure. Ele verifica seu ambiente buscando pelos programas necessários (como o compilador) e bibliotecas de desenvolvimento necessárias. Se tudo for encontrado, ele gera uma configuração e cria o Makefile;
  4. Você roda o "make" (que lê o Makefile). O programa é compilado;
  5. Se tudo der certo, você pode rodar o "make install" para instalar seu programa. Programa instalado!
Só isto? Sim, se tudo der certo. Isto significa que o ./configure vai achar todas as bibliotecas, que as opções padrão do configure são o que você quer (e precisar de alguma alteração é normal), que não existir algum problema no código manifestado somente no seu ambiente, etc... A chance de falha é grande. Além disto, existe uma miríade de variações destes passos, cortando etapas, adicionando extra ou mesmo trocando ferramentas, como usar o cmake ou escrever o Makefile manualmente (bem comum). Só a documentação pode esclarecer. Se quiser realmente aprender como compilar programas a partir do fonte, não é a intenção deste artigo (e o google é seu amigo).

OK, então eu baixo o fonte no roteador e rodo os comandos lá e... não! O seu roteador, salvo raras exceções, possui recursos limitados. Por este motivo, nem existem pacotes de desenvolvimento para instalar em um OpenWRT. Você precisa fazer uma compilação cruzada.

A compilação cruzada (cross compiling) utiliza um sistema distinto do sistema alvo (target) para compilar os programas. Como exemplo, você pode usar seu Mac OSX com processador Intel como sistema hospedeiro (host) para gerar programas para um Linux OpenWRT (target) que utiliza processador MIPS. No caso do OpenWRT, recomendo como sistema host o Linux.

E claro, ao introduzir mais esta complexidade ao processo de compilação, uma nova família de problemas pode aparecer. Nem todos os programadores pensam que um dia o seu programa será compilado para um sistema não Intel, onde o bit mais significativo é diferente. Nem todo configure está pronto para fazer a configuração da compilação cruzada e, fora isto, todas as bibliotecas e ferramentas para o processo devem ser compiladas para o sistema host compilar para o sistema alvo. Você não vai conseguir usar as bibliotecas e ferramentas já existentes no seu ambiente. Para um programa que nunca sofreu uma compilação cruzada, é normal falhar na primeira tentativa.

Mas temos boas notícias! O OpenWRT é todo preparado para a compilação cruzada, mesmo quando a arquitetura hospedeira e alvo são a mesma. Usar esta infraestrutura simplifica em muito a tarefa de ajustar o ambiente. Para usufruir deste ambiente, o mais fácil é montar um pacote mesmo que você deseje somente compilar e não empacotar um programa.

No próximo artigo, mostro como realizar o empacotamento de um comando exemplo qualquer. Será só um exemplo pois cada empacotamento é único. Pode não dar qualquer problema ou chegar ao ponto de exigir alteração profunda no código fonte do programa. Espero que seja o primeiro caso ;-)

Se precisar de uma ajuda, crie um tópico no fórum do blog ou no tópico deste artigo.
Até a próxima.