Monitoramento de ABI do kernel do Android

Você pode usar as ferramentas de monitoramento de interface binária (ABI) do aplicativo, disponíveis no Android 11 e superior, para estabilizar a ABI no kernel dos kernels do Android. As ferramentas coletam e comparam representações ABI de binários de kernel existentes ( vmlinux + módulos). Essas representações ABI são os arquivos .xml e as listas de símbolos. A interface na qual a representação fornece uma visão é chamada de Kernel Module Interfaces (KMIs). Você pode usar as ferramentas para rastrear e mitigar as alterações no KMI.

A ferramenta de monitoramento ABI é desenvolvida em AOSP e usa libabigail para gerar e comparar representações.

Esta página descreve as ferramentas, o processo de coleta e análise de representações de ABI e o uso de tais representações para fornecer estabilidade à ABI no kernel. Esta página também fornece informações para contribuir com alterações nos kernels do Android.

Este diretório contém as ferramentas específicas para a análise ABI. Use-o com os scripts de compilação fornecidos por build_abi.sh .)

Processo

A análise da ABI do kernel envolve várias etapas, a maioria das quais pode ser automatizada:

  1. Adquira a cadeia de ferramentas, construa scripts e fontes de kernel por meio de repo .
  2. Forneça quaisquer pré-requisitos (como a biblioteca libabigail e a coleção de ferramentas).
  3. Construa o kernel e sua representação ABI .
  4. Analise as diferenças de ABI entre a compilação e uma referência .
  5. Atualize a representação ABI (se necessário) .
  6. Trabalhe com listas de símbolos .

As instruções a seguir funcionam para qualquer kernel que você possa compilar usando uma cadeia de ferramentas compatível (como a cadeia de ferramentas Clang pré-compilada). Os repo manifests de repositório estão disponíveis para todas as ramificações de kernel comuns do Android e para vários kernels específicos do dispositivo, eles garantem que a cadeia de ferramentas correta seja usada ao criar uma distribuição de kernel para análise.

Use as ferramentas de monitoramento ABI

1. Adquira a cadeia de ferramentas, crie scripts e fontes do kernel por meio do repositório

Você pode adquirir a cadeia de ferramentas, construir scripts (esses scripts) e fontes do kernel com repo . Para obter documentação detalhada, consulte as informações correspondentes para compilar kernels do Android .

Para ilustrar o processo, as etapas a seguir usam common-android12-5.10 , uma ramificação do kernel do Android, que é o kernel GKI mais recente lançado no momento da redação deste artigo. Para obter essa ramificação por meio de repo , execute o seguinte:

repo init -u https://android.googlesource.com/kernel/manifest -b common-android12-5.10
repo sync

2. Forneça pré-requisitos

O conjunto de ferramentas ABI usa libabigail , uma biblioteca e uma coleção de ferramentas, para analisar binários. Um conjunto adequado de binários pré-construídos vem com o kernel-build-tools e é usado automaticamente com build_abi.sh .

Para utilizar as ferramentas de nível inferior (como dump_abi ), adicione as ferramentas kernel-build- ao PATH .

3. Construa o kernel e sua representação ABI

Neste ponto, você está pronto para construir um kernel com a cadeia de ferramentas correta e extrair uma representação ABI de seus binários ( vmlinux + módulos).

Semelhante ao processo normal de compilação do kernel do Android (usando build.sh ), esta etapa requer a execução de build_abi.sh .

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

Isso cria o kernel e extrai a representação ABI no subdiretório out_abi . Neste caso out/android12-5.10/dist/abi.xml é um link simbólico para out_abi/android12-5.10/dist/abi-<id>.xml . < id> é calculado executando git describe na árvore de origem do kernel.

4. Analise as diferenças de ABI entre a construção e uma representação de referência

build_abi.sh analisa e relata quaisquer diferenças de ABI quando uma referência é fornecida por meio da variável de ambiente ABI_DEFINITION . ABI_DEFINITION deve apontar para um arquivo de referência relativo à árvore de origem do kernel e pode ser especificado na linha de comando ou, mais comumente, como um valor em build.config . O seguinte fornece um exemplo:

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

No comando acima, build.config.gki.aarch64 define o arquivo de referência (como ABI_DEFINITION=android/abi_gki_aarch64.xml ), e diff_abi chama abidiff para comparar a representação ABI recém-gerada com o arquivo de referência. build_abi.sh imprime a localização do relatório e emite um breve relatório para qualquer quebra de ABI. Se forem detectadas quebras, build_abi.sh termina e retorna um código de saída diferente de zero.

5. Atualize a representação da ABI (se necessário)

