A polêmica sobre toolchains e glibc

Glibc Dynamic Loader atingido por uma vulnerabilidade de escalonamento de privilégios locais

Afinal, vale a pena consertar o glibc (e outros projetos que suportam apenas GCC) para compilar com LLVM? É melhor apenas substituí-los por alternativas que já suportam LLVM? É melhor usar o GCC e o LLVM, cada um para seus respectivos projetos com suporte?

Estas são algumas das perguntas a serem respondidas por Adrian Ratiu, da Collabora neste artigo que você confere a seguir. Ele vai ressaltar que, nos últimos anos, a cadeia de ferramentas LLVM tem visto um crescente desenvolvimento e adoção junto com a cadeia de ferramentas GNU mais antiga e mais estável. O surgimento deste novo mundo de duas grandes cadeias de ferramentas está trazendo desafios e questões para projetos que precisam oferecer suporte a ambos, em particular a biblioteca GNU C (glibc), que suporta apenas GCC.

Esta postagem é uma exploração a partir dessas perguntas, mas não tenta dar respostas definitivas. A intenção aqui não é causar divisões e controvérsias, mas aumentar a conscientização, descrevendo partes do status quo atual e encorajar a colaboração. O elefante óbvio na sala, o licenciamento, é deixado de fora, apesar de ser um tópico muito importante.

A carga de suporte do conjunto de ferramentas

Distribuições que usam ou permitem que LLVM/Clang seja usado como compilador principal (Yocto / OE, Gentoo / ChromeOS, OpenMandriva, Alpine e outros, talvez até Android possa ser contado aqui) são forçadas a usar libcs ??diferentes ou manter um conjunto de ferramentas GCC apenas por construir um pequeno subconjunto de pacotes no centro do qual é tipicamente glibc. Isso não é ideal, considerando a necessidade de correções de bugs e suporte a novos recursos de HW, ambos conjuntos de ferramentas que requerem manutenção ativa.

Descartar o GCC e fazer o downstream de patches em projetos individuais para suportar o Clang é difícil porque os patches quebram nas atualizações (a menos que você bifurque e nunca volte a fundir) e pode introduzir bugs ou regressões, especialmente no caso de glibc onde o diff é grande e complexo. Pequenos patches como configuração, correções de compilação cruzada ou backports de correção de bugs podem ser aceitáveis ??no downstream, mas manter algo como suporte LLVM no downstream é inviável, portanto, o suporte de primeira classe e testes cuidadosos para evitar regressões devem acontecer idealmente no projeto upstream.

O problema glibc

Ao construir um sistema com LLVM/Clang, o problema mais comum é a biblioteca GNU C, que é amplamente usada e confiável. A biblioteca C em geral é parte da definição do sistema operacional básico e alguns outros projetos importantes como o systemd só podem ser construídos especificamente com a implementação GNU da lib padrão. Glibc é provavelmente o mais problemático para construir com LLVM / Clang (pelo menos dos projetos GNU) devido à sua dependência de extensões GNU toolchain ou comportamentos GCC não padrão específicos ou bugs. Isso é compreensível, considerando a idade e o propósito do projeto.

O Glibc construído pelo LLVM deve ser alcançável se considerarmos o esforço bem-sucedido para adicionar suporte Clang de primeira classe ao kernel do Linux, que também é um projeto complexo e historicamente vinculado ao GCC. O problema é: a que custo e quando o custo do glibc + LLVM excede o tempo e esforço necessários para usar um libc diferente ou manter um conjunto de ferramentas GCC apenas para glibc? Pelo menos em teoria, trocar as bibliotecas C deve ser muito mais fácil do que trocar o Linux por outro kernel, então podemos assumir que o custo é menor. O mesmo vale para manter o GCC por perto.

Existem alguns ramos “clangify” na glibc upstream (por exemplo shebs/clangify), mas eles são baseados nas versões 2.26-2.29 e o desenvolvimento parece ter parado nos últimos anos.

Comportamentos divergentes de conjuntos de ferramentas

Às vezes, o Clang e o GCC podem funcionar como substitutos imediatos um do outro, mas nem sempre é esse o caso. Exemplos óbvios são os diferentes sinalizadores de construção ou extensões que requerem sinalizadores de ativação do Clang especiais. Um exemplo mais sutil é que o Clang “afirma” suportar -std = gnu99, embora não ofereça suporte a funções aninhadas que fazem parte do gnu99. As coisas ficam complicadas quando não há um padrão explícito para governar um comportamento e os projetos podem depender do que quer que aconteça para funcionar em uma cadeia de ferramentas, normalmente o GCC. Por exemplo. o GNU assembler é mais permissivo do que o Clang assembler que, entre outras coisas, rejeita instruções ambíguas . Pode não estar totalmente claro em qual projeto está o “bug”.

Código de suporte do projeto

