Monitoramento de ABI do kernel do Android

Você pode usar as ferramentas de monitoramento da interface binária de aplicativo (ABI, na sigla em inglês), disponíveis no Android 11 e versões mais recentes, para estabilizar a ABI no kernel de kernels do Android. As ferramentas coletam e comparam representações de ABI de binários do kernel atuais (vmlinux+ módulos de GKI). Essas representações de ABI são os arquivos .stg e as listas de símbolos. A interface em que a representação oferece uma visualização é chamada de Interface do módulo do kernel (KMI, na sigla em inglês). É possível usar as ferramentas para rastrear e reduzir as mudanças no KMI.

As ferramentas de monitoramento de ABI são desenvolvidas no AOSP e usam STG (ou libabigail no Android 13 e versões anteriores) 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 dessas representações para oferecer estabilidade à ABI no kernel. Esta página também fornece informações sobre como contribuir com mudanças para os kernels do Android.

Processo

A análise da ABI do kernel envolve várias etapas, a maioria delas automatizada:

  1. Crie o kernel e a representação da ABI dele.
  2. Analisar as diferenças de ABI entre o build e uma referência.
  3. Atualize a representação da ABI (se necessário).
  4. Trabalhar com listas de símbolos.

As instruções a seguir funcionam para qualquer kernel que você possa criar usando uma cadeia de ferramentas compatível (como a cadeia de ferramentas Clang pré-criada). repo manifests estão disponíveis para todas as ramificações comuns do kernel do Android e para vários kernels específicos do dispositivo. Eles verificam se a cadeia de ferramentas correta é usada quando você cria uma distribuição do kernel para análise.

Listas de símbolos