Para atualizar a representação ABI, invoque build_abi.sh com o sinalizador --update . Ele atualiza o arquivo abi.xml correspondente definido por build.config . Para imprimir as diferenças de ABI devido à atualização, invoque o script com --print-report . Certifique-se de incluir o relatório na mensagem de confirmação ao atualizar o arquivo abi.xml .

6. Trabalhe com listas de símbolos

Parametrize build_abi.sh com listas de símbolos KMI para filtrar símbolos durante a extração de ABI. Estes são arquivos de texto simples que listam os símbolos relevantes do kernel ABI. Por exemplo, um arquivo de lista de símbolos com o conteúdo a seguir limita a análise ABI aos símbolos ELF com os nomes symbol1 e symbol2 :

[abi_symbol_list]
   symbol1
   symbol2

Alterações em outros símbolos ELF não são consideradas. Um arquivo de lista de símbolos pode ser especificado no arquivo de configuração build.config correspondente com KMI_SYMBOL_LIST= como um arquivo relativo ao diretório de origem do kernel ( $KERNEL_DIR ). Para fornecer um nível de organização, você pode especificar arquivos de lista de símbolos adicionais usando ADDITIONAL_KMI_SYMBOL_LISTS= no arquivo build.config . Isso especifica outros arquivos de lista de símbolos, relativos a $KERNEL_DIR ; separe vários nomes de arquivos por espaço em branco.

Para criar uma lista de símbolos inicial ou atualizar uma existente , você deve usar o script build_abi.sh com o --update-symbol-list .

Quando o script é executado com uma configuração apropriada, ele constrói o kernel e extrai os símbolos que são exportados dos módulos vmlinux e GKI e que são requeridos por qualquer outro módulo na árvore.

Considere vmlinux exportando os seguintes símbolos (geralmente feito por meio das macros EXPORT_SYMBOL* ):

  func1
  func2
  func3

Além disso, imagine que houvesse dois módulos de fornecedor, modA.ko e modB.ko , que exigem os seguintes símbolos (em outras palavras, eles listam entradas de símbolos undefined em sua tabela de símbolos):

 modA.ko:    func1 func2
 modB.ko:    func2

Do ponto de vista da estabilidade da ABI, func1 e func2 devem ser mantidos estáveis, pois são usados ​​por um módulo externo. Pelo contrário, enquanto func3 é exportado, ele não é usado ativamente (ou seja, não é necessário) por nenhum módulo. Assim, a lista de símbolos contém apenas func1 e func2 .

Para criar ou atualizar uma lista de símbolos existente, build_abi.sh deve ser executado da seguinte forma:

BUILD_CONFIG=path/to/build.config.device build/build_abi.sh --update-symbol-list

Neste exemplo, build.config.device deve incluir várias opções de configuração:

  • vmlinux deve estar na lista FILES .
  • KMI_SYMBOL_LIST deve ser definido e apontando para a lista de símbolos KMI para atualizar.
  • GKI_MODULES_LIST deve ser definido e apontando para a lista de módulos GKI. Esse caminho geralmente é android/gki_aarch64_modules .

Trabalhe com as ferramentas ABI de nível inferior

A maioria dos usuários só precisará usar build_abi.sh . Em alguns casos, pode ser necessário trabalhar diretamente com as ferramentas ABI de nível inferior. Os dois comandos usados ​​por build_abi.sh , dump_abi e diff_abi , estão disponíveis para extrair e comparar arquivos ABI. Consulte as seções a seguir para seus usos.

Crie representações ABI a partir de árvores do kernel

Fornecendo uma árvore de kernel linux com módulos vmlinux e kernel construídos, a ferramenta dump_abi cria uma representação ABI usando a ferramenta ABI selecionada. Uma invocação de amostra se parece com isso:

dump_abi --linux-tree path/to/out --out-file /path/to/abi.xml

O arquivo abi.xml contém uma representação textual ABI da ABI combinada e observável do vmlinux e dos módulos do kernel no diretório fornecido. Esse arquivo pode ser usado para inspeção manual, análise adicional ou como um arquivo de referência para reforçar a estabilidade da ABI.

Comparar representações ABI

As representações ABI criadas por dump_abi podem ser comparadas com diff_abi . Use a mesma ferramenta abi para dump_abi e diff_abi . Uma invocação de amostra se parece com isso:

diff_abi --baseline abi1.xml --new abi2.xml --report report.out

O relatório gerado lista as alterações de ABI detectadas que afetam o KMI. Os arquivos especificados como baseline e new são representações ABI que foram coletadas com dump_abi . diff_abi propaga o código de saída da ferramenta subjacente e, portanto, retorna um valor diferente de zero quando as ABIs comparadas são incompatíveis.

Filtrar representações e símbolos KMI

Para filtrar representações criadas com dump_abi ou para filtrar símbolos comparados com diff_abi , use o parâmetro --kmi-symbol-list , que leva um caminho para um arquivo de lista de símbolos KMI:

