Supervisión ABI del núcleo de Android

Puede usar las herramientas de monitoreo de interfaz binaria (ABI) de la aplicación, disponibles en Android 11 y versiones posteriores, para estabilizar la ABI en el kernel de los kernels de Android. La herramienta recopila y compara las representaciones ABI de los binarios del kernel existentes ( vmlinux + módulos). Estas representaciones ABI son los archivos .xml y las listas de símbolos. La interfaz en la que la representación ofrece una vista se denomina interfaces del módulo del kernel (KMI). Puede utilizar las herramientas para realizar un seguimiento y mitigar los cambios en el KMI.

La herramienta de monitoreo ABI se desarrolla en AOSP y utiliza libabigail para generar y comparar representaciones.

Esta página describe las herramientas, el proceso de recopilación y análisis de representaciones ABI y el uso de dichas representaciones para brindar estabilidad a la ABI interna del kernel. Esta página también proporciona información para aportar cambios a los núcleos de Android.

Este directorio contiene las herramientas específicas para el análisis ABI. Úselo con los scripts de compilación proporcionados por build_abi.sh ).

Proceso

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

  1. Adquiera la cadena de herramientas, los scripts de compilación y las fuentes del núcleo a través del repo .
  2. Proporcione los requisitos previos (como la biblioteca libabigail y la colección de herramientas).
  3. Construya el núcleo y su representación ABI .
  4. Analice las diferencias de ABI entre la compilación y una referencia .
  5. Actualice la representación ABI (si es necesario) .
  6. Trabajar con listas de símbolos .

Las siguientes instrucciones funcionan para cualquier kernel que pueda compilar utilizando una cadena de herramientas compatible (como la cadena de herramientas Clang preconstruida). Los repo manifests están disponibles para todas las ramas comunes del kernel de Android y para varios kernels específicos de dispositivos, lo que garantiza que se use la cadena de herramientas correcta cuando crea una distribución de kernel para el análisis.

Utilice las herramientas de supervisión de ABI

1. Adquiera la cadena de herramientas, los scripts de compilación y las fuentes del núcleo a través del repositorio

Puede adquirir la cadena de herramientas, compilar scripts (estos scripts) y fuentes del kernel con repo . Para obtener documentación detallada, consulte la información correspondiente para compilar kernels de Android .

Para ilustrar el proceso, los siguientes pasos usan common-android12-5.10 , una rama del kernel de Android, que es el último kernel de GKI publicado en el momento de escribir este artículo. Para obtener esta rama a través de repo , ejecute lo siguiente:

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

2. Proporcionar requisitos previos

Las herramientas ABI utilizan libabigail , una biblioteca y una colección de herramientas, para analizar archivos binarios. Un conjunto adecuado de binarios precompilados viene con kernel-build-tools y se usa automáticamente con build_abi.sh .

Para utilizar las herramientas de nivel inferior (como dump_abi ), agregue las herramientas de compilación del kernel a PATH .

3. Construya el kernel y su representación ABI

En este punto, está listo para construir un kernel con la cadena de herramientas correcta y extraer una representación ABI de sus archivos binarios ( vmlinux + módulos).

Similar al proceso habitual de compilación del kernel de Android (usando build.sh ), este paso requiere ejecutar build_abi.sh .

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

Eso construye el núcleo y extrae la representación ABI en el subdirectorio out_abi . En este caso out/android12-5.10/dist/abi.xml es un enlace simbólico a out_abi/android12-5.10/dist/abi-<id>.xml . < id> se calcula ejecutando git describe contra el árbol de fuentes del kernel.

4. Analizar las diferencias de ABI entre la construcción y una representación de referencia

build_abi.sh analiza e informa cualquier diferencia de ABI cuando se proporciona una referencia a través de la variable de entorno ABI_DEFINITION . ABI_DEFINITION debe apuntar a un archivo de referencia relativo al árbol de fuentes del núcleo y se puede especificar en la línea de comando o, más comúnmente, como un valor en build.config . Lo siguiente proporciona un ejemplo:

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

