¿Su paquete de depuración se ha atascado en Android 14? |Tecnología Dewu

1. Antecedentes

¿Por qué mi aplicación está tan bloqueada? ¿Quién envenenó el código?

Un día, de repente descubrí que el paquete de depuración se estaba ejecutando con mucha demora. Después de la siguiente prueba simple, descubrí que había un problema con el paquete de depuración en Android 14.

 

2. Registros de resolución de problemas

Medios rutinarios de investigación

Se utilizó systrace y la herramienta de seguimiento de paquetes de depuración interna dutrace para solucionar problemas.

Conclusión: la CPU está inactiva y el hilo principal no está obviamente bloqueado. Parece que la ejecución del método puro lleva mucho tiempo.

Dudas encontradas

No obtuve grandes ganancias en el primer paso de solución de problemas, pero encontré una anomalía cuando usé la herramienta dutrace para solucionar problemas. Aquí hay una breve introducción al principio de implementación de dutrace:

Dutrace utiliza un gancho en línea para agregar puntos de trazo antes y después de la ejecución de artmethod y luego lo muestra a través de la herramienta perfetto ui. Tiene las siguientes ventajas:

1. Admite el análisis fuera de línea del proceso de ejecución de funciones y las funciones que requieren mucho tiempo.

2. Bajo el proceso de llamada a la función de análisis:

a. Puede ver las llamadas a funciones de todo el proceso (incluidas las funciones del marco);

b. Capacidad para especificar funciones y subprocesos monitoreados para filtrar de manera efectiva rastros inútiles;

c. La configuración dinámica no requiere reempaquetado.

3. Puede utilizar herramientas de análisis de UI listas para usar, incluidas llamadas a funciones de subprocesos clave del sistema, como tiempo de renderizado, bloqueos de subprocesos, tiempo de GC, etc., así como operaciones de E/S, carga de CPU y otros eventos.

 

diagrama de flujo

Al enganchar antes y después de la ejecución del método artístico, implica procesar tres situaciones de interpretación y ejecución del método artístico.

Intérprete en tiempo de ejecución ART

  1. El intérprete de C++, que es el intérprete tradicional basado en la estructura de conmutación, generalmente solo toma esta rama cuando el entorno de depuración, el seguimiento de métodos, las instrucciones no son compatibles o cuando ocurre una excepción en el código de bytes (como una verificación fallida de bloqueo estructurado). .
  2. El intérprete rápido mterp, en esencia, introduce una tabla de controlador para el mapeo de instrucciones e implementa un cambio rápido entre instrucciones mediante ensamblaje escrito a mano, lo que mejora el rendimiento del intérprete.
  3. Nterp es otra optimización de Mterp. Nterp elimina la necesidad de mantenimiento de pilas de código administrado, utiliza la misma estructura de marco de pila que el método nativo y todo el proceso de ejecución de decodificación y traducción se implementa mediante código ensamblador, lo que reduce aún más la brecha de rendimiento entre el intérprete y el código compilado.

Descubrí una anomalía aquí, es decir, la interpretación y ejecución de Android 14 en realidad utiliza el método de interpretación y ejecución del interruptor. Volví a probar los métodos de interpretación y ejecución de varias versiones de Android. Android 12 usa mterp, Android 13 usa nterp y solo cambiará durante la depuración. En teoría, Android 14 también debería usar nterp. ¿Cómo es que usa el interruptor más lento? Los siguientes son los métodos de las versiones 12, 13 y 14 para ejecutar el rastreo.

 

 

 

Consulta por dudas

Comencé a sospechar que la ejecución del intérprete estaba causando el retraso. Miré el código fuente
art/runtime/interpreter/mterp/nterp.cc y descubrí que efectivamente había cambios en él. Si fuera javaDebuggable, no lo haría. utilizar interp. A continuación, intente demostrar la causa de este problema.

 

 

isJavaDebuggable está controlado por RuntimeDebugState runtime_debug_state_ en runtime.cc. Podemos encontrar la instancia de tiempo de ejecución y modificar el atributo runtime_debug_state_ a través del desplazamiento. Después de mirar el código fuente, también podemos
configurarlo a través de _ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE.