dump_abi --linux-tree path/to/out --out-file /path/to/abi.xml --kmi-symbol-list /path/to/symbol_list_file

Trabalhando com listas de símbolos

O KMI não inclui todos os símbolos no kernel ou mesmo todos os mais de 30.000 símbolos exportados. Em vez disso, os símbolos que podem ser usados ​​pelos módulos são listados explicitamente em um conjunto de arquivos de lista de símbolos mantidos publicamente na raiz da árvore do kernel. A união de todos os símbolos em todos os arquivos de lista de símbolos define o conjunto de símbolos KMI mantidos como estáveis. Um exemplo de arquivo de lista de símbolos é abi_gki_aarch64_db845c , que declara os símbolos necessários para o DragonBoard 845c .

Apenas os símbolos listados em uma lista de símbolos e suas estruturas e definições relacionadas são considerados parte do KMI. Você pode postar alterações em suas listas de símbolos se os símbolos necessários não estiverem presentes. Depois que as novas interfaces estão em uma lista de símbolos e fazem parte da descrição do KMI, elas são mantidas estáveis ​​e não devem ser removidas da lista de símbolos ou modificadas após o congelamento da ramificação.

Cada ramificação do kernel KMI do Android Common Kernel (ACK) tem seu próprio conjunto de listas de símbolos. Nenhuma tentativa é feita para fornecer estabilidade de ABI entre diferentes ramificações do kernel KMI. Por exemplo, o KMI para android12-5.10 é completamente independente do KMI para android13-5.10 .

As ferramentas ABI usam listas de símbolos KMI para limitar quais interfaces devem ser monitoradas para estabilidade. A lista de símbolos principal contém os símbolos que são requeridos pelos módulos do kernel GKI. Espera-se que os fornecedores enviem e atualizem listas de símbolos adicionais para garantir que as interfaces nas quais eles dependem mantenham a compatibilidade com ABI. Por exemplo, para ver uma lista de listas de símbolos para o android13-5.15 , consulte https://android.googlesource.com/kernel/common/+/refs/heads/android13-5.15/android

Uma lista de símbolos contém os símbolos relatados como necessários para o fornecedor ou dispositivo específico. A lista completa utilizada pelas ferramentas é a união de todos os arquivos da lista de símbolos KMI. As ferramentas ABI determinam os detalhes de cada símbolo, incluindo assinatura de função e estruturas de dados aninhadas.

Quando o KMI está congelado, nenhuma alteração é permitida nas interfaces KMI existentes; eles são estáveis. No entanto, os fornecedores são livres para adicionar símbolos ao KMI a qualquer momento, desde que as adições não afetem a estabilidade da ABI existente. Os símbolos recém-adicionados são mantidos estáveis ​​assim que são citados por uma lista de símbolos KMI. Símbolos não devem ser removidos de uma lista de um kernel a menos que possa ser confirmado que nenhum dispositivo foi enviado com uma dependência desse símbolo.

Você pode gerar uma lista de símbolos KMI para um dispositivo usando o utilitário build/abi/extract_symbols , que extrai as dependências de símbolos dos artefatos de compilação *.ko . Este utilitário adiciona anotações à saída na forma de comentários, que são úteis para identificar usuários de um símbolo. Ao enviar a lista de símbolos para ACK, é altamente recomendável manter esses comentários para facilitar o processo de revisão. Para omitir comentários, passe a --skip-module-grouping ao executar o script extract_symbols . Muitos parceiros enviam uma lista de símbolos por ACK, mas isso não é um requisito difícil. Se isso ajudar na manutenção, você pode enviar várias listas de símbolos.

Para obter ajuda com a personalização de listas de símbolos e o uso de ferramentas ABI de alto e baixo nível para depuração e análise detalhada, consulte Monitoramento de ABI para kernels do Android .

Estenda o KMI

Embora os símbolos KMI e as estruturas relacionadas sejam mantidos estáveis ​​(o que significa que alterações que quebram interfaces estáveis ​​em um kernel com um KMI congelado não podem ser aceitas), o kernel GKI permanece aberto a extensões para que os dispositivos enviados no final do ano não precisem defina todas as suas dependências antes que o KMI seja congelado. Para estender o KMI, você pode adicionar novos símbolos ao KMI para funções de kernel exportadas novas ou existentes, mesmo se o KMI estiver congelado. Novos patches de kernel também são aceitos se não quebrarem o KMI.

Sobre quebras de KMI