En el comando anterior, build.config.gki.aarch64 define el archivo de referencia (como ABI_DEFINITION=android/abi_gki_aarch64.xml ) y diff_abi llama a abidiff para comparar la representación ABI recién generada con el archivo de referencia. build_abi.sh imprime la ubicación del informe y emite un breve informe para cualquier rotura de ABI. Si se detectan roturas, build_abi.sh finaliza y devuelve un código de salida distinto de cero.

5. Actualice la representación ABI (si es necesario)

Para actualizar la representación ABI, invoque build_abi.sh con el indicador --update . Actualiza el archivo abi.xml correspondiente definido por build.config . Para imprimir las diferencias de ABI debido a la actualización, invoque el script con --print-report . Asegúrese de incluir el informe en el mensaje de confirmación cuando actualice el archivo abi.xml .

6. Trabajar con listas de símbolos

Parametrice build_abi.sh con listas de símbolos KMI para filtrar símbolos durante la extracción ABI. Estos son archivos de texto sin formato que enumeran los símbolos del núcleo ABI relevantes. Por ejemplo, un archivo de lista de símbolos con el siguiente contenido limita el análisis ABI a los símbolos ELF con los nombres symbol1 y symbol2 :

[abi_symbol_list]
   symbol1
   symbol2

No se tienen en cuenta los cambios en otros símbolos ELF. Se puede especificar un archivo de lista de símbolos en el archivo de configuración build.config correspondiente con KMI_SYMBOL_LIST= como un archivo relativo al directorio fuente del kernel ( $KERNEL_DIR ). Para proporcionar un nivel de organización, puede especificar archivos de lista de símbolos adicionales mediante ADDITIONAL_KMI_SYMBOL_LISTS= en el archivo build.config . Esto especifica más archivos de lista de símbolos, relativos a $KERNEL_DIR ; separe varios nombres de archivo por espacios en blanco.

Para crear una lista de símbolos inicial o actualizar una existente , debe usar el script build_abi.sh con el --update-symbol-list .

Cuando el script se ejecuta con una configuración adecuada, construye el kernel y extrae los símbolos que se exportan desde los módulos vmlinux y GKI y que son requeridos por cualquier otro módulo en el árbol.

Considere la posibilidad de vmlinux exporte los siguientes símbolos (normalmente se hace a través de las macros EXPORT_SYMBOL* ):

  func1
  func2
  func3

Además, imagina que hay dos módulos de proveedores, modA.ko y modB.ko , que requieren los siguientes símbolos (en otras palabras, enumeran entradas de símbolos undefined en su tabla de símbolos):

 modA.ko:    func1 func2
 modB.ko:    func2

Desde el punto de vista de la estabilidad de ABI, func1 y func2 deben mantenerse estables, ya que los utiliza un módulo externo. Por el contrario, mientras se exporta func3 , ningún módulo lo utiliza activamente (en otras palabras, no es necesario). Por lo tanto, la lista de símbolos contiene solo func1 y func2 .

Para crear o actualizar una lista de símbolos existente, build_abi.sh debe ejecutarse de la siguiente manera:

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

En este ejemplo, build.config.device debe incluir varias opciones de configuración:

  • vmlinux debe estar en la lista de FILES .
  • KMI_SYMBOL_LIST debe estar configurado y apuntando a la lista de símbolos KMI para actualizar.
  • GKI_MODULES_LIST debe estar configurado y apuntando a la lista de módulos GKI. Esta ruta suele ser android/gki_aarch64_modules .

Trabajar con las herramientas ABI de nivel inferior

La mayoría de los usuarios solo necesitarán usar build_abi.sh . En algunos casos, puede ser necesario trabajar directamente con las herramientas ABI de nivel inferior. Los dos comandos utilizados por build_abi.sh , dump_abi y diff_abi , están disponibles para extraer y comparar archivos ABI. Consulte las siguientes secciones para conocer sus usos.

Cree representaciones ABI a partir de árboles kernel

Con un árbol del kernel de Linux con vmlinux integrado y módulos del kernel, la herramienta dump_abi crea una representación ABI utilizando la herramienta ABI seleccionada. Una invocación de muestra se ve así:

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

El archivo abi.xml contiene una representación ABI textual de la ABI observable combinada de vmlinux y los módulos del kernel en el directorio dado. Este archivo puede usarse para inspección manual, análisis adicional o como archivo de referencia para reforzar la estabilidad de ABI.