void Runtime::SetRuntimeDebugState(RuntimeDebugState state) {
  if (state != RuntimeDebugState::kJavaDebuggableAtInit) {
    // We never change the state if we started as a debuggable runtime.
    DCHECK(runtime_debug_state_ != RuntimeDebugState::kJavaDebuggableAtInit);
  }
  runtime_debug_state_ = state;
}

Intenté verificarlo mediante el método anterior. Configuré el isJavaDebuggable del paquete de prueba en falso y todavía se atascó. Configuré el isJavaDebuggable del paquete de producción en verdadero y se atascó un poco. Entonces anulé mi conjetura de que el método de ejecución causaba el retraso.

La resolución de problemas nativos requiere mucho tiempo

Sospecho que la ejecución del método nativie lleva mucho tiempo. Intente usar simpleperf nuevamente para localizar el problema.

Conclusión: Básicamente, explicar la pila en el código de ejecución lleva mucho tiempo y no existe otra pila especial.

 

Orientación

DEBUG_JAVA_DEBUGGABLE

Luego piense en comenzar desde la fuente depurable y reducir gradualmente el alcance para localizar las variables que influyen.

El depurable en AndroidManifest afecta el proceso del sistema para iniciar un runtimeFlags en nuestro proceso.


El sexto parámetro del método de inicio en frameworks/base/core/java/android/os/Process.java es runtimeFlags. Si es debuggableFlag, se agregarán runtimeFlags con los siguientes indicadores. Luego, primero se reducirá el rango de etiquetas.

 if (debuggableFlag) {
                runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
                runtimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;
                runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
                // Also turn on CheckJNI for debuggable apps. It's quite
                // awkward to turn on otherwise.
                runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;


                // Check if the developer does not want ART verification
                if (android.provider.Settings.Global.getInt(mService.mContext.getContentResolver(),
                        android.provider.Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE, 1) == 0) {
                    runtimeFlags |= Zygote.DISABLE_VERIFIER;
                    Slog.w(TAG_PROCESSES, app + ": ART verification disabled");
                }
            }

Necesitamos modificar los parámetros de inicio de nuestro proceso. Entonces necesitas conectar el proceso del sistema. Esto implica rootear el teléfono, instalar algunas operaciones del marco de enlace y luego realizar algunas modificaciones de parámetros hasta el inicio del proceso de enlace.

hookAllMethods(
        Process.class,
        "start",
        new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                final String niceName = (String) param.args[1];
                final int uid = (int) param.args[2];
                final int runtimeFlags = (int) param.args[5];
                XposedBridge.log("process_xx " + runtimeFlags);
                if (isDebuggable(niceName, user)) {
                    param.args[5] = runtimeFlags&~DEBUG_JAVA_DEBUGGABLE;
                    XposedBridge.log("process_xx " + param.args[5]);


                }
            }
        }
);

Esta vez hubo algunos resultados obvios. El paquete de prueba runtimeflags ya no se bloquea después de eliminar DEBUG_JAVA_DEBUGGABLE. El paquete de producción, incluidas las aplicaciones en el mercado de aplicaciones, se bloqueó después de agregar la marca DEBUG_JAVA_DEBUGGABLE. Entonces se puede demostrar que es causado por la variable DEBUG_JAVA_DEBUGGABLE.

Orientación

Desoptimizar imagen de arranque

Continúe con el código fuente para observar el impacto de DEBUG_JAVA_DEBUGGABLE.

