Supervisión de ABI de kernel de Android

Puedes usar herramientas de supervisión de la interfaz binaria de aplicación (ABI), disponibles en Android 11 y versiones posteriores, para estabilizar la ABI de kernel de los kernels de Android. Las herramientas recopilan y comparan representaciones de ABI de los archivos binarios del kernel existentes (vmlinux + módulos de GKI). Estas representaciones de ABI son los archivos .stg y las listas de símbolos. La interfaz en la que la representación ofrece una vista se denomina Interfaz del módulo del kernel (KMI). Puedes usar las herramientas para hacer un seguimiento de los cambios en la KMI y mitigarlos.

Las herramientas de supervisión de ABI se desarrollan en AOSP y usan STG (o libabigail en Android 13 y versiones anteriores) para generar y comparar representaciones.

En esta página, se describen las herramientas, el proceso de recopilación y análisis de las representaciones de ABI, y el uso de dichas representaciones para proporcionar estabilidad a la ABI del kernel. En esta página, también se proporciona información para contribuir con cambios a los kernels de Android.

Procesar

El análisis de la ABI del kernel requiere varios pasos, la mayoría de los cuales se pueden automatizar:

  1. Compila el kernel y su representación de ABI.
  2. Analiza las diferencias de ABI entre la compilación y una referencia.
  3. Actualiza la representación de la ABI (si es necesario).
  4. Trabaja con listas de símbolos.

Las siguientes instrucciones funcionan para cualquier kernel que puedas compilar con una cadena de herramientas compatible (como la cadena de herramientas de Clang compilada previamente). repo manifests están disponibles para todas las ramas comunes del kernel de Android y para varios kernels específicos del dispositivo. Verifican que se use la cadena de herramientas correcta cuando compilas una distribución del kernel para el análisis.

Listas de símbolos