Comparar representaciones ABI

Las representaciones ABI creadas por dump_abi se pueden comparar con diff_abi . Use la misma herramienta abi para dump_abi y diff_abi . Una invocación de muestra se ve así:

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

El informe generado enumera los cambios ABI detectados que afectan al KMI. Los archivos especificados como baseline y new son representaciones ABI que se recopilaron con dump_abi . diff_abi propaga el código de salida de la herramienta subyacente y, por lo tanto, devuelve un valor distinto de cero cuando las ABI comparadas son incompatibles.

Filtrar representaciones y símbolos de KMI

Para filtrar representaciones creadas con dump_abi o para filtrar símbolos en comparación con diff_abi , use el parámetro --kmi-symbol-list , que lleva una ruta a un archivo 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

Trabajar con listas de símbolos

El KMI no incluye todos los símbolos en el kernel o incluso todos los más de 30,000 símbolos exportados. En cambio, los símbolos que pueden usar los módulos se enumeran explícitamente en un conjunto de archivos de lista de símbolos que se mantienen públicamente en la raíz del árbol del kernel. La unión de todos los símbolos en todos los archivos de lista de símbolos define el conjunto de símbolos KMI mantenido como estable. Un ejemplo de archivo de lista de símbolos es abi_gki_aarch64_db845c , que declara los símbolos necesarios para DragonBoard 845c .

Solo los símbolos enumerados en una lista de símbolos y sus estructuras y definiciones relacionadas se consideran parte de la KMI. Puede publicar cambios en sus listas de símbolos si los símbolos que necesita no están presentes. Una vez que las nuevas interfaces están en una lista de símbolos y son parte de la descripción de KMI, se mantienen estables y no deben eliminarse de la lista de símbolos ni modificarse después de que la rama esté congelada.

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

Las herramientas ABI usan listas de símbolos KMI para limitar qué interfaces deben monitorearse para la estabilidad. La lista principal de símbolos contiene los símbolos que requieren los módulos del kernel de GKI. Se espera que los proveedores envíen y actualicen listas de símbolos adicionales para garantizar que las interfaces en las que confían mantengan la compatibilidad con ABI. Por ejemplo, para ver una lista de listas de símbolos para android13-5.15 , consulte https://android.googlesource.com/kernel/common/+/refs/heads/android13-5.15/android

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

Cuando el KMI está congelado, no se permiten cambios en las interfaces KMI existentes; son estables Sin embargo, los proveedores pueden agregar símbolos a la KMI en cualquier momento, siempre que las adiciones no afecten la estabilidad de la ABI existente. Los símbolos recién agregados se mantienen estables tan pronto como se citan en una lista de símbolos de KMI. Los símbolos no deben eliminarse de una lista para un kernel a menos que se pueda confirmar que ningún dispositivo se ha enviado alguna vez con una dependencia de ese símbolo.

Puede generar una lista de símbolos KMI para un dispositivo mediante la utilidad build/abi/extract_symbols , que extrae las dependencias de símbolos de los artefactos de compilación *.ko . Esta utilidad agrega anotaciones a la salida en forma de comentarios, que son útiles para identificar a los usuarios de un símbolo. Al enviar la lista de símbolos a ACK, se recomienda mantener estos comentarios para facilitar el proceso de revisión. Para omitir comentarios, pase la opción --skip-module-grouping cuando ejecute el script extract_symbols . Muchos socios envían una lista de símbolos por ACK, pero este no es un requisito estricto. Si ayuda con el mantenimiento, puede enviar varias listas de símbolos.

Para obtener ayuda con la personalización de listas de símbolos y el uso de herramientas ABI de alto y bajo nivel para la depuración y el análisis detallado, consulte Supervisión ABI para kernels de Android .

Ampliar el KMI

Si bien los símbolos de KMI y las estructuras relacionadas se mantienen estables (lo que significa que no se pueden aceptar cambios que rompan las interfaces estables en un kernel con un KMI congelado), el kernel de GKI permanece abierto a extensiones para que los dispositivos que se envíen más adelante en el año no necesiten defina todas sus dependencias antes de que se congele el KMI. Para ampliar la KMI, puede agregar nuevos símbolos a la KMI para funciones de kernel exportadas nuevas o existentes, incluso si la KMI está congelada. También se aceptan nuevos parches del kernel si no rompen el KMI.