if ((runtime_flags & DEBUG_JAVA_DEBUGGABLE) != 0) {
    runtime->AddCompilerOption("--debuggable");
    runtime_flags |= DEBUG_GENERATE_MINI_DEBUG_INFO;
    runtime->SetRuntimeDebugState(Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
    {
      // Deoptimize the boot image as it may be non-debuggable.
      ScopedSuspendAll ssa(__FUNCTION__);
      runtime->DeoptimizeBootImage();
    }
    runtime_flags &= ~DEBUG_JAVA_DEBUGGABLE;
    needs_non_debuggable_classes = true;
  }

La lógica aquí es el impacto de DEBUG_JAVA_DEBUGGABLE, y SetRuntimeDebugState se ha probado antes. No es
el impacto de DEBUG_GENERATE_MINI_DEBUG_INFO. ¿Es el tiempo de ejecución->DeoptimizeBootImage()? Así que utilicé el paquete con debugable como falso para llamar activamente al método DeoptimizeBootImage a través de _ZN3art7Runtime19DeoptimizeBootImageEv, ¡y luego se reprodujo!

Análisis de causa

DeoptimizeBootImage convierte el método de código AOT en bootImage en java depurable. Reinicialice el punto de entrada del método y avance hacia la ejecución interpretada sin utilizar código AOT. Volviendo al
método Instrumentation::InitializeMethodsCode, todavía llegamos al punto de CanUseNterp(método) CanRuntimeUseNterp. Además, Android 13 puede usar nterp y Android 14 solo puede usar switch.

Volví a conectar el código y le pedí a CanRuntimeUseNterp que devolviera verdadero directamente, pero aún así se bloqueó. Lo encontré incluso si lo enganché. Los siguientes métodos aún sirven para cambiar la interpretación y la ejecución. Pensándolo al revés, es porque mi gancho se quedó atrás y se ejecutó DeoptimizeBootImage. Cuando se llama al método básico, se ejecuta el cambio.

 

Utilicé el paquete verdadero depurable de Android 13 para realizar pruebas, primero enganché CanRuntimeUseNterp y devolví falso, luego ejecuté DeoptimizeBootImage y el retraso volvió a aparecer.

Posicionamiento preliminar: el método en la imagen de arranque es nterp en Android 13 y el método de cambio en Android 14. El método en la imagen de arranque es muy básico y fragmentado, por lo que la ejecución del método de cambio lleva mucho tiempo.

La verificación es un problema del sistema.

Si es un problema del sistema, entonces todos deberían encontrarlo, no solo nuestra aplicación tiene este problema, así que encontré algunos amigos para ayudar a verificar el problema con el paquete de depuración. Efectivamente, todos tienen este problema. La experiencia de instalar el mismo paquete en Android 14 y Android 13 es completamente inconsistente.

Pregunta de retroalimentación

Alguien informó en IssueTracker que el paquete de depuración de Android 14 es lento
https://issuetracker.google.com/issues/311251587. Pero aún no hubo resultado, así que solucioné el problema que identifiqué.

 

Por cierto, también planteé un problema
https://issuetracker.google.com/issues/328477628

3. Solución temporal

Mientras espero la respuesta de Google, también estoy pensando en cómo la capa de aplicación puede evitar este problema y hacer que la experiencia del paquete de depuración vuelva a ser fluida, por ejemplo, cómo volver a optimizar el método en la imagen de arranque. Con esta idea en mente, estudié el código artístico nuevamente y descubrí que Android 14 agregó un nuevo
método UpdateEntrypointsForDebuggable. Este método restablecerá el método de ejecución del método de acuerdo con las reglas, como aot y nterp. Luego conecté CanRuntimeUseNterp antes de regresar. . Verdadero Si vuelves a llamar a UpdateEntrypointsForDebuggable, ¿no volverás a nterp?

void Instrumentation::UpdateEntrypointsForDebuggable() {
  Runtime* runtime = Runtime::Current();
  // If we are transitioning from non-debuggable to debuggable, we patch
  // entry points of methods to remove any aot / JITed entry points.
  InstallStubsClassVisitor visitor(this);
  runtime->GetClassLinker()->VisitClasses(&visitor);
}

Lo probé de acuerdo con la idea anterior y ¡se volvió mucho más fluido! ! !

De hecho, todavía quedan algunos problemas con la solución anterior. En comparación con el paquete con depurable configurado en falso, todavía hay algo de retraso. También descubrí que los métodos en bootImage pasaron a nterp, pero la mayor parte del código en el apk aún cambió de interpretación y ejecución, así que cambié de opinión.
¿Está bien si configuro RuntimeDebugState como no depurable antes de llamar a UpdateEntrypointsForDebuggable y luego configuro RuntimeDebugState como depurable después de llamar a UpdateEntrypointsForDebuggable? El código final es el siguiente. El marco de enlace utiliza https://github.com/bytedance/android-inline-hook.

Java_test_ArtMethodTrace_bootImageNterp(JNIEnv *env,
                                                      jclass clazz) {
    void *handler = shadowhook_dlopen("libart.so");
    instance_ = static_cast<void **>(shadowhook_dlsym(handler, "_ZN3art7Runtime9instance_E"));
    jobject
    (*getSystemThreadGroup)(void *runtime) =(jobject (*)(void *runtime)) shadowhook_dlsym(handler,
                                                                                          "_ZNK3art7Runtime20GetSystemThreadGroupEv");
    void
    (*UpdateEntrypointsForDebuggable)(void *instrumentation) = (void (*)(void *i)) shadowhook_dlsym(
            handler,
            "_ZN3art15instrumentation15Instrumentation30UpdateEntrypointsForDebuggableEv");
    if (getSystemThreadGroup == nullptr || UpdateEntrypointsForDebuggable == nullptr) {
        LOGE("getSystemThreadGroup  failed ");
        shadowhook_dlclose(handler);
        return;
    }
    jobject thread_group = getSystemThreadGroup(*instance_);
    int vm_offset = findOffset(*instance_, 0, 4000, thread_group);
    if (vm_offset < 0) {
        LOGE("vm_offset not found ");
        shadowhook_dlclose(handler);
        return;
    }
    void (*setRuntimeDebugState)(void *instance_, int r) =(void (*)(void *runtime,
                                                                    int r)) shadowhook_dlsym(
            handler, "_ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE");
    if (setRuntimeDebugState != nullptr) {
        setRuntimeDebugState(*instance_, 0);
    }
    void *instrumentation = reinterpret_cast<void *>(reinterpret_cast<char *>(*instance_) +
                                                     vm_offset - 368 );

    UpdateEntrypointsForDebuggable(instrumentation);
    setRuntimeDebugState(*instance_, 2);
    shadowhook_dlclose(handler);
    LOGE("bootImageNterp success");


}

4. Finalmente

Recientemente, también vi un artículo de un ingeniero de Qualcomm en la comunidad. Hizo un análisis más detallado basado en el problema que identifiqué y confirmó que Google solucionará este problema en Android 15. Si se trata de una versión extranjera de dispositivos con Android 14, Google. planea solucionar este problema mediante una actualización del módulo com.android.artapex. Sin embargo, debido a problemas de red en China, el impulso de Google no puede funcionar, por lo que cada fabricante de teléfonos móviles debe incorporar activamente estos dos cambios. [1]

Si necesita resolver temporalmente el problema de los paquetes depurables atascados, también puede resolverlo mediante el método anterior.

 

Artículo de referencia:

[1] https://juejin.cn/post/7353106089296789556

 

*Texto/ Wuyou

 

Este artículo es original de Dewu Technology. Para obtener más artículos interesantes, consulte: Sitio web oficial de Dewu Technology.

 

La reimpresión sin el permiso de Dewu Technology está estrictamente prohibida; de lo contrario, se perseguirá la responsabilidad legal de acuerdo con la ley.

Linus tomó el asunto en sus propias manos para evitar que los desarrolladores del kernel reemplacen las pestañas con espacios. Su padre es uno de los pocos líderes que puede escribir código, su segundo hijo es el director del departamento de tecnología de código abierto y su hijo menor es un núcleo. Colaborador de código abierto Huawei: tomó 1 año convertir 5000 aplicaciones móviles de uso común Migración completa a Hongmeng Java es el lenguaje más propenso a vulnerabilidades de terceros Wang Chenglu, el padre de Hongmeng: el código abierto Hongmeng es la única innovación arquitectónica. En el campo del software básico en China, Ma Huateng y Zhou Hongyi se dan la mano para "eliminar rencores". Ex desarrollador de Microsoft: el rendimiento de Windows 11 es "ridículamente malo " " Aunque lo que Laoxiangji es de código abierto no es el código, las razones detrás de él. Son muy conmovedores. Meta Llama 3 se lanza oficialmente. Google anuncia una reestructuración a gran escala.
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/5783135/blog/11054175
Recomendado
Clasificación