O KMI não inclui todos os símbolos no kernel nem todos os mais de 30.000 símbolos exportados. Em vez disso, os símbolos que podem ser usados pelos módulos do fornecedor são listados explicitamente em um conjunto de arquivos de lista de símbolos mantidos publicamente na árvore do kernel (gki/{ARCH}/symbols/* ou android/abi_gki_{ARCH}_* no Android 15 e versões anteriores). A união de todos os símbolos em todos os arquivos de lista de símbolos define o conjunto de símbolos de KMI mantidos como estáveis. Um exemplo de arquivo de lista de símbolos é gki/aarch64/symbols/db845c, que declara os símbolos necessários para o DragonBoard 845c.

Somente os símbolos listados em uma lista de símbolos e as estruturas e definições relacionadas a eles são considerados parte da KMI. Você pode postar mudanças nas 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 da KMI, elas são mantidas como estáveis e não podem ser removidas da lista de símbolos nem modificadas depois que a ramificação é congelada.

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

As ferramentas de ABI usam listas de símbolos da KMI para limitar quais interfaces precisam ser monitoradas para estabilidade. Os fornecedores precisam enviar e atualizar as próprias listas de símbolos para verificar se as interfaces em que se baseiam mantêm a compatibilidade com a ABI. Por exemplo, para ver uma lista de listas de símbolos do kernel android16-6.12, consulte https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols

Uma lista de símbolos contém os símbolos que precisam ser informados para o fornecedor ou dispositivo específico. A lista completa usada pelas ferramentas é a união de todos os arquivos de lista de símbolos do KMI. As ferramentas de ABI determinam os detalhes de cada símbolo, incluindo assinatura de função e estruturas de dados aninhadas.

Quando a KMI é congelada, não são permitidas mudanças nas interfaces dela. Elas ficam estáveis. No entanto, os fornecedores podem adicionar símbolos à KMI a qualquer momento, desde que as adições não afetem a estabilidade da ABI atual. Os símbolos recém-adicionados são mantidos estáveis assim que são citados por uma lista de símbolos do KMI. Os símbolos não podem ser removidos de uma lista para um kernel, a menos que seja possível confirmar que nenhum dispositivo foi enviado com uma dependência desse símbolo.

Para gerar uma lista de símbolos do KMI para um dispositivo, siga as instruções em Como trabalhar com listas de símbolos. Muitos parceiros enviam uma lista de símbolos por ACK, mas isso não é um requisito obrigatório. Se isso ajudar na manutenção, envie várias listas de símbolos.

Estender o KMI

Embora os símbolos da KMI e as estruturas relacionadas sejam mantidos como estáveis (ou seja, não é possível aceitar mudanças que quebrem interfaces estáveis em um kernel com uma KMI congelada), o kernel GKI permanece aberto a extensões para que os dispositivos lançados mais tarde no ano não precisem definir todas as dependências antes do congelamento da KMI. Para estender a KMI, adicione novos símbolos a ela para funções de kernel exportadas novas ou existentes, mesmo que a KMI esteja congelada. Novos patches de kernel também podem ser aceitos se não violarem a KMI.

Sobre falhas na KMI

Um kernel tem fontes, e os binários são criados com base nelas. As ramificações do kernel monitoradas por ABI incluem uma representação da ABI GKI atual (na forma de um arquivo .stg). Depois que os binários (vmlinux, Image e todos os módulos de GKI) são criados, uma representação de ABI pode ser extraída dos binários. Qualquer mudança feita em um arquivo de origem do kernel pode afetar os binários e, por sua vez, também afetar o .stg extraído. A análise de conformidade da ABI compara o arquivo .stg confirmado com o extraído dos artefatos de build e define um rótulo Lint-1 na mudança no Gerrit se encontrar uma diferença semântica.

Processar quebras de ABI

Por 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 42786e6364ef..e15f1d0f137b 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -657,6 +657,7 @@ struct mm_struct {
                ANDROID_KABI_RESERVE(1);
        } __randomize_layout;

+       int tickle_count;
        /*
         * The mm_cpumask needs to be at the end of mm_struct, because it
         * is dynamically sized based on nr_cpu_ids.

Quando você executa o build da ABI com esse patch aplicado, as ferramentas são encerradas com um código de erro diferente de zero e informam uma diferença de ABI semelhante a esta:

function symbol 'struct block_device* I_BDEV(struct inode*)' changed
  CRC changed from 0x8d400dbd to 0xabfc92ad

function symbol 'void* PDE_DATA(const struct inode*)' changed
  CRC changed from 0xc3c38b5c to 0x7ad96c0d

function symbol 'void __ClearPageMovable(struct page*)' changed
  CRC changed from 0xf489e5e8 to 0x92bd005e

... 4492 omitted; 4495 symbols have only CRC changes

type 'struct mm_struct' changed
  byte size changed from 992 to 1000
  member 'int tickle_count' was added
  member 'unsigned long cpu_bitmap[0]' changed
    offset changed by 64

Diferenças de ABI detectadas no momento da build

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, primeiro verifique se ele foi exportado com EXPORT_SYMBOL_GPL(symbol_name) e atualize a lista de símbolos e a representação da ABI. Por exemplo, as mudanças a seguir adicionam o novo recurso de sistema de arquivos incremental à ramificação android-12-5.10, que inclui a atualização da lista de símbolos e da representação da ABI.

  • O exemplo de mudança de recurso está em aosp/1345659.
  • O exemplo de lista de símbolos está em aosp/1346742.
  • Um exemplo de mudança na representação da ABI está em aosp/1349377.

Se o símbolo for exportado (por você ou anteriormente), mas nenhum outro driver estiver usando, você poderá receber um erro de build 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 isso, atualize a lista de símbolos KMI no kernel e no ACK. Consulte Atualizar a representação da ABI. Para um exemplo de atualização de uma lista de símbolos e da representação de ABI no ACK, consulte aosp/1367601.

Resolver falhas de ABI do kernel

É possível lidar com quebras de ABI do kernel refatorando o código para não mudar 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 de ABI

Figura 1. Resolução de falhas de ABI

Refatorar o código para evitar mudanças na ABI

Faça o possível para evitar modificar a ABI atual. Em muitos casos, é possível refatorar o código para remover mudanças que afetam a ABI.

  • Refatoração de mudanças no campo da estrutura. Se uma mudança modificar a ABI de um recurso de depuração, adicione um #ifdef ao redor dos campos (nas structs e referências de origem) e verifique se o CONFIG usado para o #ifdef está desativado para a defconfig de produção e gki_defconfig. Para um exemplo de como uma configuração de depuração pode ser adicionada a uma struct sem interromper a ABI, consulte este conjunto de patches.

  • Refatoração de recursos para não mudar o kernel principal. Se for necessário adicionar novos recursos ao ACK para oferecer suporte aos módulos do parceiro, tente refatorar a parte da ABI da mudança para evitar modificar a ABI do kernel. Para um exemplo de uso da ABI do kernel atual para adicionar outros recursos sem mudar a ABI do kernel, consulte aosp/1312213.

Corrigir uma ABI corrompida no Android Gerrit

Se você não quebrou a ABI do kernel intencionalmente, investigue usando as orientações fornecidas pelas ferramentas de monitoramento de ABI. As causas mais comuns de falhas são estruturas de dados alteradas e as mudanças associadas no CRC do símbolo ou mudanças nas opções de configuração que levam a qualquer uma das situações mencionadas. Comece corrigindo os problemas encontrados pela ferramenta.

É possível reproduzir os resultados da ABI localmente. Consulte Criar o kernel e a representação da ABI.

Sobre os rótulos Lint-1

Se você fizer upload de mudanças em uma ramificação que contenha uma KMI congelada ou finalizada, as mudanças precisarão passar por análises de conformidade e compatibilidade de ABI para garantir que as mudanças na representação de ABI reflitam a ABI real e não contenham incompatibilidades (remoções de símbolos ou mudanças de tipo).

Cada uma dessas análises de ABI pode definir o rótulo Lint-1 e bloquear o envio de mudanças até que todos os problemas sejam resolvidos ou o rótulo seja substituído.

Atualizar a ABI do kernel

Se for inevitável modificar a ABI, aplique as mudanças de código, a representação da ABI e a lista de símbolos ao ACK. Para que o Lint remova o -1 e não quebre a compatibilidade com o GKI, siga estas etapas:

  1. Faça upload das mudanças de código para o ACK.

  2. Aguarde receber uma revisão de código +2 para o conjunto de patches.

  3. Atualize a representação da ABI de referência.

  4. Mescle as mudanças no código e na atualização da ABI.

Fazer upload das mudanças no código da ABI para o ACK

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

  • Se uma mudança de ABI estiver relacionada a um recurso que afeta os testes do CTS ou do VTS, a mudança geralmente poderá ser escolhida para ACK como está. Por exemplo:

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

  • Se uma mudança 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.

Usar stubs para ACK

Os stubs só podem ser necessários para mudanças no kernel principal que não beneficiam o ACK, como mudanças de desempenho e energia. A lista a seguir detalha exemplos de stubs e cherry-picks parciais no ACK para GKI.

  • Stub de recurso de isolamento de núcleo (aosp/1284493). As funcionalidades no ACK não são necessárias, mas os símbolos precisam estar presentes no ACK para que seus módulos usem esses símbolos.

  • Símbolo de marcador de posição para módulo do fornecedor. (aosp/1288860)

  • Cherry-pick somente de ABI do recurso de acompanhamento de eventos mm por processo (aosp/1288454). O patch original foi escolhido para ACK e depois cortado para incluir apenas as mudanças necessárias para resolver a diferença de ABI para task_struct e mm_event_count. Esse patch também atualiza a enumeração mm_event_type para conter os membros finais.

  • Cherry-pick parcial das mudanças na ABI da estrutura térmica que exigiram mais do que apenas adicionar os novos campos da ABI.

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

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

  • Mudanças na ABI CONFIG_NL80211_TESTMODE (aosp/1344321). O patch adicionou as mudanças necessárias na struct para ABI e garantiu que os campos extras não causassem diferenças funcionais, permitindo que os parceiros incluíssem CONFIG_NL80211_TESTMODE nos kernels de produção e ainda mantivessem a conformidade com a GKI.

Aplicar a KMI no ambiente 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 os 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 exige um símbolo não exportado é negado. Essa restrição é aplicada no momento da build, e as entradas ausentes são sinalizadas.

Para fins de desenvolvimento, você pode usar um build de kernel GKI que não inclua corte de símbolos. Isso significa que todos os símbolos geralmente exportados podem ser usados. Para localizar esses builds, procure os builds kernel_debug_aarch64 em ci.android.com.

Aplicar o KMI usando o controle de versões do módulo

Os kernels de imagem genérica do kernel (GKI) usam o controle de versões do módulo (CONFIG_MODVERSIONS) como uma medida adicional para aplicar a conformidade do KMI em tempo de execução. O controle de versões do módulo pode causar falhas de incompatibilidade de verificação de redundância cíclica (CRC) no momento do carregamento do módulo se o KMI esperado de um módulo não corresponder ao KMI do vmlinux. Por exemplo, esta é 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 do controle de versões do módulo

O controle de versões de módulos é útil pelos seguintes motivos:

  • O controle de versões do módulo detecta mudanças na visibilidade da estrutura de dados. Se os módulos mudarem estruturas de dados opacas, ou seja, estruturas que não fazem parte da KMI, eles vão falhar após mudanças futuras na estrutura.

    Por exemplo, considere o campo fwnode em struct device. Esse campo PRECISA ser opaco para que os módulos não possam fazer mudanças nos campos de device->fw_node nem fazer suposições sobre o tamanho dele.

    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. Em seguida, o módulo pode fazer mudanças em device->fwnode->dev ou device->fwnode->ops. Esse cenário é problemático por vários motivos, como:

    • Ele pode violar as proposições que o código do kernel principal faz sobre as estruturas de dados internas.

    • Se uma atualização futura do kernel mudar o struct fwnode_handle (o tipo de dados de fwnode), o módulo não vai mais funcionar com o novo kernel. Além disso, stgdiff não vai mostrar nenhuma diferença porque o módulo está violando a KMI ao manipular diretamente 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 a KMI quando é carregado em uma data posterior por um novo kernel incompatível. O controle de versões do módulo adiciona uma verificação de tempo de execução para evitar o carregamento acidental de um módulo que não é compatível com a KMI do kernel. Essa verificação evita problemas de tempo de execução difíceis de depurar e falhas do kernel que podem resultar de uma incompatibilidade não detectada na KMI.

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

Verificar se há incompatibilidades de CRC sem inicializar o dispositivo

stgdiff compara e informa incompatibilidades de CRC entre kernels, além de outras diferenças de ABI.

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

Compare os arquivos Module.symvers entre o build do GKI e o seu para verificar se há diferenças de CRC nos símbolos exportados por vmlinux. Se houver uma diferença no valor de CRC em qualquer símbolo exportado por vmlinux e e esse símbolo for usado por um dos módulos carregados no dispositivo, o módulo não será carregado.

Se você não tiver todos os artefatos de build, mas tiver os arquivos vmlinux do kernel GKI e do seu kernel, compare os valores de CRC de um símbolo específico executando o seguinte comando nos dois kernels e comparando a saída:

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

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

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

Resolver incompatibilidades de CRC

Siga estas etapas para resolver uma incompatibilidade de CRC ao carregar um módulo:

  1. Crie o kernel da GKI e o kernel do dispositivo usando a opção --kbuild_symtypes conforme mostrado no comando a seguir:

    tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist

    Esse comando gera um arquivo .symtypes para cada arquivo .o. Consulte KBUILD_SYMTYPES no Kleaf para mais detalhes.

    Para o Android 13 e versões anteriores, crie o kernel da GKI e o kernel do dispositivo adicionando KBUILD_SYMTYPES=1 ao comando que você usa para criar o kernel, conforme mostrado no comando a seguir:

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

    Ao usar build_abi.sh,, a flag KBUILD_SYMTYPES=1 já é definida implicitamente.

  2. Encontre o arquivo .c em que o símbolo com incompatibilidade de CRC é exportado usando o seguinte comando:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. O arquivo .c tem um arquivo .symtypes correspondente no GKI e nos artefatos de build do kernel do dispositivo. Localize o arquivo .symtypes usando os seguintes comandos:

    cd bazel-bin/common/kernel_aarch64/symtypes
    ls -1 kernel/module/version.symtypes

    No Android 13 e versões anteriores, usando os scripts de build legados, o local provavelmente será out/$BRANCH/common ou out_abi/$BRANCH/common.

    Cada arquivo .symtypes é um arquivo de texto simples que consiste em descrições de tipo e símbolo:

    • Cada linha tem o formato key description, em que a descrição pode se referir a outras chaves no mesmo arquivo.

    • Chaves como [s|u|e|t]#foo se referem a [struct|union|enum|typedef] foo. Exemplo:

      t#bool typedef _Bool bool
      
    • As chaves sem prefixo x# são apenas nomes de símbolos. Exemplo:

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

É melhor gerar symtypes com um build logo antes da mudança problemática e depois na mudança problemática. Salvar todos os arquivos significa que eles podem ser comparados em massa.

Por exemplo:

for f in $(find good bad -name '*.symtypes' | sed -r 's;^(good|bad)/;;' | LANG=C sort -u); do
  diff -N -U0 --label good/"$f" --label bad/"$f" <(LANG=C sort good/"$f") <(LANG=C sort bad/"$f")
done

Caso contrário, compare apenas os arquivos específicos de interesse.

Caso 1: diferenças devido à visibilidade do tipo de dado

Um novo #include pode extrair uma nova definição de tipo (por exemplo, de struct foo) para um arquivo de origem. Nesses casos, a descrição no arquivo .symtypes correspondente muda de um structure_type foo { } vazio para uma definição completa.

Isso vai afetar todos os CRCs de todos os símbolos no arquivo .symtypes cujas descrições dependem direta ou indiretamente da definição de tipo.

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

 #include <linux/fwnode.h>

Comparar o module/version.symtypes desse símbolo expõe as seguintes diferenças:

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

Se o kernel do GKI tiver a definição de tipo completa, mas o seu não tiver (muito improvável), mescle o kernel comum do Android mais recente no seu para usar a base do kernel do GKI mais recente.

Na maioria dos casos, o kernel GKI não tem a definição de tipo completa em .symtypes, mas seu kernel tem devido a outras diretivas #include.

Resolução para Android 16 e versões mais recentes

Verifique se o arquivo de origem afetado inclui o cabeçalho de estabilização da KABI do Android:

#include <linux/android_kabi.h>

Para cada tipo afetado, adicione ANDROID_KABI_DECLONLY(name); no escopo global ao arquivo de origem afetado.

Por exemplo, se a diferença de symtypes for esta:

--- good/drivers/android/vendor_hooks.symtypes
+++ bad/drivers/android/vendor_hooks.symtypes
@@ -1051 +1051,2 @@
-s#ubuf_info structure_type ubuf_info { }
+s#ubuf_info structure_type ubuf_info { member pointer_type { const_type { s#ubuf_info_ops } } ops data_member_location(0) , member t#refcount_t refcnt data_member_location(8) , member t#u8 flags data_member_location(12) } byte_size(16)
+s#ubuf_info_ops structure_type ubuf_info_ops { member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } , formal_parameter t#bool ) -> base_type void } complete data_member_location(0) , member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } ) -> base_type int byte_size(4) encoding(5) } link_skb data_member_location(8) } byte_size(16)

O problema é que struct ubuf_info agora tem uma definição completa em symtypes. A solução é adicionar uma linha a drivers/android/vendor_hooks.c:

ANDROID_KABI_DECLONLY(ubuf_info);

Isso instrui o gendwarfksyms a tratar o tipo nomeado como indefinido no arquivo.

Uma possibilidade mais complexa é que o novo #include esteja em um arquivo de cabeçalho. Nesse caso, talvez seja necessário distribuir diferentes conjuntos de invocações de macro ANDROID_KABI_DECLONLY em arquivos de origem que indiretamente extraem definições de tipo extras, já que alguns deles podem já ter algumas das definições de tipo.

Para facilitar a leitura, coloque essas invocações de macro perto do início do arquivo de origem.

Resolução para Android 15 e versões anteriores

Muitas vezes, a correção é apenas ocultar o novo #include de genksyms.

#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif

Caso contrário, 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 o 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 de cima 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 o módulo. O erro de tempo de build resultante mostra a cadeia de arquivo de cabeçalho #include que levou a essa incompatibilidade de CRC. 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 links nessa cadeia de #include é devido a uma mudança feita no seu kernel, que está faltando no kernel GKI.

Caso 2: diferenças devido a mudanças no tipo de dados

Se a incompatibilidade de CRC de um símbolo ou tipo de dados não for devido a uma diferença de visibilidade, ela será causada por mudanças reais (adições, remoções ou alterações) no próprio tipo de dados.

Por exemplo, fazer a seguinte mudança no kernel causa várias incompatibilidades de CRC, já que muitos símbolos são afetados indiretamente por esse tipo de mudança:

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 desse símbolo, eles podem 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 structure_type 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 structure_type 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 entre seu kernel e o kernel GKI, encontre o commit executando o seguinte comando:
    git blame
    • Para símbolos excluídos (quando um símbolo é excluído em uma árvore e você também quer excluí-lo na outra), encontre a mudança 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 de commits retornada para localizar a mudança ou exclusão. O primeiro commit é provavelmente o que você está procurando. Caso contrário, percorra a lista até encontrar o commit.

  3. Depois de identificar o commit, reverta-o no kernel ou atualize-o para suprimir a mudança de CRC e fazer upload para o ACK e mesclar. Cada quebra de ABI residual precisa ser analisada quanto à segurança e, se necessário, uma quebra permitida pode ser registrada.

Preferir consumir o padding atual

Algumas estruturas no GKI são preenchidas para permitir a extensão sem interromper os módulos do fornecedor atuais. Se um commit upstream (por exemplo) adicionar um membro a uma estrutura desse tipo, talvez seja possível mudar para consumir parte do padding. Essa mudança é ocultada do cálculo de CRC.

A macro padronizada e autodocumentada ANDROID_KABI_RESERVE reserva um espaço de u64 (alinhado). Ele é usado no lugar de uma declaração de membro.

Exemplo:

struct data {
        u64 handle;
        ANDROID_KABI_RESERVE(1);
        ANDROID_KABI_RESERVE(2);
};

O padding pode ser consumido, sem afetar os CRCs de símbolos, com ANDROID_KABI_USE (ou ANDROID_KABI_USE2 ou outras variantes que podem ser definidas).

O membro sekret está disponível como se tivesse sido declarado diretamente, mas a macro é expandida para um membro de união anônimo que contém sekret e itens usados por gendwarfksyms para manter a estabilidade do symtype.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
Resolução para Android 16 e versões mais recentes

Os CRCs são calculados por gendwarfksyms, que usa informações de depuração DWARF, oferecendo suporte aos tipos C e Rust. A resolução varia de acordo com o tipo de mudança. Veja alguns exemplos.

Enumeradores novos ou modificados

Às vezes, novos enumeradores são adicionados, e ocasionalmente um valor de enumerador MAX ou semelhante também é afetado. Essas mudanças são seguras se não "escaparem" do GKI ou se pudermos ter certeza de que os módulos do fornecedor não se importam com os valores deles.

Exemplo:

 enum outcome {
       SUCCESS,
       FAILURE,
       RETRY,
+      TRY_HARDER,
       OUTCOME_LIMIT
 };

A adição de TRY_HARDER e a mudança para OUTCOME_LIMIT podem ser ocultadas do cálculo de CRC com invocações de macro no escopo global:

ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);

Para facilitar a leitura, coloque-as logo após a definição de enum.

Um novo membro da estrutura ocupando um buraco existente

Devido ao alinhamento, haverá bytes não utilizados entre urgent e scratch.

        void *data;
        bool urgent;
+       bool retry;
        void *scratch;

Nenhum deslocamento de membro ou tamanho da estrutura é afetado pela adição de retry. No entanto, isso pode afetar os CRCs de símbolos, a representação da ABI ou ambos.

Isso vai ocultar o valor do cálculo de CRC:

        void *data;
        bool urgent;
+       ANDROID_KABI_IGNORE(1, bool retry);
        void *scratch_space;

O membro retry está disponível como se tivesse sido declarado diretamente, mas a macro é expandida para um membro de união anônimo que contém retry e itens usados por gendwarfksyms para manter a estabilidade do symtype.

Extensão de uma estrutura com novos membros

Às vezes, os membros são adicionados ao final de uma estrutura. Isso não afeta os deslocamentos de membros atuais nem os usuários atuais da estrutura que só a acessam por ponteiro. O tamanho da estrutura afeta o CRC, e as mudanças podem ser suprimidas com uma invocação de macro extra no escopo global, da seguinte forma:

struct data {
        u64 handle;
        u64 counter;
        ANDROID_KABI_IGNORE(1, void *sekret);
};

ANDROID_KABI_BYTE_SIZE(data, 16);

Para facilitar a leitura, coloque isso logo após a definição de struct.

Todas as outras mudanças em um tipo ou no tipo de um símbolo

Em raras ocasiões, pode haver mudanças que não se enquadram em uma das categorias anteriores, resultando em mudanças de CRC que não podem ser suprimidas usando as macros anteriores.

Nesses casos, a descrição original symtypes de um tipo ou símbolo pode ser fornecida com uma invocação de ANDROID_KABI_TYPE_STRING no escopo global.

struct data {
        /* extensive changes */
};

ANDROID_KABI_TYPE_STRING("s#data", "original s#data symtypes definition");

Para facilitar a leitura, coloque isso logo após a definição do tipo ou do símbolo.

Resolução para Android 15 e versões anteriores

As mudanças de tipo e tipo de símbolo precisam ser ocultadas de genksyms. Isso pode ser feito controlando o pré-processamento com __GENKSYMS__.

Transformações de código arbitrárias podem ser expressas dessa forma.

Por exemplo, para ocultar um novo membro que ocupa um espaço em uma estrutura existente:

struct parcel {
        void *data;
        bool urgent;
#ifndef __GENKSYMS__
        bool retry;
#endif
        void *scratch_space;
};