Acerca de las roturas de KMI

Un kernel tiene fuentes y un binario se construye en base a esas fuentes. Las ramas del núcleo supervisadas por ABI incluyen abi.xml que es una representación de la GKI ABI actual. Después de compilar el binario (el binario del kernel, vmlinux , Image más los módulos del kernel), se puede extraer un archivo abi.xml de los binarios. Cualquier cambio realizado en una fuente del kernel puede cambiar el binario y también puede cambiar el abi.xml extraído (el archivo que se extrae después de aplicar el cambio y construir el kernel). El analizador AbiAnalyzer compara semánticamente los dos archivos abi.xml y establece una etiqueta Lint-1 en el cambio si encuentra un problema.

Manejar roturas de ABI

Como ejemplo, el siguiente parche presenta una ruptura de ABI muy obvia:

 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 */

Cuando ejecuta build_abi.sh nuevamente con este parche aplicado, la herramienta sale con un código de error distinto de cero e informa una diferencia de ABI similar 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:
  [...]

Diferencias de ABI detectadas en el momento de la compilación

La razón más común de errores es cuando un controlador usa un nuevo símbolo del kernel que no está en ninguna de las listas de símbolos.

Si el símbolo no está incluido en la lista de símbolos ( android/abi_gki_aarch64 ), primero debe verificar que se haya exportado con EXPORT_SYMBOL_GPL( symbol_name ) y luego actualizar la representación ABI XML y la lista de símbolos. Por ejemplo, los siguientes cambios agregan la nueva función Incremental FS a la rama android-12-5.10 , que incluye la actualización de la lista de símbolos y la representación XML de ABI.

Si el símbolo se exporta (ya sea por usted o se exportó previamente) pero ningún otro controlador lo está utilizando actualmente, es posible que obtenga 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 resolverlo, actualice la lista de símbolos KMI tanto en su kernel como en el ACK (consulte Actualizar la representación ABI ). Para ver un ejemplo de cómo actualizar el ABI XML y la lista de símbolos en el ACK, consulte aosp/1367601 .

Resolver roturas de kernel ABI

Puede manejar las roturas de la ABI del kernel refactorizando el código para que no cambie la ABI o actualizando la representación de la ABI . Utilice el siguiente cuadro para determinar el mejor enfoque para su situación.

Diagrama de flujo de rotura de ABI

Figura 1. Resolución de rotura del ABI

Refactorizar código para evitar cambios ABI

Haga todo lo posible para evitar modificar la ABI existente. En muchos casos, puede refactorizar su código para eliminar los cambios que afectan la ABI.

  • Refactorización de cambios de campo de estructura. Si un cambio modifica la ABI para una función de depuración, agregue un #ifdef alrededor de los campos (en las referencias de estructuras y fuentes) y asegúrese de que la CONFIG utilizada para #ifdef esté deshabilitada para la producción defconfig 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, consulte este conjunto de parches .

  • Refactorización de características para no cambiar el kernel central. Si es necesario agregar nuevas funciones a ACK para admitir los módulos asociados, intente refactorizar la parte ABI del cambio para evitar modificar la ABI del kernel. Para ver un ejemplo del uso de la ABI del kernel existente para agregar funciones adicionales sin cambiar la ABI del kernel, consulte aosp/1312213 .

Arreglar un ABI roto en Android Gerrit

Si no rompió intencionalmente la ABI del kernel, entonces necesita investigar, usando la guía provista por las herramientas de monitoreo de la ABI. Las causas más comunes de interrupciones son funciones agregadas o eliminadas, estructuras de datos modificadas o cambios en la ABI causados ​​por agregar opciones de configuración que conducen a cualquiera de los mencionados anteriormente. Comience abordando los problemas encontrados por la herramienta.

Puede reproducir la prueba ABI localmente ejecutando el siguiente comando con los mismos argumentos que habría utilizado para ejecutar build/build.sh :

Este es un comando de ejemplo para los núcleos GKI:

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