La KMI no incluye todos los símbolos del kernel ni siquiera todos los más de 30,000 símbolos exportados. En cambio, los símbolos que pueden usar los módulos del proveedor se enumeran de forma explícita en un conjunto de archivos de lista de símbolos que se mantienen públicamente en el árbol del kernel (gki/{ARCH}/symbols/* o android/abi_gki_{ARCH}_* en Android 15 y versiones anteriores). La unión de todos los símbolos en todos los archivos de la lista de símbolos define el conjunto de símbolos de KMI que se mantienen como estables. Un ejemplo de archivo de lista de símbolos es gki/aarch64/symbols/db845c, que declara los símbolos necesarios para la DragonBoard 845c.

Solo los símbolos que se enumeran en una lista de símbolos y sus estructuras y definiciones relacionadas se consideran parte del KMI. Puedes publicar cambios en tus listas de símbolos si los símbolos que necesitas no están presentes. Una vez que las interfaces nuevas se incluyen en una lista de símbolos y forman parte de la descripción de la KMI, se mantienen como estables y no se deben quitar de la lista de símbolos ni modificar después de que se congeló la rama.

Cada rama del kernel de KMI del kernel común de Android (ACK) tiene su propio conjunto de listas de símbolos. No se intenta proporcionar estabilidad de ABI entre diferentes ramas del kernel de KMI. Por ejemplo, el KMI de android12-5.10 es completamente independiente del KMI de android13-5.10.

Las herramientas de ABI usan listas de símbolos de KMI para limitar las interfaces que se deben supervisar en cuanto a estabilidad. Se espera que los proveedores envíen y actualicen sus propias listas de símbolos para verificar que las interfaces en las que se basan mantengan la compatibilidad con la ABI. Por ejemplo, para ver una lista de listas de símbolos del kernel android16-6.12, consulta https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols.

Una lista de símbolos contiene los símbolos que se indican como necesarios para el proveedor o dispositivo en particular. La lista completa que usan las herramientas es la unión de todos los archivos de la lista de símbolos del KMI. Las herramientas de ABI determinan los detalles de cada símbolo, incluida la firma de la función y las estructuras de datos anidadas.

Cuando la KMI está congelada, no se permiten cambios en las interfaces existentes de la KMI, ya que son estables. Sin embargo, los proveedores pueden agregar símbolos a la KMI en cualquier momento, siempre y cuando las adiciones no afecten la estabilidad de la ABI existente. Los símbolos recién agregados se mantienen estables en cuanto se citan en una lista de símbolos de KMI. No se deben quitar símbolos de una lista para un kernel, a menos que se pueda confirmar que ningún dispositivo se envió con una dependencia de ese símbolo.

Puedes generar una lista de símbolos de KMI para un dispositivo siguiendo las instrucciones de Cómo trabajar con listas de símbolos. Muchos socios envían una lista de símbolos por ACK, pero esto no es un requisito estricto. Si esto ayuda con el mantenimiento, puedes enviar varias listas de símbolos.

Extiende el KMI

Si bien los símbolos de la KMI y las estructuras relacionadas se mantienen estables (es decir, no se pueden aceptar cambios que interrumpan las interfaces estables en un kernel con una KMI inmovilizada), el kernel de GKI permanece abierto a extensiones para que los dispositivos que se lancen más adelante en el año no necesiten definir todas sus dependencias antes de que se inmovilice la KMI. Para extender la KMI, puedes agregar símbolos nuevos a la KMI para funciones del kernel exportadas nuevas o existentes, incluso si la KMI está congelada. También se pueden aceptar parches nuevos del kernel si no interrumpen la KMI.

Acerca de las interrupciones de KMI

Un kernel tiene fuentes y los archivos binarios se compilan a partir de esas fuentes. Las ramas del kernel supervisadas por la ABI incluyen una representación de la ABI actual de GKI (en forma de un archivo .stg). Después de compilar los archivos binarios (vmlinux, Image y cualquier módulo de GKI), se puede extraer una representación de la ABI de los archivos binarios. Cualquier cambio que se realice en un archivo fuente del kernel puede afectar los archivos binarios y, a su vez, también el .stg extraído. El análisis de cumplimiento de la ABI compara el archivo .stg confirmado con el que se extrajo de los artefactos de compilación y establece una etiqueta Lint-1 en el cambio en Gerrit si encuentra una diferencia semántica.

Cómo controlar las interrupciones de la ABI

Por ejemplo, el siguiente parche introduce una ruptura de ABI muy obvia:

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.

Cuando ejecutas la ABI de compilación con este parche aplicado, la herramienta se cierra con un código de error distinto de cero y muestra una diferencia de ABI similar a la siguiente:

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

Se detectaron diferencias en la ABI durante el tiempo de compilación

El motivo más común de los errores es cuando un controlador usa un símbolo nuevo del kernel que no se encuentra en ninguna de las listas de símbolos.

Si el símbolo no se incluye en tu lista de símbolos, primero debes verificar que se exporte con EXPORT_SYMBOL_GPL(symbol_name) y, luego, actualizar la lista de símbolos y la representación de la ABI. Por ejemplo, los siguientes cambios agregan la nueva función de FS incremental a la rama android-12-5.10, lo que incluye la actualización de la lista de símbolos y la representación de la ABI.

  • El ejemplo de cambio de función se encuentra en aosp/1345659.
  • El ejemplo de la lista de símbolos se encuentra en aosp/1346742.
  • El ejemplo de cambio en la representación de la ABI se encuentra en aosp/1349377.

Si el símbolo se exporta (ya sea por ti o se exportó anteriormente), pero ningún otro controlador lo usa, es posible que recibas un error de compilación similar al siguiente.

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 el problema, actualiza la lista de símbolos de KMI en el kernel y en el ACK (consulta Cómo actualizar la representación de la ABI). Para ver un ejemplo de cómo actualizar una lista de símbolos y la representación de la ABI en el ACK, consulta aosp/1367601.

Cómo resolver interrupciones de la ABI del kernel

Puedes controlar las interrupciones de la ABI del kernel refactorizando el código para no cambiar la ABI o actualizando la representación de la ABI. Usa el siguiente diagrama para determinar el mejor enfoque para tu situación.

Diagrama de flujo de interrupción de ABI

Figura 1: Resolución de interrupciones de ABI

Refactoriza el código para evitar cambios en la ABI

Haz todo lo posible para evitar modificar la ABI existente. En muchos casos, puedes refactorizar tu código para quitar los cambios que afectan la ABI.

  • Refactorización de los cambios en los campos de struct. Si un cambio modifica la ABI de una función de depuración, agrega un #ifdef alrededor de los campos (en las referencias de structs y fuentes) y asegúrate de que el CONFIG que se usa para el #ifdef esté inhabilitado para la defconfig de producción y gki_defconfig. Para ver un ejemplo de cómo se puede agregar una configuración de depuración a una estructura sin interrumpir la ABI, consulta este conjunto de parches.

  • Se refactorizaron las funciones para no cambiar el kernel principal. Si es necesario agregar funciones nuevas a ACK para admitir los módulos de socios, intenta refactorizar la parte de la ABI del cambio para evitar modificar la ABI del kernel. Para ver un ejemplo del uso de la ABI del kernel existente para agregar capacidades adicionales sin cambiar la ABI del kernel, consulta aosp/1312213.

Cómo corregir una ABI dañada en Gerrit de Android

Si no rompiste la ABI del kernel de forma intencional, debes investigar con la orientación que proporciona la herramienta de supervisión de la ABI. Las causas más comunes de las interrupciones son los cambios en las estructuras de datos y los cambios asociados en el CRC de los símbolos, o bien los cambios en las opciones de configuración que provocan cualquiera de los problemas mencionados anteriormente. Comienza por abordar los problemas que encontró la herramienta.

Puedes reproducir los hallazgos de la ABI de forma local. Consulta Cómo compilar el kernel y su representación de la ABI.

Acerca de las etiquetas de Lint-1

Si subes cambios a una rama que contiene una KMI inalterable o finalizada, los cambios deben pasar los análisis de cumplimiento y compatibilidad de la ABI para garantizar que los cambios en la representación de la ABI reflejen la ABI real y no contengan incompatibilidades (eliminación de símbolos o cambios de tipo).

Cada uno de estos análisis de ABI puede establecer la etiqueta Lint-1 y bloquear el envío de cambios hasta que se resuelvan todos los problemas o se anule la etiqueta.

Actualiza la ABI del kernel

Si es inevitable modificar la ABI, debes aplicar los cambios de código, la representación de la ABI y la lista de símbolos al ACK. Para que Lint quite el -1 y no interrumpa la compatibilidad con GKI, sigue estos pasos:

  1. Sube los cambios de código al ACK.

  2. Espera a recibir un Code-Review +2 para el conjunto de parches.

  3. Actualiza la representación de la ABI de referencia.

  4. Combina los cambios de código y el cambio de actualización de la ABI.

Sube los cambios de código de la ABI al ACK

La actualización de la ABI de ACK depende del tipo de cambio que se realice.

  • Si un cambio en la ABI se relaciona con una función que afecta las pruebas de CTS o VTS, el cambio se puede transferir a ACK tal como está. Por ejemplo:

  • Si un cambio de ABI es para una función que se puede compartir con el ACK, ese cambio se puede transferir al ACK tal como está. Por ejemplo, los siguientes cambios no son necesarios para las pruebas de CTS o VTS, pero se pueden compartir con el ACK:

  • Si un cambio en la ABI introduce una nueva función que no es necesario incluir en el ACK, puedes introducir los símbolos en el ACK usando un código auxiliar, como se describe en la siguiente sección.

Usa stubs para ACK

Los stubs solo deben ser necesarios para los cambios principales del kernel que no benefician al ACK, como los cambios de rendimiento y energía. En la siguiente lista, se detallan ejemplos de stubs y cherry-picks parciales en ACK para GKI.

  • Se agregó un código auxiliar de la función de aislamiento del núcleo (aosp/1284493). Las capacidades en ACK no son necesarias, pero los símbolos deben estar presentes en ACK para que tus módulos los usen.

  • Símbolo de marcador de posición para el módulo del proveedor (aosp/1288860).

  • Se realizó un cherry-pick solo de la ABI de la función de seguimiento de eventos mm por proceso (aosp/1288454). El parche original se seleccionó para ACK y, luego, se recortó para incluir solo los cambios necesarios para resolver la diferencia de ABI para task_struct y mm_event_count. Este parche también actualiza la enumeración mm_event_type para que contenga los miembros finales.

  • Cherry-pick parcial de los cambios en la ABI de la estructura térmica que requirieron más que solo agregar los nuevos campos de la ABI.

    • El parche aosp/1255544 resolvió las diferencias de ABI entre el kernel del socio y el ACK.

    • El parche aosp/1291018 corrigió los problemas funcionales que se encontraron durante las pruebas del GKI del parche anterior. La corrección incluyó la inicialización de la estructura del parámetro del sensor para registrar varias zonas térmicas en un solo sensor.

  • Cambios en la ABI de CONFIG_NL80211_TESTMODE (aosp/1344321). Este parche agregó los cambios necesarios en la estructura para la ABI y se aseguró de que los campos adicionales no causaran diferencias funcionales, lo que permitió a los socios incluir CONFIG_NL80211_TESTMODE en sus kernels de producción y seguir manteniendo el cumplimiento de la GKI.

Aplica la KMI durante el tiempo de ejecución

Los kernels de GKI usan las opciones de configuración TRIM_UNUSED_KSYMS=y y UNUSED_KSYMS_WHITELIST=<union of all symbol lists>, que limitan los símbolos exportados (como los símbolos exportados con EXPORT_SYMBOL_GPL()) a los que se enumeran en una lista de símbolos. Todos los demás símbolos no se exportan, y se rechaza la carga de un módulo que requiere un símbolo no exportado. Esta restricción se aplica en el momento de la compilación y se marcan las entradas faltantes.

Para fines de desarrollo, puedes usar una compilación del kernel de GKI que no incluya la reducción de símbolos (es decir, se pueden usar todos los símbolos que se suelen exportar). Para encontrar estas compilaciones, busca las compilaciones kernel_debug_aarch64 en ci.android.com.

Cómo aplicar la KMI con el control de versiones de módulos

Los kernels de la imagen genérica de kernel (GKI) usan el control de versiones de módulos (CONFIG_MODVERSIONS) como una medida adicional para aplicar el cumplimiento de la KMI en el tiempo de ejecución. El control de versiones del módulo puede provocar errores de no coincidencia de la verificación de redundancia cíclica (CRC) en el tiempo de carga del módulo si la KMI esperada de un módulo no coincide con la KMI de vmlinux. Por ejemplo, la siguiente es una falla típica que ocurre en el tiempo de carga del módulo debido a una discrepancia en el CRC del 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 del control de versiones del módulo

El control de versiones de los módulos es útil por los siguientes motivos:

  • El control de versiones del módulo detecta los cambios en la visibilidad de la estructura de datos. Si los módulos cambian las estructuras de datos opacas, es decir, las estructuras de datos que no forman parte de la KMI, se interrumpen después de los cambios futuros en la estructura.

    Por ejemplo, considera el campo fwnode en struct device. Este campo DEBE ser opaco para los módulos, de modo que no puedan realizar cambios en los campos de device->fw_node ni hacer suposiciones sobre su tamaño.

    Sin embargo, si un módulo incluye <linux/fwnode.h> (directa o indirectamente), el campo fwnode en struct device ya no es opaco para él. Luego, el módulo puede realizar cambios en device->fwnode->dev o device->fwnode->ops. Esta situación es problemática por varios motivos, como los siguientes:

    • Puede romper las suposiciones que el código del kernel principal hace sobre sus estructuras de datos internas.

    • Si una actualización futura del kernel cambia struct fwnode_handle (el tipo de datos de fwnode), el módulo ya no funcionará con el nuevo kernel. Además, stgdiff no mostrará ninguna diferencia porque el módulo interrumpe la KMI manipulando directamente las estructuras de datos internas de formas que no se pueden capturar solo inspeccionando la representación binaria.

  • Un módulo actual se considera incompatible con la KMI cuando un kernel nuevo incompatible lo carga en una fecha posterior. El control de versiones del módulo agrega una verificación en el tiempo de ejecución para evitar cargar accidentalmente un módulo que no sea compatible con la KMI del kernel. Esta verificación evita problemas de tiempo de ejecución difíciles de depurar y fallas del kernel que podrían deberse a una incompatibilidad no detectada en la KMI.

Si habilitas el control de versiones del módulo, evitarás todos estos problemas.

Cómo verificar si hay discrepancias en el CRC sin iniciar el dispositivo

stgdiff compara y registra las discrepancias de CRC entre los kernels junto con otras diferencias de ABI.

Además, una compilación completa del kernel con CONFIG_MODVERSIONS habilitado genera un archivo Module.symvers como parte del proceso de compilación normal. Este archivo tiene una línea para cada símbolo exportado por el kernel (vmlinux) y los módulos. Cada línea consta del valor de CRC, el nombre del símbolo, el espacio de nombres del símbolo, el nombre del módulo o vmlinux que exporta el símbolo y el tipo de exportación (por ejemplo, EXPORT_SYMBOL en comparación con EXPORT_SYMBOL_GPL).

Puedes comparar los archivos Module.symvers entre la compilación del GKI y tu compilación para verificar si hay diferencias en el CRC de los símbolos exportados por vmlinux. Si hay una diferencia en el valor de CRC en algún símbolo exportado por vmlinux y ese símbolo lo usa uno de los módulos que cargas en tu dispositivo, el módulo no se carga.

Si no tienes todos los artefactos de compilación, pero sí los archivos vmlinux del kernel del GKI y tu kernel, puedes comparar los valores de CRC de un símbolo específico ejecutando el siguiente comando en ambos kernels y comparando el resultado:

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

Por ejemplo, el siguiente comando verifica el valor de CRC del símbolo module_layout:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

Cómo resolver discrepancias de CRC

Sigue estos pasos para resolver una discrepancia de CRC cuando cargas un módulo:

  1. Compila el kernel de GKI y el kernel del dispositivo con la opción --kbuild_symtypes, como se muestra en el siguiente comando:

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

    Este comando genera un archivo .symtypes para cada archivo .o. Consulta KBUILD_SYMTYPES en Kleaf para obtener más información.

    Para Android 13 y versiones anteriores, compila el kernel de GKI y el kernel del dispositivo anteponiendo KBUILD_SYMTYPES=1 al comando que usas para compilar el kernel, como se muestra en el siguiente comando:

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

    Cuando se usa build_abi.sh,, la marca KBUILD_SYMTYPES=1 ya se establece de forma implícita.

  2. Busca el archivo .c en el que se exporta el símbolo con una discrepancia en el CRC con el siguiente comando:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. El archivo .c tiene un archivo .symtypes correspondiente en el GKI y en los artefactos de compilación del kernel del dispositivo. Ubica el archivo .symtypes con los siguientes comandos:

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

    En Android 13 y versiones anteriores, si se usan las secuencias de comandos de compilación heredadas, es probable que la ubicación sea out/$BRANCH/common o out_abi/$BRANCH/common.

    Cada archivo .symtypes es un archivo de texto sin formato que consta de descripciones de tipo y símbolo:

    • Cada línea tiene el formato key description, en el que la descripción puede hacer referencia a otras claves del mismo archivo.

    • Las claves como [s|u|e|t]#foo hacen referencia a [struct|union|enum|typedef] foo. Por ejemplo:

      t#bool typedef _Bool bool
      
    • Las claves sin el prefijo x# son solo nombres de símbolos. Por ejemplo:

      find_module s#module * find_module ( const char * )
      
  4. Compara los dos archivos y corrige todas las diferencias.

Lo mejor es generar symtypes con una compilación justo antes del cambio problemático y, luego, en el cambio problemático. Guardar todos los archivos significa que se pueden comparar de forma masiva.

Por ejemplo:

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

De lo contrario, solo compara los archivos específicos que te interesan.

Caso 1: Diferencias debido a la visibilidad del tipo de datos

Un nuevo #include puede extraer una nueva definición de tipo (por ejemplo, de struct foo) en un archivo fuente. En estos casos, su descripción en el archivo .symtypes correspondiente cambiará de un structure_type foo { } vacío a una definición completa.

Esto afectará a todas las CRC de todos los símbolos del archivo .symtypes cuyas descripciones dependan directa o indirectamente de la definición de tipo.

Por ejemplo, agregar la siguiente línea al archivo include/linux/device.h en tu kernel provoca discrepancias de CRC, una de las cuales es para module_layout():

 #include <linux/fwnode.h>

Si comparas el module/version.symtypes de ese símbolo, se exponen las siguientes diferencias:

 $ 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 ] ; }
  ...

Si el kernel de GKI tiene la definición de tipo completa, pero tu kernel no la tiene (algo muy poco probable), combina el kernel común de Android más reciente en tu kernel para que uses la base del kernel de GKI más reciente.

En la mayoría de los casos, el kernel de GKI no incluye la definición de tipo completa en .symtypes, pero tu kernel sí la incluye debido a directivas #include adicionales.

Resolución para Android 16 y versiones posteriores

Asegúrate de que el archivo fuente afectado incluya el encabezado de estabilización de la KABI de Android:

#include <linux/android_kabi.h>

Para cada tipo afectado, agrega ANDROID_KABI_DECLONLY(name); en el alcance global al archivo fuente afectado.

Por ejemplo, si la diferencia de symtypes era la siguiente:

--- 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)

Entonces, el problema es que struct ubuf_info ahora tiene una definición completa en symtypes. La solución es agregar una línea a drivers/android/vendor_hooks.c:

ANDROID_KABI_DECLONLY(ubuf_info);

Esto indica a gendwarfksyms que trate el tipo con nombre como indefinido en el archivo.

Una posibilidad más compleja es que el nuevo #include se encuentre en un archivo de encabezado. En este caso, es posible que debas distribuir diferentes conjuntos de invocaciones de macros ANDROID_KABI_DECLONLY en archivos fuente que incorporan indirectamente definiciones de tipos adicionales, ya que algunos de ellos ya podrían tener algunas de las definiciones de tipos.

Para facilitar la lectura, coloca esas invocaciones de macros cerca del comienzo del archivo fuente.

Resolución para Android 15 y versiones anteriores

A menudo, la solución consiste en ocultar el nuevo #include de genksyms.

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

De lo contrario, para identificar el #include que causa la diferencia, sigue estos pasos:

  1. Abre el archivo de encabezado que define el símbolo o el tipo de datos que tiene esta diferencia. Por ejemplo, edita include/linux/fwnode.h para struct fwnode_handle.

  2. Agrega el siguiente código en la parte superior del archivo de encabezado:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. En el archivo .c del módulo que tiene una discrepancia en el CRC, agrega lo siguiente como la primera línea antes de cualquiera de las líneas #include.

    #define CRC_CATCH 1
    
  4. Compila tu módulo. El error de tiempo de compilación resultante muestra la cadena del archivo de encabezado #include que provocó esta discrepancia de CRC. Por ejemplo:

    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"
    

    Uno de los vínculos en esta cadena de #include se debe a un cambio realizado en tu kernel, que falta en el kernel de GKI.

Caso 2: Diferencias debido a cambios en el tipo de datos

Si la discrepancia del CRC para un símbolo o tipo de datos no se debe a una diferencia en la visibilidad, se debe a cambios reales (adiciones, eliminaciones o cambios) en el tipo de datos en sí.

Por ejemplo, si realizas el siguiente cambio en tu kernel, se producirán varias discrepancias de CRC, ya que muchos símbolos se ven afectados indirectamente por este tipo de cambio:

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);

Una discrepancia en el CRC es para devm_of_platform_populate().

Si comparas los archivos .symtypes de ese símbolo, es posible que se vea de la siguiente manera:

 $ 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 el tipo cambiado, sigue estos pasos:

  1. Busca la definición del símbolo en el código fuente (por lo general, en archivos .h).

    • Para ver las diferencias de símbolos entre tu kernel y el kernel de GKI, ejecuta el siguiente comando para encontrar la confirmación:
    git blame
    • En el caso de los símbolos borrados (en los que se borra un símbolo en un árbol y también quieres borrarlo en el otro), debes buscar el cambio que borró la línea. Usa el siguiente comando en el árbol en el que se borró la línea:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. Revisa la lista de confirmaciones que se devolvió para ubicar el cambio o la eliminación. Es probable que la primera confirmación sea la que estás buscando. Si no es así, revisa la lista hasta que encuentres la confirmación.

  3. Después de identificar la confirmación, revierte el cambio en tu kernel o actualízalo para suprimir el cambio de CRC y súbelo a ACK para que se combine. Cada cambio residual en la ABI deberá revisarse para garantizar la seguridad y, si es necesario, se podrá registrar un cambio permitido.

Prefiere consumir el padding existente

Algunas estructuras en GKI se completan para permitir su extensión sin interrumpir los módulos existentes del proveedor. Si una confirmación ascendente (por ejemplo) agrega un miembro a una estructura de este tipo, es posible que se pueda cambiar para que consuma parte del padding. Luego, este cambio se oculta del cálculo del CRC.

La macro ANDROID_KABI_RESERVE estandarizada y autodescriptiva reserva un espacio de u64 (alineado). Se usa en lugar de una declaración de miembro.

Por ejemplo:

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

El padding se puede consumir sin afectar los CRC de los símbolos con ANDROID_KABI_USE (o ANDROID_KABI_USE2 o cualquier otra variante que se pueda definir).

El miembro sekret está disponible como si se hubiera declarado directamente, pero la macro en realidad se expande a un miembro de unión anónimo que contiene sekret, así como elementos que usa gendwarfksyms para mantener la estabilidad de symtype.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
Resolución para Android 16 y versiones posteriores

Los CRC se calculan con gendwarfksyms, que usa información de depuración de DWARF, por lo que admite tipos de C y Rust. La resolución varía según el tipo de cambio. Aquí tienes algunos ejemplos.

Enumeradores nuevos o modificados

A veces, se agregan nuevos enumeradores y, en ocasiones, también se ve afectado un valor de enumerador MAX o similar. Estos cambios son seguros si no "escapan" del GKI o si podemos asegurarnos de que los módulos del proveedor no pueden preocuparse por sus valores.

Por ejemplo:

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

La adición de TRY_HARDER y el cambio a OUTCOME_LIMIT se pueden ocultar del cálculo de CRC con invocaciones de macros en el alcance global:

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

Para facilitar la lectura, colócalos justo después de la definición de enum.

Un nuevo miembro de la estructura que ocupa un orificio existente

Debido a la alineación, habrá bytes sin usar entre urgent y scratch.

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

La adición de retry no afecta ningún desplazamiento de miembro existente ni el tamaño de la estructura. Sin embargo, puede afectar los CRC de los símbolos o la representación de la ABI, o ambos.

Esto lo ocultará del cálculo del CRC:

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

El miembro retry está disponible como si se hubiera declarado directamente, pero la macro en realidad se expande a un miembro de unión anónimo que contiene retry, así como elementos que usa gendwarfksyms para mantener la estabilidad de symtype.

Extensión de una estructura con miembros nuevos

A veces, los miembros se agregan al final de una estructura. Esto no afecta los desplazamientos de los miembros existentes ni a los usuarios existentes de la estructura que solo acceden a ella por puntero. El tamaño de la estructura afecta su CRC, y los cambios en este se pueden suprimir con una invocación de macro adicional en el alcance global, de la siguiente manera:

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

ANDROID_KABI_BYTE_SIZE(data, 16);

Para facilitar la lectura, colócalo justo después de la definición de struct.

Todos los demás cambios en un tipo o en el tipo de un símbolo

En ocasiones, puede haber cambios que no se incluyan en una de las categorías anteriores, lo que genera cambios en el CRC que no se pueden suprimir con las macros anteriores.

En estos casos, la descripción original de symtypes de un tipo o símbolo se puede proporcionar con una invocación de ANDROID_KABI_TYPE_STRING en el alcance global.

struct data {
        /* extensive changes */
};

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

Para mayor legibilidad, colócala justo después de la definición del tipo o el símbolo.

Resolución para Android 15 y versiones anteriores

Los cambios en el tipo y el tipo de símbolo deben ocultarse de genksyms. Esto se puede hacer controlando el preprocesamiento con __GENKSYMS__.

Las transformaciones de código arbitrarias se pueden expresar de esta manera.

Por ejemplo, para ocultar a un miembro nuevo que ocupa un hueco en una estructura existente, haz lo siguiente:

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