Devido às diferenças entre LLVM e GCC como acima, vários projetos acabam tendo que #ifdef código específico da cadeia de ferramentas, que pode ser uma fonte de bugs, além de tornar o código mais feio em geral. Isso é muito bem relatado em uma apresentação do desenvolvedor do OpenMandriva Bernhard Rosenkränzer na EuroLLVM 2019. Os exemplos são tristes e cômicos ao mesmo tempo. Como contexto para o exemplo a seguir, o Clang define __GNUC__ == 4.

#if __GNUC__> = 5
do_something_sane ();
#outro
assume_the_compiler_sucks ();
#fim se

Complexidade da base de código do projeto

Como muitos outros projetos com uma longa história, a base de código glibc também é complexa e isso pode se tornar um impedimento para contribuições que desejam consertar o suporte do Clang. Para um exemplo específico, consulte este patch de 2014 que tentou substituir as declarações de funções aninhadas em regcomp.c. Esse arquivo fonte específico é compartilhado com o gnulib, o que significa que requer duas rodadas de revisão dos respectivos projetos e, infelizmente, os arquivos estavam fora de sincronia entre os projetos, então o patch foi NAK’ed até que os fontes pudessem ser colocados em sincronia. O autor do patch decidiu trabalhar em outra coisa e, portanto, essa declaração de função aninhada específica permanece sem correção até hoje.

O progresso é feito

Mesmo sem um grande esforço concentrado como o do kernel do Linux, o suporte LLVM / Clang está melhorando lentamente na glibc como resultado de um tipo de movimento “popular”. Um recente esforço admirável de persistência de Fangrui Song adicionou suporte para vincular Glibc com LLD, o linker LLVM, começando com o LLVM 13 e o glibc 2.35 que serão lançados em alguns meses. Fangrui Song também escreveu uma postagem no blog descrevendo sua experiência, que é uma leitura muito boa para qualquer pessoa interessada no assunto.

Glibc + LLVM vale a pena?

Tudo é possível no futuro. O que é certo é que a adoção do LLVM está crescendo tanto no FOSS quanto na indústria de software em geral e há alternativas para projetos que estão muito vinculados às especificações do GCC, então não é surpresa que escolhas sejam feitas para substituí-los.

Por exemplo, elfutils, que é uma alternativa estabelecida ao GNU binutils, também oferecerá suporte à construção com o Clang em sua próxima versão (v0.186). Está faltando um linker, mas pode ser fornecido pelo LLVM Core. O próprio LLVM Core fornece alternativas ao GNU binutils enquanto, ao mesmo tempo, trabalha para melhorar a compatibilidade do GNU binutils e adicionar recursos ausentes.

Existem algumas implementações de biblioteca padrão C que não são tão dependentes do GCC quanto a glibc. Há musl que é (talvez muito estritamente) compatível com os padrões, um bom número de libcs ??menores com foco embutido como newlib ou uClibc, há também bionic que visa principalmente a suportar Android, há uma libc escrita em Rust e até mesmo o projeto LLVM está criando sua própria biblioteca C padrão. Dadas tantas alternativas, não deve ser surpresa que o trabalho de suporte do LLVM/Clang seja escasso no glibc.

Um problema que as bibliotecas C alternativas têm é que o desenvolvimento de recursos ocorre principalmente para glibc, com fornecedores de HW visando compreensivelmente a biblioteca C mais popular e amplamente usada.

Conclusão

Estamos vivendo em tempos interessantes nesta área da pilha de software, após tantos anos de domínio do conjunto de ferramentas GCC. Ter que escolher entre projetos fundamentais de alta qualidade é incrível. O LLVM/Clang mais recente está se desenvolvendo para se tornar uma alternativa completa, mais focada em conformidade com os padrões, mas obviamente o GCC continuará a ser muito popular e não irá embora tão cedo. A biblioteca padrão do LLVM C em particular deve levar muito tempo antes de ficar pronta para produção.

Em um mundo dominado por duas cadeias de ferramentas, os projetos estão adicionando suporte para o LLVM emergente e, pelo menos por agora, fornecedores de distribuição e integradores de sistemas (geralmente incorporados) acabam mantendo ambas as cadeias de ferramentas ou encontrando alternativas para projetos incompatíveis com cadeias de ferramentas com diferentes negócios -offs.

Pode-se argumentar que talvez a própria linguagem C esteja se tornando menos relevante e alternativas como Rust, onde a situação de suporte GCC vs LLVM está atualmente revertida, se tornariam o que é considerado uma cadeia de ferramentas “padrão”, se, por exemplo, a biblioteca Rust std for feita libc independente porque atualmente a libc (musl /glibc) é a interface do SO para a lib std Rust. No entanto, isso é altamente especulativo e está em um futuro distante – é mais provável que tenhamos glibc com suporte LLVM ou LLVM libc até então. 🙂