Acerca de las etiquetas Lint-1

Si carga cambios en una rama que contiene un KMI congelado o finalizado, los cambios deben pasar el ABIAnalyzer para garantizar que los cambios no afecten el ABI estable de una manera incompatible. Durante este proceso, ABIAnalyzer busca el informe ABI que se crea durante la compilación (una compilación extendida que realiza la compilación normal y luego algunos pasos de extracción y comparación de ABI. Si ABIAnalyzer encuentra un informe que no está vacío, establece la etiqueta Lint-1 y el cambio se bloquea desde el envío hasta que se resuelve, hasta que el conjunto de parches recibe una etiqueta Lint+1.

Actualice la ABI del núcleo

Si necesita actualizar la representación ABI del kernel, debe actualizar el archivo abi.xml correspondiente en el árbol de fuentes del kernel. La forma más conveniente de hacer esto es usando build/build_abi.sh así:

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

Usa los mismos argumentos que hubieras usado para ejecutar build/build.sh . Esto actualiza el abi.xml correcto en el árbol de fuentes e imprime las diferencias detectadas. Como práctica, incluya el informe impreso (breve) en el mensaje de confirmación (al menos parcialmente).

Actualizar la representación ABI

Si es inevitable modificar la ABI, entonces su código cambia y la lista de símbolos y XML de la ABI deben aplicarse al ACK. Para que Lint elimine el -1 y no rompa la compatibilidad con GKI, realice los siguientes pasos:

  1. Cargue los cambios del código ABI en el ACK .

  2. Actualice los archivos ACK ABI .

  3. Combine los cambios de su código y el cambio de actualización de ABI.

Subir cambios de código ABI al ACK

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

  • Si un cambio de ABI está relacionado con una función que afecta las pruebas de CTS o VTS, el cambio generalmente se puede seleccionar para ACK tal como está. Por ejemplo:

  • Si un cambio de ABI es para una característica que se puede compartir con el ACK, ese cambio se puede seleccionar para ACK tal como está. Por ejemplo, los siguientes cambios no son necesarios para la prueba CTS o VTS, pero se pueden compartir con ACK:

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

Usar stubs para ACK

Los stubs deben ser necesarios solo para cambios en el kernel central que no beneficien al ACK, como cambios en el rendimiento y la potencia. La siguiente lista detalla ejemplos de stubs y selecciones selectivas parciales en ACK para GKI.

  • Código auxiliar de función de aislamiento de núcleo ( aosp/1284493 ). La funcionalidad en ACK no es necesaria, pero los símbolos deben estar presentes en ACK para que sus módulos usen estos símbolos.

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

  • Selección exclusiva de 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 contener los miembros finales.

  • Selección parcial de cambios ABI de estructura térmica que requerían más que solo agregar los nuevos campos ABI.

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

    • El parche aosp/1291018 solucionó los problemas funcionales encontrados durante las pruebas de GKI del parche anterior. La solución incluía la inicialización de la estructura de parámetros del sensor para registrar múltiples zonas térmicas en un solo sensor.

  • Cambios en CONFIG_NL80211_TESTMODE ABI ( aosp/1344321 ). Este parche agregó los cambios de estructura necesarios para 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 aún mantener el cumplimiento de GKI.

Actualizar archivos ACK ABI

Para actualizar los archivos ACK ABI:

  1. Cargue los cambios de ABI y espere a recibir una revisión de código +2 para el conjunto de parches.

  2. Actualice los archivos 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 la actualización de ABI:

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

    $DIST_DIR/abi.report.short contiene un breve informe de los cambios. El uso del indicador -F con git commit usa automáticamente el informe para el texto de confirmación, que luego puede editar para agregar una línea de asunto (o recortar si el mensaje es demasiado largo).

Ramas del kernel de Android con ABI predefinido

Algunas ramas del kernel vienen con representaciones ABI predefinidas para Android como parte de su distribución de código fuente. Estas representaciones de ABI pretenden ser precisas y reflejar el resultado de build_abi.sh como si fuera a ejecutarlo por su cuenta. Como la ABI está fuertemente influenciada por varias opciones de configuración del kernel, estos archivos .xml generalmente pertenecen a una determinada configuración. Por ejemplo, la rama common-android12-5.10 contiene un abi_gki_aarch64.xml que corresponde al resultado de la compilación cuando se usa build.config.gki.aarch64 . En particular, build.config.gki.aarch64 también hace referencia a este archivo a través de ABI_DEFINITION .

Dichas representaciones ABI predefinidas se utilizan como una definición de referencia cuando se compara con diff_abi . Por ejemplo, para validar un parche del kernel con respecto a cualquier cambio en la ABI, cree la representación de la ABI con el parche aplicado y use diff_abi para compararlo con la ABI esperada para esa configuración o árbol fuente en particular. Si se establece ABI_DEFINITION , ejecutar build_abi.sh en consecuencia servirá.

Hacer cumplir el KMI en tiempo de ejecución

Los núcleos de GKI utilizan 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 mediante EXPORT_SYMBOL_GPL() ) a los enumerados en una lista de símbolos. Todos los demás símbolos no se exportan y se deniega la carga de un módulo que requiera 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, puede usar una compilación de kernel de GKI que no incluye el recorte de símbolos (lo que significa que se pueden usar todos los símbolos que normalmente se exportan). Para ubicar estas compilaciones, busque las compilaciones kernel_debug_aarch64 en ci.android.com .