Um kernel tem fontes e um binário é construído com base nessas fontes. As ramificações do kernel monitoradas por ABI incluem abi.xml que é uma representação da GKI ABI atual. Depois que o binário é compilado (o binário do kernel, vmlinux , Image mais módulos do kernel), um arquivo abi.xml pode ser extraído dos binários. Qualquer alteração feita em uma fonte do kernel pode alterar o binário e também pode alterar o abi.xml extraído (o arquivo que é extraído após aplicar a alteração e construir o kernel). O analisador AbiAnalyzer compara os dois arquivos abi.xml semanticamente e define um rótulo Lint-1 na alteração se encontrar um problema.

Lidar com quebras de ABI

Como exemplo, o patch a seguir apresenta uma quebra de ABI muito óbvia:

 diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
  index 5ed8f6292a53..f2ecb34c7645 100644
  --- a/include/linux/mm_types.h
  +++ b/include/linux/mm_types.h
  @@ -339,6 +339,7 @@ struct core_state {
   struct kioctx_table;
   struct mm_struct {
      struct {
  +       int dummy;
          struct vm_area_struct *mmap;            /* list of VMAs */
          struct rb_root mm_rb;
          u64 vmacache_seqnum;                   /* per-thread vmacache */

Quando você executa o build_abi.sh novamente com este patch aplicado, o conjunto de ferramentas sai com um código de erro diferente de zero e relata uma diferença de ABI semelhante a esta:

 Leaf changes summary: 1 artifact changed
  Changed leaf types summary: 1 leaf type changed
  Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
  Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable

  'struct mm_struct at mm_types.h:372:1' changed:
    type size changed from 6848 to 6912 (in bits)
    there are data member changes:
  [...]

Diferenças de ABI detectadas em tempo de compilação

O motivo mais comum para erros é quando um driver usa um novo símbolo do kernel que não está em nenhuma das listas de símbolos.

Se o símbolo não estiver incluído na lista de símbolos ( android/abi_gki_aarch64 ), você precisará primeiro verificar se ele foi exportado com EXPORT_SYMBOL_GPL( symbol_name ) e, em seguida, atualizar a representação XML da ABI e a lista de símbolos. Por exemplo, as alterações a seguir adicionam o novo recurso Incremental FS à ramificação android-12-5.10 , que inclui a atualização da lista de símbolos e a representação XML da ABI.

Se o símbolo for exportado (por você ou foi exportado anteriormente), mas nenhum outro driver o estiver usando no momento, você poderá receber um erro de compilação semelhante ao seguinte.

Comparing the KMI and the symbol lists:
+ build/abi/compare_to_symbol_list out/$BRANCH/common/Module.symvers out/$BRANCH/common/abi_symbollist.raw
ERROR: Differences between ksymtab and symbol list detected!
Symbols missing from ksymtab:
Symbols missing from symbol list:
 - simple_strtoull

Para resolver, atualize a lista de símbolos KMI em seu kernel e no ACK (consulte Atualizar a representação da ABI ). Para obter um exemplo de atualização do XML ABI e da lista de símbolos no ACK, consulte aosp/1367601 .

Resolver quebras de ABI do kernel

Você pode lidar com quebras de ABI do kernel refatorando o código para não alterar a ABI ou atualizando a representação da ABI . Use o gráfico a seguir para determinar a melhor abordagem para sua situação.

Fluxograma de quebra da ABI

Figura 1. Resolução de quebra de ABI

Refatorar código para evitar alterações de ABI

Faça todos os esforços para evitar modificar a ABI existente. Em muitos casos, você pode refatorar seu código para remover as alterações que afetam a ABI.

  • Refatorando alterações de campo de estrutura. Se uma alteração modifica a ABI para um recurso de depuração, adicione um #ifdef ao redor dos campos (nas estruturas e referências de origem) e certifique-se de que o CONFIG usado para o #ifdef esteja desabilitado para a produção defconfig e gki_defconfig . Para obter um exemplo de como uma configuração de depuração pode ser adicionada a um struct sem quebrar a ABI, consulte este conjunto de patches .

  • Recursos de refatoração para não alterar o kernel principal. Se novos recursos precisarem ser adicionados ao ACK para oferecer suporte aos módulos parceiros, tente refatorar a parte ABI da alteração para evitar modificar a ABI do kernel. Para obter um exemplo de uso da ABI do kernel existente para adicionar funcionalidades adicionais sem alterar a ABI do kernel, consulte aosp/1312213 .

Corrigir uma ABI quebrada no Android Gerrit

Se você não quebrou intencionalmente a ABI do kernel, precisará investigar, usando a orientação fornecida pelas ferramentas de monitoramento da ABI. As causas mais comuns de quebras são funções adicionadas ou excluídas, estruturas de dados alteradas ou alterações na ABI causadas pela adição de opções de configuração que levam a qualquer um dos itens mencionados acima. Comece abordando os problemas encontrados pela ferramenta.

Você pode reproduzir o teste ABI localmente executando o seguinte comando com os mesmos argumentos que você usaria para executar build/build.sh :

Este é um comando de exemplo para os kernels GKI:

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

Sobre rótulos Lint-1

Se você carregar alterações em uma ramificação contendo um KMI congelado ou finalizado, as alterações devem passar pelo ABIAnalyzer para garantir que as alterações não afetem a ABI estável de maneira incompatível. Durante esse processo, o ABIAnalyzer procura o relatório ABI que é criado durante a compilação (uma compilação estendida que executa a compilação normal e, em seguida, algumas etapas de extração e comparação de ABI. Se o ABIAnalyzer encontrar um relatório não vazio, ele definirá o rótulo Lint-1 e a alteração é bloqueada do envio até ser resolvida; até que o conjunto de patches receba um rótulo Lint+1.

Atualizar a ABI do Kernel

Se você precisar atualizar a representação da ABI do kernel, deverá atualizar o arquivo abi.xml correspondente na árvore de origem do kernel. A maneira mais conveniente de fazer isso é usando build/build_abi.sh assim:

build/build_abi.sh --update --print-report

Use os mesmos argumentos que você usaria para executar build/build.sh . Isso atualiza o abi.xml correto na árvore de origem e imprime as diferenças detectadas. Por uma questão de prática, inclua o relatório impresso (curto) na mensagem de confirmação (pelo menos parcialmente).

Atualizar a representação ABI

Se a modificação da ABI for inevitável, seu código será alterado e o XML da ABI e a lista de símbolos precisam ser aplicados ao ACK. Para que o Lint remova o -1 e não quebre a compatibilidade do GKI, execute as seguintes etapas:

  1. Carregue as alterações do código ABI para o ACK .

  2. Atualize os arquivos ACK ABI .

  3. Mescle suas alterações de código e a alteração de atualização da ABI.

Carregar alterações de código ABI para o ACK

A atualização da ACK ABI depende do tipo de alteração que está sendo feita.

  • Se uma alteração de ABI estiver relacionada a um recurso que afeta os testes CTS ou VTS, a alteração geralmente pode ser selecionada para ACK como está. Por exemplo:

  • Se uma alteração de ABI for para um recurso que pode ser compartilhado com o ACK, essa alteração pode ser selecionada para ACK como está. Por exemplo, as seguintes alterações não são necessárias para o teste CTS ou VTS, mas podem ser compartilhadas com ACK:

  • Se uma alteração de ABI introduzir um novo recurso que não precisa ser incluído no ACK, você poderá introduzir os símbolos no ACK usando um stub conforme descrito na seção a seguir.

Use stubs para ACK

Os stubs devem ser necessários apenas para alterações do núcleo do kernel que não beneficiam o ACK, como alterações de desempenho e energia. A lista a seguir detalha exemplos de stubs e seleções parciais em ACK para GKI.

  • Esboço de recurso de isolamento de núcleo ( aosp/1284493 ). A funcionalidade no ACK não é necessária, mas os símbolos precisam estar presentes no ACK para que seus módulos usem esses símbolos.

  • Símbolo de espaço reservado para o módulo do fornecedor ( aosp/1288860 ).

  • Escolha apenas ABI do recurso de rastreamento de eventos mm por processo ( aosp/1288454 ). O patch original foi escolhido a dedo para ACK e depois cortado para incluir apenas as alterações necessárias para resolver o diff ABI para task_struct e mm_event_count . Este patch também atualiza o enum mm_event_type para conter os membros finais.

  • Seleção parcial de alterações da ABI da estrutura térmica que exigiam mais do que apenas adicionar os novos campos da ABI.

    • O patch aosp/1255544 resolveu as diferenças de ABI entre o kernel parceiro e o ACK.

    • O patch aosp/1291018 corrigiu os problemas funcionais encontrados durante o teste GKI do patch anterior. A correção incluiu a inicialização da estrutura de parâmetros do sensor para registrar várias zonas térmicas em um único sensor.

  • CONFIG_NL80211_TESTMODE ABI altera ( aosp/1344321 ). Esse patch adicionou as alterações de estrutura necessárias para ABI e garantiu que os campos adicionais não causassem diferenças funcionais, permitindo que os parceiros incluíssem CONFIG_NL80211_TESTMODE em seus kernels de produção e ainda mantivessem a conformidade com GKI.

Atualizar arquivos ACK ABI

Para atualizar os arquivos ACK ABI:

  1. Carregue as alterações da ABI e espere receber um Code-Review +2 para o conjunto de patches.

  2. Atualize os arquivos ACK ABI.

    cp partner kernel/android/abi_gki_aarch64_partner ACK kernel/abi_gki_aarch64_partner
    BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh --update
    
  3. Confirme a atualização da ABI:

    cd common
    git add android/abi*
    git commit -s -F $DIST_DIR/abi.report.short
    <push to gerrit>
    

    $DIST_DIR/abi.report.short contém um breve relatório das mudanças. Usar o sinalizador -F com git commit usa automaticamente o relatório para o texto do commit, que você pode editar para adicionar uma linha de assunto (ou aparar se a mensagem for muito longa).

Ramificações do kernel do Android com ABI predefinida

Algumas ramificações do kernel vêm com representações ABI predefinidas para Android como parte de sua distribuição de origem. Essas representações ABI devem ser precisas e refletir o resultado de build_abi.sh como se você fosse executá-lo por conta própria. Como a ABI é fortemente influenciada por várias opções de configuração do kernel, esses arquivos .xml geralmente pertencem a uma determinada configuração. Por exemplo, a ramificação common-android12-5.10 contém um abi_gki_aarch64.xml que corresponde ao resultado da compilação ao usar o build.config.gki.aarch64 . Em particular, build.config.gki.aarch64 também se refere a esse arquivo por meio de ABI_DEFINITION .

Essas representações ABI predefinidas são usadas como uma definição de linha de base ao comparar com diff_abi . Por exemplo, para validar um patch do kernel em relação a quaisquer alterações na ABI, crie a representação da ABI com o patch aplicado e use diff_abi para compará-la com a ABI esperada para essa árvore ou configuração de origem específica. Se ABI_DEFINITION estiver definido, a execução de build_abi.sh será suficiente.

Aplicar o KMI em tempo de execução

Os kernels GKI usam as opções de configuração TRIM_UNUSED_KSYMS=y e UNUSED_KSYMS_WHITELIST=<union of all symbol lists> , que limitam os símbolos exportados (como símbolos exportados usando EXPORT_SYMBOL_GPL() ) aos listados em uma lista de símbolos. Todos os outros símbolos não são exportados e o carregamento de um módulo que requer um símbolo não exportado é negado. Essa restrição é aplicada no momento da compilação e as entradas ausentes são sinalizadas.

Para fins de desenvolvimento, você pode usar uma compilação do kernel GKI que não inclui corte de símbolos (o que significa que todos os símbolos normalmente exportados podem ser usados). Para localizar essas compilações, procure as compilações kernel_debug_aarch64 em ci.android.com .

Aplicar o KMI usando o controle de versão do módulo

Os kernels Generic Kernel Image (GKI) usam o controle de versão do módulo ( CONFIG_MODVERSIONS ) como uma medida adicional para impor a conformidade com KMI em tempo de execução. O controle de versão do módulo pode causar falhas de incompatibilidade de verificação de redundância cíclica (CRC) no tempo de carregamento do módulo se o KMI esperado de um módulo não corresponder ao KMI vmlinux . Por exemplo, o seguinte é uma falha típica que ocorre no tempo de carregamento do módulo devido a uma incompatibilidade de CRC para o símbolo module_layout() :

init: Loading module /lib/modules/kernel/.../XXX.ko with args ""
XXX: disagrees about version of symbol module_layout
init: Failed to insmod '/lib/modules/kernel/.../XXX.ko' with args ''

Usos de versionamento de módulo

O controle de versão do módulo é útil pelos seguintes motivos:

  • O controle de versão do módulo captura as alterações na visibilidade da estrutura de dados. Se os módulos alterarem estruturas de dados opacas, ou seja, estruturas de dados que não fazem parte do KMI, eles serão interrompidos após alterações futuras na estrutura.

    Como exemplo, considere o campo fwnode em struct device . Este campo DEVE ser opaco aos módulos para que eles não possam fazer alterações nos campos de device->fw_node ou fazer suposições sobre seu tamanho.

    No entanto, se um módulo incluir <linux/fwnode.h> (direta ou indiretamente), o campo fwnode no struct device não será mais opaco para ele. O módulo pode então fazer alterações em device->fwnode->dev ou device->fwnode->ops . Esse cenário é problemático por várias razões, conforme indicado a seguir:

    • Ele pode quebrar suposições que o código do núcleo do kernel está fazendo sobre suas estruturas de dados internas.

    • Se uma atualização futura do kernel alterar o struct fwnode_handle (o tipo de dados de fwnode ), o módulo não funcionará mais com o novo kernel. Além disso, abidiff não mostrará nenhuma diferença porque o módulo está quebrando o KMI manipulando diretamente as estruturas de dados internas de maneiras que não podem ser capturadas apenas inspecionando a representação binária.

  • Um módulo atual é considerado incompatível com KMI quando é carregado posteriormente por um novo kernel incompatível. O controle de versão do módulo adiciona uma verificação em tempo de execução para evitar o carregamento acidental de um módulo que não é compatível com KMI com o kernel. Essa verificação evita problemas de tempo de execução difíceis de depurar e travamentos do kernel que podem resultar de uma incompatibilidade não detectada no KMI.

  • abidiff tem limitações na identificação de diferenças de ABI em certos casos complicados que CONFIG_MODVERSIONS pode capturar.

Ativar o controle de versão do módulo evita todos esses problemas.

Verifique se há incompatibilidades de CRC sem inicializar o dispositivo

abidiff compara e relata incompatibilidades de CRC entre os kernels. Esta ferramenta permite que você capture CRC incompatíveis ao mesmo tempo que outras diferenças de ABI.

Além disso, uma compilação completa do kernel com CONFIG_MODVERSIONS habilitado gera um arquivo Module.symvers como parte do processo normal de compilação. Este arquivo possui uma linha para cada símbolo exportado pelo kernel ( vmlinux ) e pelos módulos. Cada linha consiste no valor CRC, nome do símbolo, namespace do símbolo, vmlinux ou nome do módulo que está exportando o símbolo e o tipo de exportação (por exemplo, EXPORT_SYMBOL versus EXPORT_SYMBOL_GPL ).

Você pode comparar os arquivos Module.symvers entre a compilação GKI e sua compilação para verificar quaisquer diferenças de CRC nos símbolos exportados pelo vmlinux . Se houver uma diferença de valor CRC em qualquer símbolo exportado pelo vmlinux e esse símbolo for usado por um dos módulos que você carrega em seu dispositivo, o módulo não carrega.

Se você não tiver todos os artefatos de compilação, mas tiver os arquivos vmlinux do kernel GKI e seu kernel, poderá comparar os valores de CRC para um símbolo específico executando o seguinte comando em ambos os kernels e comparando a saída:

nm <path to vmlinux>/vmlinux | grep __crc_<symbol name>

Por exemplo, o comando a seguir verifica o valor CRC para o símbolo module_layout :

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

Resolver incompatibilidades de CRC

Use as etapas a seguir para resolver uma incompatibilidade de CRC ao carregar um módulo:

  1. Compile o kernel GKI e o kernel do seu dispositivo KBUILD_SYMTYPES=1 ao comando que você usa para compilar o kernel, conforme mostrado no comando a seguir:

    KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
    

    Este comando gera um arquivo .symtypes para cada arquivo .o . Ao usar build_abi.sh, o KBUILD_SYMTYPES=1 já está definido implicitamente.

  2. Encontre o arquivo .c no qual o símbolo com incompatibilidade de CRC é exportado, usando o seguinte comando:

    cd common && git grep EXPORT_SYMBOL.*module_layout
    kernel/module.c:EXPORT_SYMBOL(module_layout);
    
  3. O arquivo .c tem um arquivo .symtypes correspondente no GKI e os artefatos de compilação do kernel do seu dispositivo. Localize o arquivo .c usando os seguintes comandos:

    cd out/$BRANCH/common && ls -1 kernel/module.*
    kernel/module.o
    kernel/module.o.symversions
    kernel/module.symtypes
    

    A seguir estão as características do arquivo .c :

    • O formato do arquivo .c é uma linha (potencialmente muito longa) por símbolo.

    • [s|u|e|etc]# no início da linha significa que o símbolo é do tipo de dados [struct|union|enum|etc] . Por exemplo:

      t#bool typedef _Bool bool
      
    • Um prefixo # ausente no início da linha indica que o símbolo é uma função. Por exemplo:

      find_module s#module * find_module ( const char * )
      
  4. Compare os dois arquivos e corrija todas as diferenças.

Caso 1: Diferenças devido à visibilidade do tipo de dados

Se um kernel mantém um símbolo ou tipo de dados opaco para os módulos e o outro kernel não, essa diferença aparece entre os arquivos .symtypes dos dois kernels. O arquivo .symtypes de um dos kernels tem um símbolo UNKNOWN e o arquivo .symtypes do outro kernel tem uma visão expandida do símbolo ou tipo de dados.

Por exemplo, adicionar a seguinte linha ao arquivo include/linux/device.h em seu kernel causa incompatibilidades de CRC, uma das quais é para module_layout() :

 #include <linux/fwnode.h>

Comparando o module.symtypes para esse símbolo, expõe as seguintes diferenças:

 $ diff -u <GKI>/kernel/module.symtypes <your kernel>/kernel/module.symtypes
  --- <GKI>/kernel/module.symtypes
  +++ <your kernel>/kernel/module.symtypes
  @@ -334,12 +334,15 @@
  ...
  -s#fwnode_handle struct fwnode_handle { UNKNOWN }
  +s#fwnode_reference_args struct fwnode_reference_args { s#fwnode_handle * fwnode ; unsigned int nargs ; t#u64 args [ 8 ] ; }
  ...

Se o seu kernel tiver um valor de UNKNOWN e o kernel GKI tiver a visualização expandida do símbolo (muito improvável), mescle o kernel comum do Android mais recente em seu kernel para que você esteja usando a base do kernel GKI mais recente.

Na maioria dos casos, o kernel GKI tem um valor de UNKNOWN , mas seu kernel tem os detalhes internos do símbolo devido às alterações feitas em seu kernel. Isso ocorre porque um dos arquivos em seu kernel adicionou um #include que não está presente no kernel GKI.

Para identificar o #include que causa a diferença, siga estas etapas:

  1. Abra o arquivo de cabeçalho que define o símbolo ou tipo de dados com essa diferença. Por exemplo, edite include/linux/fwnode.h para o struct fwnode_handle .

  2. Adicione o seguinte código na parte superior do arquivo de cabeçalho:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. No arquivo .c do módulo que tem uma incompatibilidade de CRC, adicione o seguinte como a primeira linha antes de qualquer uma das linhas #include .

    #define CRC_CATCH 1
    
  4. Compile seu módulo. O erro de tempo de compilação resultante mostra a cadeia do arquivo de cabeçalho #include que levou a essa incompatibilidade de CRC. Por exemplo:

    In file included from .../drivers/clk/XXX.c:16:`
    In file included from .../include/linux/of_device.h:5:
    In file included from .../include/linux/cpu.h:17:
    In file included from .../include/linux/node.h:18:
    .../include/linux/device.h:16:2: error: "Included from here"
    #error "Included from here"
    

    Um dos elos dessa corrente de #include se deve a uma alteração feita em seu kernel, que está faltando no kernel GKI.

  5. Identifique a mudança, reverta-a em seu kernel ou carregue-a para ACK e faça a mesclagem .

Caso 2: Diferenças devido a alterações de tipo de dados

Se a incompatibilidade de CRC para um símbolo ou tipo de dados não for devido a uma diferença na visibilidade, isso se deve a alterações reais (adições, remoções ou alterações) no próprio tipo de dados. Normalmente, abidiff captura isso, mas se perder algum devido a falhas de detecção conhecidas, o mecanismo MODVERSIONS pode capturá-los.

Por exemplo, fazer a seguinte alteração em seu kernel causa várias incompatibilidades de CRC, pois muitos símbolos são afetados indiretamente por esse tipo de alteração:

diff --git a/include/linux/iommu.h b/include/linux/iommu.h
  --- a/include/linux/iommu.h
  +++ b/include/linux/iommu.h
  @@ -259,7 +259,7 @@ struct iommu_ops {
     void (*iotlb_sync)(struct iommu_domain *domain);
     phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);
     phys_addr_t (*iova_to_phys_hard)(struct iommu_domain *domain,
  -        dma_addr_t iova);
  +        dma_addr_t iova, unsigned long trans_flag);
     int (*add_device)(struct device *dev);
     void (*remove_device)(struct device *dev);
     struct iommu_group *(*device_group)(struct device *dev);

Uma incompatibilidade de CRC é para devm_of_platform_populate() .

Se você comparar os arquivos .symtypes para esse símbolo, pode ficar assim:

 $ diff -u <GKI>/drivers/of/platform.symtypes <your kernel>/drivers/of/platform.symtypes
  --- <GKI>/drivers/of/platform.symtypes
  +++ <your kernel>/drivers/of/platform.symtypes
  @@ -399,7 +399,7 @@
  ...
  -s#iommu_ops struct iommu_ops { ... ; t#phy
  s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t ) ; int
    ( * add_device ) ( s#device * ) ; ...
  +s#iommu_ops struct iommu_ops { ... ; t#phy
  s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t , unsigned long ) ; int ( * add_device ) ( s#device * ) ; ...

Para identificar o tipo alterado, siga estas etapas:

  1. Encontre a definição do símbolo no código-fonte (geralmente em arquivos .h ).

    • Para diferenças de símbolos simples entre seu kernel e o kernel GKI, encontre o commit executando o seguinte comando:
    git blame
    
    • Para símbolos excluídos (onde um símbolo é excluído em uma árvore e você também deseja excluí-lo na outra árvore), você precisa encontrar a alteração que excluiu a linha. Use o seguinte comando na árvore em que a linha foi excluída:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
    
  2. Revise a lista retornada de confirmações para localizar a alteração ou exclusão. O primeiro commit é provavelmente aquele que você está procurando. Se não for, percorra a lista até encontrar o commit.

  3. Depois de identificar a alteração, reverta-a em seu kernel ou carregue-a no ACK e faça a mesclagem .