API privada (aplicada desde la API 24)
Las bibliotecas privadas deben usar únicamente
API públicas y no deben establecer vínculos con bibliotecas de plataformas no incluidas en el NDK. A partir de la API 24, esta regla rige y las aplicaciones ya no pueden cargar bibliotecas de plataformas no relacionadas con el NDK. La regla se aplica a través del vinculador dinámico. Por lo tanto, independientemente de la manera en que se intente cargar bibliotecas públicas a través del código, no es posible acceder a ellas: Las entradas System.loadLibrary(...), DT_NEEDED y las llamadas directas a dlopen(...) experimentarán errores de la misma manera.
La experiencia de los usuarios con la aplicación debe ser uniforme en todas las actualizaciones, y los desarrolladores no deben llevar a cabo actualizaciones de emergencia de aplicaciones para manejar cambios de plataforma. Por ese motivo, recomendamos no usar símbolos de C/C++ privados. Los símbolos privados no se comprueban como parte del conjunto de pruebas de compatibilidad (CTS) que deben superar todos los dispositivos Android. Es posible que no existan o que se comporten de manera diferente. Esto aumenta las probabilidades de que las aplicaciones en las cuales se usan experimenten errores en dispositivos específicos o en versiones futuras, lo que les sucedió a muchos desarrolladores cuando en Android 6.0 Marshmallow se realizó un cambio de OpenSSL a BoringSSL.
Con el propósito de reducir el impacto de esta transición para los usuarios, hemos identificado un conjunto de bibliotecas que se usa mucho en las aplicaciones más instaladas de Google Play y para las cuales podemos brindar compatibilidad en el corto plazo (incluidas libandroid_runtime.so, libcutils.so, libcrypto.so y libssl.so). A fin de darte más tiempo para la transición, por el momento ofreceremos compatibilidad para estas bibliotecas. Por ello, si ves una advertencia en la cual se indique que tu código no funcionará en una versión futura, corrígelo cuanto antes.
$ readelf --dynamic libBroken.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [libnativehelper.so]
0x00000001 (NEEDED) Shared library: [libutils.so]
0x00000001 (NEEDED) Shared library: [libstagefright_foundation.so]
0x00000001 (NEEDED) Shared library: [libmedia_jni.so]
0x00000001 (NEEDED) Shared library: [liblog.so]
0x00000001 (NEEDED) Shared library: [libdl.so]
0x00000001 (NEEDED) Shared library: [libz.so]
0x00000001 (NEEDED) Shared library: [libstdc++.so]
0x00000001 (NEEDED) Shared library: [libm.so]
0x00000001 (NEEDED) Shared library: [libc.so]
Posibles problemas: a partir de la API 24, el vinculador dinámico no cargará bibliotecas privadas. Esto evitará que se cargue la aplicación.
Resolución: vuelve a escribir tu código nativo de modo que use únicamente API públicas. Como solución transitoria, se pueden copiar al proyecto las bibliotecas de plataformas sin dependencias complejas (libcutils.so). La solución a largo plazo implica copiar el código correspondiente al árbol de proyectos. No se debe acceder a las API de SSL, medios y clases internal y binder de JNI desde el código nativo. Cuando sea necesario, el código nativo debe llamar a los métodos de API de Java públicas correspondientes.
Se encuentra disponible una lista completa de bibliotecas públicas dentro del NDK, en
platforms/android-API/usr/lib.
Nota: SSL/crypto es un caso especial. En las aplicaciones, NO deben usarse la plataforma libcrypto ni las bibliotecas libssl de manera directa, incluso en plataformas anteriores. En todas las aplicaciones, debe usarse el
proveedor de seguridad GMS para garantizar la protección de estas contra vulnerabilidades conocidas.
Encabezados de sección faltantes (aplicados desde la API 24)
Cada archivo ELF contiene información adicional en los encabezados de sección. Estos encabezados deben estar presentes, ya que el vinculador dinámico los usa para comprobaciones. Algunos desarrolladores intentan eliminarlos en un intento por ocultar el ejecutable y evitar la ingeniería inversa. (Esto en verdad no sirve porque es posible reconstruir la información eliminada a través de herramientas ampliamente disponibles).
$ readelf --header libBroken.so | grep 'section headers'
Start of section headers: 0 (bytes into file)
Size of section headers: 0 (bytes)
Number of section headers: 0
$
Resolución: quita de tu compilación los pasos adicionales con los cuales se eliminen los encabezados de sección.
Reubicaciones de texto (aplicadas desde la API 23)
A partir de la API 23, los objetos compartidos no deben contener reubicaciones de texto. Es decir, el código debe cargarse como esté y no debe modificarse. Este enfoque reduce el tiempo de carga y mejora la seguridad.
Las reubicaciones de texto en general se deben al ensamblador de redacción manual y no independiente de la posición. Esto no es común. Usa la
herramienta scanelf según lo descrito en
nuestra documentación para ampliar el diagnóstico
:
$ scanelf -qT libTextRel.so
libTextRel.so: (memory/data?) [0x15E0E2] in (optimized out: previous simd_broken_op1) [0x15E0E0]
libTextRel.so: (memory/data?) [0x15E3B2] in (optimized out: previous simd_broken_op2) [0x15E3B0]
[skipped the rest]
Si no dispones de la herramienta scanelf, es posible realizar una verificación básica con readelf como alternativa. Busca una entrada o el marcador de TEXTREL. Con cualquiera bastará. (El valor correspondiente a la entrada de TEXTREL es irrelevante y normalmente equivale a 0; con la simple presencia de la entrada de TEXTREL se declara que .so contiene reubicaciones de texto). En este ejemplo, se encuentran ambos indicadores:
$ readelf --dynamic libTextRel.so | grep TEXTREL
0x00000016 (TEXTREL) 0x0
0x0000001e (FLAGS) SYMBOLIC TEXTREL BIND_NOW
$
Nota: es técnicamente posible disponer de un objeto compartido con la entrada o el marcador de TEXTREL sin reubicaciones de texto reales. Esto no sucede con el NDK, pero si generas archivos ELF por tu cuenta asegúrate de que en ellos no se soliciten reubicaciones de texto, ya que el vinculador dinámico de Android se rige por la entrada o el marcador.
Posibles problemas: Las reubicaciones imponen páginas de código editables y aumentan, de manera poco económica, el número de páginas desfasadas en la memoria. El vinculador dinámico ha emitido advertencias sobre reubicaciones de texto desde Android K (API 19), y a partir de la API 23 no carga código que las contenga.
Resolución: reescribe el ensamblador para que sea independiente de la posición, a fin de garantizar que no se necesiten reubicaciones de texto. Mira la
documentación de Gentoo para hallar fórmulas.
Entradas DT_NEEDED no válidas (aplicadas desde la API 23)
Aunque las dependencias de bibliotecas (entradas DT_NEEDED de los encabezados ELF) pueden ser rutas de acceso absolutas, en Android esto no tiene sentido porque no puedes controlar el destino en el cual el sistema instalará tu biblioteca. Una entrada de DT_NEEDED debe ser igual al SONAME de la biblioteca que se necesita. Esto hará que el vinculador dinámico se encargue de hallar la biblioteca en el tiempo de ejecución.
Antes de la API 23, el vinculador dinámico de Android ignoraba la ruta de acceso completa y usaba solo el basename (la sección posterior a la última “/”) al consultar las bibliotecas requeridas. A partir de la API 23, el vinculador de tiempo de ejecución cumple a la perfección con el DT_NEEDED y, por lo tanto, no puede cargar la biblioteca si no está presente en la ubicación exacta del dispositivo especificada.
Algo aún peor... algunos sistemas de compilación tienen errores por los cuales insertan entradas DT_NEEDED que apuntan a un archivo en el host de
compilación, algo que no puede hallarse en el dispositivo.
$ readelf --dynamic libSample.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [libm.so]
0x00000001 (NEEDED) Shared library: [libc.so]
0x00000001 (NEEDED) Shared library: [libdl.so]
0x00000001 (NEEDED) Shared library:
[C:\Users\build\Android\ci\jni\libBroken.so]
$
Posibles problemas: antes de la API 23 se usaba el basename de la entrada DT_NEEDED, pero a partir de esta, el tiempo de ejecución de Android intenta cargar la biblioteca usando la ruta de acceso especificada, y esta no existe en el dispositivo. Hay cadenas de herramientas y sistemas de compilación de terceros dañados que usan una ruta de acceso en un host de compilación en lugar del SONAME.
Resolución: asegúrate de que se haga referencia a todas las bibliotecas requeridas únicamente a través de SONAME. Es mejor dejar que el vinculador de tiempo de ejecución las encuentre y cargue, ya que la ubicación puede cambiar de un dispositivo a otro.
SONAME faltante (en uso a partir de la API 23)
Cada objeto compartido ELF (“biblioteca nativa”) debe tener un atributo SONAME (nombre de objeto compartido). La cadena de herramientas del NDK agrega este atributo de manera predeterminada. Por lo tanto, su ausencia es señal de una mala configuración de la cadena de herramientas o del sistema de compilación. La ausencia de un SONAME puede ocasionar problemas en el tiempo de ejecución, como la carga de la biblioteca incorrecta. Se usa el nombre del archivo en su lugar cuando falta este atributo.
$ readelf --dynamic libWithSoName.so | grep SONAME
0x0000000e (SONAME) Library soname: [libWithSoName.so]
$
Posibles problemas: los conflictos de espacio de nombres pueden hacer que se cargue la biblioteca incorrecta en el tiempo de ejecución. Esto generará fallos cuando no se encuentren los símbolos necesarios o cuando uses una biblioteca incompatible con la ABI que no sea la esperada.
Resolución: el NDK actual genera el SONAME correcto de manera predeterminada. Asegúrate de usar el NDK actual y de no haber configurado tu sistema de compilación de modo que genere entradas de SONAME incorrectas (con la opción de vinculador -soname).
Recuerda que los códigos multiplataforma claros creados con un NDK actual no generarán problemas en Android N. Te sugerimos revisar tu compilación de código nativo para que produzca ejecutables correctos.