Hacer cumplir el KMI usando el control de versiones del módulo

Los kernels Generic Kernel Image (GKI) utilizan versiones de módulos ( CONFIG_MODVERSIONS ) como una medida adicional para hacer cumplir el KMI en tiempo de ejecución. El control de versiones del módulo puede causar fallas de desajuste de verificación de redundancia cíclica (CRC) en el momento de la carga del módulo si el KMI esperado de un módulo no coincide con el KMI de vmlinux . Por ejemplo, la siguiente es una falla típica que ocurre en el momento de la carga del módulo debido a una discrepancia de CRC para el 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 de módulos

El control de versiones del módulo es útil por las siguientes razones:

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

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

    Sin embargo, si un módulo incluye <linux/fwnode.h> (directa o indirectamente), entonces el campo fwnode en el struct device ya no es opaco para él. Luego, el módulo puede realizar cambios en device->fwnode->dev o device->fwnode->ops . Este escenario es problemático por varias razones, enunciadas a continuación:

    • Puede romper las suposiciones que el código del kernel central está haciendo sobre sus estructuras de datos internas.

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

  • Un módulo actual se considera incompatible con KMI cuando se carga en una fecha posterior mediante un nuevo kernel que es incompatible. El control de versiones del módulo agrega una verificación en tiempo de ejecución para evitar cargar accidentalmente un módulo que no es compatible con KMI con el kernel. Esta verificación evita problemas de tiempo de ejecución difíciles de depurar y bloqueos del kernel que pueden resultar de una incompatibilidad no detectada en el KMI.

  • abidiff tiene limitaciones para identificar las diferencias de ABI en ciertos casos complicados que CONFIG_MODVERSIONS puede detectar.

Habilitar el control de versiones del módulo evita todos estos problemas.

Compruebe si hay discrepancias de CRC sin arrancar el dispositivo

abidiff compara e informa sobre discrepancias de CRC entre núcleos. Esta herramienta le permite detectar discrepancias de CRC al mismo tiempo que otras diferencias de ABI.

Además, una compilación completa del núcleo con CONFIG_MODVERSIONS habilitada 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 CRC, el nombre del símbolo, el espacio de nombres del símbolo, el vmlinux o el nombre del módulo que exporta el símbolo y el tipo de exportación (por ejemplo, EXPORT_SYMBOL frente EXPORT_SYMBOL_GPL ).

Puede comparar los archivos Module.symvers entre la compilación GKI y su compilación para verificar si hay diferencias de CRC en los símbolos exportados por vmlinux . Si hay una diferencia de valor de CRC en cualquier símbolo exportado por vmlinux y ese símbolo es utilizado por uno de los módulos que carga en su dispositivo, el módulo no se carga.

Si no tiene todos los artefactos de compilación, pero tiene los archivos vmlinux del kernel GKI y su kernel, puede comparar los valores de CRC para 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 para el símbolo module_layout :

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

