Esta seção explica algumas das razões e detalhes técnicos por trás do método completo de construção. Não tente imediatamente entender tudo nesta seção. A maior parte desta informação ficará mais clara depois de realizar uma construção real. Volte e releia este capítulo a qualquer tempo durante o processo de construção.
O objetivo geral do Capítulo 5 e do Capítulo 6 é o de produzir uma área temporária contendo um conjunto de ferramentas que sejam conhecidas por serem boas e que estejam isoladas do sistema anfitrião. Usando-se o comando chroot, as compilações nos capítulos subsequentes estarão isoladas naquele ambiente, assegurando uma construção limpa e livre de problemas do sistema LFS alvo. O processo de construção foi projetado para minimizar os riscos para novos(as) leitores(as) e para fornecer o maior valor educacional ao mesmo tempo.
O processo de construção é baseado em compilação cruzada. Compilação cruzada normalmente é usada para construir um compilador e o conjunto de ferramentas associadas dele para uma máquina diferente daquela que é usada para a construção. Isso não é estritamente necessário para o LFS, pois a máquina onde o novo sistema executará é a mesma que aquela usada para a construção. Porém, a compilação cruzada tem a grande vantagem: tudo o que for compilado cruzadamente não pode depender do ambiente do anfitrião.
O livro LFS não é (e não contém) um tutorial geral para construir um conjunto cruzado de ferramentas (ou nativo). Não use os comandos no livro para um conjunto cruzado de ferramentas para algum outro propósito que não o de construir o LFS, a menos que você realmente entenda o que está fazendo.
É sabido que instalar o GCC passagem 2 quebrará o conjunto cruzado de ferramentas. Nós não consideramos isso um defeito porque o GCC passagem 2 é o último pacote a ser compilado cruzadamente no livro, e não o “consertaremos” até que realmente precisemos compilar cruzadamente algum pacote depois do GCC passagem 2 no futuro.
Compilação cruzada envolve alguns conceitos que merecem uma seção por si próprios. Embora esta seção possivelmente seja omitida em uma primeira leitura, retornar até ela posteriormente te ajudará a ganhar um entendimento mais completo do processo.
Permita-nos primeiro definir alguns termos usados nesse contexto.
é a máquina onde nós construímos programas. Observe que essa máquina também é referenciada como a “anfitriã.”
é a máquina/sistema onde os programas construídos executarão. Observe que esse uso de “host” não é o mesmo que em outras seções.
é usado somente para compiladores. Ele é a máquina para a qual o compilador produz código. Ele possivelmente seja diferente tanto da construtora quanto da anfitriã.
Como um exemplo, permita-nos imaginar o seguinte cenário (ocasionalmente referenciado como “Cruzado Canadense”). Nós temos um compilador somente em uma máquina lenta, vamos chamá-la de máquina A, e o compilador de ccA. Nós também temos uma máquina rápida (B), porém nenhum compilador para (B), e nós queremos produzir código para uma terceira, máquina lenta (C). Nós construiremos um compilador para a máquina C em três estágios.
Estágio | Construtora | Anfitriã | Alvo | Ação |
---|---|---|---|---|
1 | A | A | B | Construir compilador cruzado cc1 usando ccA na máquina A. |
2 | A | B | C | Construir compilador cruzado cc2 usando cc1 na máquina A. |
3 | B | C | C | Construir compilador ccC usando cc2 na máquina B. |
Então, todos os programas necessários para a máquina C podem ser compilados usando cc2 na rápida máquina B. Observe que a menos que B consiga executar programas produzidos por C, não existe maneira de testar os programas recém construídos até que a própria máquina C esteja executando. Por exemplo, para executar uma suíte de teste em ccC, nós possivelmente queiramos adicionar um quarto estágio:
Estágio | Construtora | Anfitriã | Alvo | Ação |
---|---|---|---|---|
4 | C | C | C | Reconstruir e testar ccC usando ccC na máquina C. |
No exemplo acima, somente cc1 e cc2 são compiladores cruzados, isto é, eles produzem código para uma máquina diferente daquela na qual estão executando. Os outros compiladores, ccA e ccC, produzem código para a máquina na qual estão executando. Tais compiladores são chamados de compiladores nativos.
Todos os pacotes compilados cruzadamente neste livro usam um sistema de construção baseado no autoconf. O sistema de construção baseado no autoconf aceita tipos de sistema na forma cpu-vendor-kernel-os, referenciado como o tripleto do sistema. Dado que o campo vendor frequentemente é irrelevante, o autoconf te permite omiti-lo.
Um(a) leitor(a) astuto(a) possivelmente questione porque um
“tripleto”
se refere a um nome de quatro componentes. O campo kernel e o
campo os iniciaram como um campo único do “sistema”. Tal forma de
três campos ainda é válida atualmente para alguns sistemas, por
exemplo, x86_64-unknown-freebsd
.
Porém, dois sistemas conseguem compartilhar o mesmo núcleo e
ainda serem muito diferentes para usarem o mesmo tripleto para
descrevê-los. Por exemplo, o Android executando em um telefone
móvel é completamente diferente do Ubuntu executando em um
servidor ARM64, apesar de ambos estarem executando no mesmo tipo
de CPU (ARM64) e usando o mesmo núcleo (Linux).
Sem uma camada de emulação, você não consegue executar um
executável para um servidor em um telefone móvel ou vice versa.
Assim, o campo “system” foi dividido nos campos kernel e os
para designar esses sistemas inequivocamente. No nosso exemplo, O
sistema Android é designado como aarch64-unknown-linux-android
e o sistema Ubuntu
é designado como aarch64-unknown-linux-gnu
.
A palavra “tripleto” permanece embutida no léxico. Uma
maneira simples para se determinar o teu tripleto do sistema é a
de executar o conjunto de comandos sequenciais config.guess que vem com o
fonte para muitos pacotes. Desempacote os fontes do binutils,
execute o conjunto de comandos sequenciais ./config.guess
e observe a
saída gerada. Por exemplo, para um processador Intel de 32 bits,
a saída gerada será i686-pc-linux-gnu. Em um sistema de 64
bits, será x86_64-pc-linux-gnu. Na maioria dos
sistemas Linux, o comando ainda mais simples gcc -dumpmachine te dará
informação semelhante.
Você também deveria estar ciente do nome do vinculador dinâmico
da plataforma, frequentemente referido como o carregador dinâmico
(não seja confundido com o vinculador padrão ld que é parte do binutils). O
vinculador dinâmico fornecido pelo pacote glibc encontra e
carrega as bibliotecas compartilhadas necessárias para um
programa, prepara o programa para execução e então o executa. O
nome do vinculador dinâmico para uma máquina Intel de 32 bits é
ld-linux.so.2
; e é ld-linux-x86-64.so.2
em sistemas de 64 bits.
Uma maneira infalível para se determinar o nome do vinculador
dinâmico é a de inspecionar uma biblioteca aleatória oriunda do
sistema anfitrião executando: readelf -l <nome do binário> | grep
interpreter
e observar a saída gerada. A
referência autoritativa cobrindo todas as plataformas está em
uma página wiki da
Glibc.
Existem dois pontos-chave para uma compilação cruzada:
Ao produzir e processar o código de máquina supostamente para ser executado na “anfitriã”, o conjunto cruzado de ferramentas precisa ser usado. Observe que o conjunto nativo de ferramentas originário da “construtora” ainda pode ser invocado para gerar código de máquina supostamente para ser executado na “construtora”. Por exemplo, o sistema de construção pode compilar um gerador com o conjunto nativo de ferramentas, então gerar um arquivo fonte C com o gerador, e finalmente compilar o arquivo fonte C com o conjunto cruzado de ferramentas, de forma que o código gerado estará apto para executar na “anfitriã.”
Com um sistema de construção baseado em autoconf, esse
requisito é garantido usando-se a chave --host
para se especificar o
tripleto da “anfitriã”. Com essa chave, o sistema de
construção usará os componentes do conjunto de ferramentas
prefixados com
para gerar e processar o
código de máquina para “a anfitriã”; por exemplo, o compilador
será <the host
triplet>
<the host
triplet>
-gcc e a ferramenta
readelf será
<the host
triplet>
-readelf.
O sistema de construção não deveria tentar executar nenhum
código de máquina gerado supostamente para ser executado na
“anfitriã.” Por exemplo, ao construir um
utilitário nativamente, a página de manual dele pode ser
gerada executando-se o utilitário com a chave --help
e processando-se a saída
gerada, mas geralmente não é possível fazer isso para uma
compilação cruzada, pois o utilitário possivelmente falhe
para executar na “construtora”: obviamente é impossível
executar código de máquina ARM64 em uma CPU x86 (sem um
emulador).
Com um sistema de construção baseado em autoconf, esse
requisito é satisfeito no “modo de compilação cruzada”, onde os
recursos opcionais que exigem executar código de máquina para
“a
anfitriã” durante o tempo de construção são
desabilitados. Quando o tripleto da “anfitriã” for
especificado explicitamente, o “modo de compilação
cruzada” é habilitado se e somente se o
conjunto de comandos sequenciais configure falhar para
executar um programa fictício compilado no código de máquina
da “anfitriã” ou o tripleto da “construtora” for
especificado explicitamente por meio da chave --build
e ele for diferente do
tripleto da “anfitriã”.
Para a finalidade de compilar cruzadamente um pacote para o sistema
temporário LFS, o nome do tripleto do sistema é ligeiramente
ajustado mudando-se o campo "vendor" na variável LFS_TGT
, de forma que ele diga "lfs" e LFS_TGT
é então especificada como o tripleto
“da anfitriã”
via --host
, de forma que o
conjunto cruzado de ferramentas será usado para gerar e processar o
código de máquina executando como parte do sistema temporário LFS.
E também nós precisamos habilitar “o modo de compilação cruzada”: apesar do
código de máquina da “anfitriã”, ou seja, o código de máquina para
o sistema temporário LFS, estar apto para executar na CPU real, ele
possivelmente se refira a uma biblioteca não disponível na
“construtora”
(a distribuição da anfitriã), ou algum código ou dados não existir
ou ser definido diferentemente na biblioteca, mesmo se acontecer de
estar disponível. Ao compilar cruzadamente um pacote para o sistema
temporário LFS, nós não podemos confiar no conjunto de comandos
sequenciais configure
para detectar esse problema com o programa fictício: o fictício usa
somente alguns componentes na libc
que a libc
da distribuição da
anfitriã provavelmente fornece (a menos que, talvez, a distribuição
da anfitriã use uma implementação da libc
diferente, como Musl), de forma que ele
não falhará como os programas realmente úteis provavelmente
falhariam. Portanto, nós precisamos especificar explicitamente o
tripleto da “construtora” para habilitar “o modo de compilação
cruzada.” O valor que nós usamos é exatamente o
padrão, ou seja, o tripleto original do sistema proveniente da
saída gerada do config.guess, mas “o modo de compilação
cruzada” depende de uma especificação explícita, como
nós discutimos.
Nós usamos a opção --with-sysroot
ao construir o
vinculador cruzado e o compilador cruzado, para dizer a eles onde
encontrar os arquivos necessários para “a anfitriã.” Isso quase
garante que nenhum dos outros programas construídos no Capítulo 6
consiga vincular-se a bibliotecas na “construtora.” A palavra
“quase” é
usada porque libtool,
um envolucrador de “compatibilidade” do compilador e do
vinculador para sistemas de construção baseados em autoconf, pode
tentar ser muito inteligente e passar erroneamente opções
permitindo que o vinculador encontre bibliotecas da “construtora.” Para
evitar essa precipitação, nós precisamos deletar os arquivos de
arquivamento da libtool (.la
) e
consertar uma cópia desatualizada da libtool enviada com o código
do Binutils.
Estágio | Construtora | Anfitriã | Alvo | Ação |
---|---|---|---|---|
1 | pc | pc | lfs | Construir compilador cruzado cc1 usando cc-pc em pc. |
2 | pc | lfs | lfs | Construir compilador cc-lfs usando cc1 em pc. |
3 | lfs | lfs | lfs | Reconstrua (e talvez teste) cc-lfs usando cc-lfs no lfs. |
Na tabela precedente, “em pc” significa que os comandos são executados em uma máquina usando a distribuição já instalada. “Em lfs” significa que os comandos são executados em um ambiente chroot.
Esse não é ainda o fim da estória. A linguagem C não é meramente um compilador; ela também define uma biblioteca padrão. Neste livro, a biblioteca GNU C, chamada de glibc, é usada (existe uma alternativa, "musl"). Essa biblioteca precisa ser compilada para a máquina LFS; isto é, usando o compilador cruzado cc1. Porém, o próprio compilador usa uma biblioteca interna fornecendo sub rotinas complexas para funções não disponíveis no conjunto de instruções do montador. Essa biblioteca interna é chamada de libgcc e ela precisa ser lincada à biblioteca glibc para ser completamente funcional. Além disso, a biblioteca padrão para C++ (libstdc++) também precisa estar lincada com a glibc. A solução para esse problema de ovo e galinha é a de primeiro se construir uma libgcc degradada baseada em cc1, carecendo de algumas funcionalidades, tais como camadas e manuseio de exceções, e então se construir a glibc usando esse compilador degradado (a glibc em si não é degradada), e também se construir a libstdc++. Essa última biblioteca carecerá de algumas das funcionalidades da libgcc.
O resultado do parágrafo precedente é o de que cc1 está inapto para construir uma libstdc++ totalmente funcional com a libgcc degradada, mas cc1 é o único compilador disponível para se construir as bibliotecas C/C++ durante o estágio 2. Conforme nós discutimos, não podemos executar cc-lfs na pc (a distribuição da anfitriã) porque ele possivelmente exija alguma biblioteca, código ou dados não disponível na “construtora” (a distribuição da anfitriã). Então, quando nós construímos o estágio 2 do gcc, substituímos o caminho de pesquisa da biblioteca para lincar libstdc++ à libgcc recém-reconstruída em vez da construção antiga e degradada. Isso torna a libstdc++ reconstruída totalmente funcional.
No Capítulo 8 (ou “estágio 3”), todos os pacotes necessários para o sistema LFS são construídos. Mesmo se um pacote já tiver sido instalado no sistema LFS em um capítulo anterior, nós ainda reconstruímos o pacote. O principal motivo para se reconstruir esses pacotes é para torná-los estáveis: se reinstalarmos um pacote do LFS em um sistema LFS concluído, o conteúdo reinstalado do pacote deveria ser o mesmo que o conteúdo do mesmo pacote quando instalado pela primeira vez no Capítulo 8. Os pacotes temporários instalados no Capítulo 6 ou no Capítulo 7 não conseguem satisfazer essa exigência, porque alguns recursos opcionais deles são desabilitados devido ou às dependências ausentes ou ao “modo de compilação cruzada.” Além disso, um motivo secundário para se reconstruir os pacotes é para executar as suítes de teste.
O compilador cruzado será instalado em um diretório $LFS/tools
separado, pois ele não será parte do
sistema final.
Binutils é instalado primeiro, pois as execuções do configure do gcc e da glibc realizam vários testes de recursos no montador e no lincador para determinar quais recursos de software habilitar ou desabilitar. Isso é mais importante do que, inicialmente, alguém possa perceber. Um gcc ou uma glibc configurado incorretamente pode resultar em um conjunto de ferramentas sutilmente quebrado, onde o impacto de tal quebra talvez não se manifeste até próximo do final da construção de uma distribuição inteira. Uma falha de suíte de teste normalmente destacará tal erro antes que muito mais trabalho adicional seja realizado.
O Binutils instala o montador e o lincador dele em dois locais,
$LFS/tools/bin
e $LFS/tools/$LFS_TGT/bin
. As ferramentas em um
local são rigidamente lincadas às outras. Uma faceta importante do
lincador é a ordem dele de procura de biblioteca. Informação
detalhada pode ser obtida a partir do ld passando-lhe o sinalizador
--verbose
. Por exemplo,
$LFS_TGT-ld --verbose | grep
SEARCH ilustrará os caminhos atuais de procura e a
ordem deles. (Observe que esse exemplo consegue ser executado
conforme mostrado somente enquanto logado(a) como usuário(a)
lfs
. Se você retornar a esta página
posteriormente, substitua $LFS_TGT-ld por ld).
O próximo pacote instalado é gcc. Um exemplo do que pode ser visto durante a execução dele do configure é:
checking what assembler to use... /mnt/lfs/tools/i686-lfs-linux-gnu/bin/as
checking what linker to use... /mnt/lfs/tools/i686-lfs-linux-gnu/bin/ld
Isso é importante pelas razões mencionadas acima. Também demonstra que o conjunto de comandos sequenciais de configuração do gcc não procura nos diretórios do PATH para encontrar quais ferramentas usar. Entretanto, durante a operação real do gcc em si, os mesmos caminhos de procura não são necessariamente usados. Para descobrir qual lincador padrão o gcc usará, execute: $LFS_TGT-gcc -print-prog-name=ld. (Novamente, remova o prefixo $LFS_TGT- se retornar a isso posteriormente.)
Informação detalhada pode ser obtida a partir do gcc passando-se a opção de linha
de comando -v
enquanto
compilar um programa. Por exemplo, $LFS_TGT-gcc -v example.c
(ou sem
$LFS_TGT- se retornar
posteriormente) exibirá informação detalhada acerca do
preprocessador, compilação e estágios da montagem, incluindo os
caminhos de procura do gcc para cabeçalhos inclusos e a
ordem deles.
Em seguida: cabeçalhos sanitizados da API do Linux. Eles permitem que a biblioteca C padrão (glibc) interaja com os recursos que o núcleo Linux fornecerá.
Em seguida vem glibc. Esse é o primeiro pacote que nós compilamos
cruzadamente. Nós usamos a opção --host=$LFS_TGT
para fazer o sistema
de construção usar aquelas ferramentas prefixadas com $LFS_TGT-
, e a opção --build=$(../scripts/config.guess)
para habilitar “o modo de
compilação cruzada” como discutimos. A variável
DESTDIR
é usada para forçar a instalação
no sistema de arquivos do LFS.
Conforme mencionado acima, a biblioteca padrão C++ é compilada em seguida, seguida no Capítulo 6 por outros programas que precisam ser compilados cruzadamente para quebrar dependências circulares ao tempo da construção. As etapas para aqueles pacotes são semelhantes às etapas para glibc.
No final do Capítulo 6
o compilador nativo do LFS é instalado. Primeiro binutils-pass2 é
construído, no mesmo diretório DESTDIR
que os outros programas, então a segunda passagem de gcc é
construída, omitindo-se algumas bibliotecas não críticas.
Uma vez dentro do ambiente chroot no Capítulo 7, as instalações temporárias de programas necessários para a operação apropriada do conjunto de ferramentas são realizadas. Deste ponto em diante, o conjunto central de ferramentas está auto-contido e auto-hospedado. No Capítulo 8, as versões finais de todos os pacotes necessários para um sistema completamente funcional são construídas, testadas e instaladas.