Resolver discrepancias de CRC

Utilice los siguientes pasos para resolver una discrepancia de CRC al cargar un módulo:

  1. Compile el kernel de GKI y el kernel de su dispositivo KBUILD_SYMTYPES=1 al comando que usa para compilar el kernel, como se muestra en el siguiente comando:

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

    Este comando genera un archivo .symtypes para cada archivo .o . Cuando se usa build_abi.sh, el KBUILD_SYMTYPES=1 ya está establecido implícitamente.

  2. Busque el archivo .c en el que se exporta el símbolo con discrepancia CRC, utilizando el siguiente comando:

    cd common && git grep EXPORT_SYMBOL.*module_layout
    kernel/module.c:EXPORT_SYMBOL(module_layout);
    
  3. El archivo .c tiene un archivo .symtypes correspondiente en el GKI y los artefactos de compilación del kernel de su dispositivo. Localice el archivo .c usando los siguientes comandos:

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

    Las siguientes son las características del archivo .c :

    • El formato del archivo .c es una línea (potencialmente muy larga) por símbolo.

    • [s|u|e|etc]# al comienzo de la línea significa que el símbolo es del tipo de datos [struct|union|enum|etc] . Por ejemplo:

      t#bool typedef _Bool bool
      
    • Un prefijo # faltante al comienzo de la línea indica que el símbolo es una función. Por ejemplo:

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

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

Si un kernel mantiene un símbolo o tipo de datos opaco a los módulos y el otro kernel no, esa diferencia aparece entre los archivos .symtypes de los dos kernels. El archivo .symtypes de uno de los kernels tiene UNKNOWN para un símbolo y el archivo .symtypes del otro kernel tiene una vista ampliada del símbolo o tipo de datos.

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

 #include <linux/fwnode.h>

Al comparar los tipos de module.symtypes para ese símbolo, se exponen las siguientes diferencias:

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

Si su kernel tiene un valor de UNKNOWN y el kernel GKI tiene la vista ampliada del símbolo (muy poco probable), combine el kernel común de Android más reciente en su kernel para que esté utilizando la base del kernel GKI más reciente.

En la mayoría de los casos, el kernel GKI tiene un valor de UNKNOWN , pero su kernel tiene los detalles internos del símbolo debido a los cambios realizados en su kernel. Esto se debe a que uno de los archivos en su kernel agregó un #include que no está presente en el kernel GKI.

Para identificar el #include que causa la diferencia, siga estos pasos:

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

  2. Agregue 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 de CRC, agregue lo siguiente como la primera línea antes de cualquiera de las líneas #include .

    #define CRC_CATCH 1
    
  4. Compile su módulo. El error de tiempo de compilación resultante muestra la cadena del archivo de encabezado #include que condujo a 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 enlaces en esta cadena de #include se debe a un cambio realizado en su kernel, que falta en el kernel GKI.

  5. Identifique el cambio, reviértalo en su kernel o cárguelo en ACK y combínelo .

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

Si la discrepancia de 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 lo general, abidiff esto, pero si omite alguno debido a lagunas de detección conocidas, el mecanismo MODVERSIONS puede detectarlo.

Por ejemplo, realizar el siguiente cambio en su kernel provoca varios errores de coincidencia 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 de CRC es para devm_of_platform_populate() .

Si compara los archivos .symtypes para ese símbolo, podría verse así:

 $ 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 el tipo modificado, siga estos pasos:

  1. Busque la definición del símbolo en el código fuente (normalmente en archivos .h ).

    • Para diferencias de símbolos simples entre su kernel y el kernel GKI, busque la confirmación ejecutando el siguiente comando:
    git blame
    
    • Para los símbolos eliminados (donde un símbolo se elimina en un árbol y también desea eliminarlo en el otro árbol), debe encontrar el cambio que eliminó la línea. Use el siguiente comando en el árbol donde se eliminó la línea:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
    
  2. Revise la lista de confirmaciones devuelta para localizar el cambio o la eliminación. La primera confirmación es probablemente la que está buscando. Si no es así, revisa la lista hasta que encuentres la confirmación.

  3. Después de identificar el cambio, revertirlo en su kernel o cargarlo en ACK y fusionarlo .