Seu pacote de depuração travou no Android 14? |Tecnologia Dewu

1. Fundo

Por que meu aplicativo está tão travado? Quem envenenou o código?

Um dia, de repente descobri que o pacote de depuração estava extremamente lento. Após o teste simples a seguir, descobri que havia um problema com o pacote de depuração no Android 14.

 

2. Registros de solução de problemas

Meios rotineiros de investigação

Usou o systrace e a ferramenta de rastreamento de pacote de depuração interna dutrace para solução de problemas.

Conclusão: A CPU está ociosa e o thread principal não está obviamente bloqueado. Parece que a execução do método puro é demorada.

Dúvidas encontradas

Não houve grande ganho na primeira etapa da solução de problemas, mas encontrei uma anomalia ao usar a ferramenta dutrace para solucionar problemas. Aqui está uma breve introdução ao princípio de implementação do dutrace:

Dutrace usa gancho inline para adicionar pontos de rastreamento antes e depois da execução do artmethod e então os exibe através da ferramenta perfetto ui. Tem as seguintes vantagens:

1. Suporte à análise offline do processo de execução de funções e da função demorada.

2. No processo de chamada de função de análise:

a. Você pode visualizar as chamadas de função de todo o processo (incluindo funções da estrutura);

b. Capacidade de especificar funções e threads monitorados para filtrar efetivamente rastros inúteis;

c. A configuração dinâmica não requer reempacotamento.

3. Você pode usar ferramentas de análise de UI prontas, incluindo chamadas de função de threads-chave do sistema, como tempo de renderização, bloqueio de thread, tempo de GC, etc., bem como operações de E/S, carga de CPU e outros eventos.

 

fluxograma

Ao conectar antes e depois da execução do método artístico, envolve o processamento de três situações de interpretação e execução do método artístico.

Intérprete de tempo de execução ART

  1. O intérprete C++, que é o intérprete tradicional baseado na estrutura de switch, geralmente só usa essa ramificação quando o ambiente de depuração, rastreamento de método e instruções não são suportados ou quando ocorre uma exceção no bytecode (como falha na verificação de bloqueio estruturado) .
  2. O intérprete rápido mterp, em sua essência, introduz uma tabela manipuladora para mapeamento de instruções e implementa a alternância rápida entre instruções por meio de montagem manuscrita, melhorando o desempenho do intérprete.
  3. Nterp é outra otimização do Mterp. O Nterp elimina a necessidade de manutenção de pilhas de código gerenciado, usa a mesma estrutura de stack frame do método Native, e todo o processo de execução de decodificação e tradução é implementado por código assembly, reduzindo ainda mais a lacuna de desempenho entre o intérprete e o código compilado.

Descobri uma anomalia aqui, ou seja, a interpretação e execução do Android 14 na verdade usa o método switch de interpretação e execução. Testei novamente os métodos de interpretação e execução de várias versões do Android. O Android 12 usa mterp, o Android 13 usa nterp e só mudará durante a depuração. Em teoria, o Android 14 também deveria usar o nterp. A seguir estão os métodos das versões 12, 13 e 14 para executar o backtrace.

 

 

 

Verifique se há dúvidas

Comecei a suspeitar que a execução do interpretador estava causando o atraso. Examinei o código-fonte
art/runtime/interpreter/mterp/nterp.cc e descobri que realmente havia alterações nele. use nterp. A seguir, tente provar que esse problema é causado.

 

 

isJavaDebuggable é controlado por RuntimeDebugState runtime_debug_state_ em runtime.cc. Podemos encontrar a instância de tempo de execução e modificar o atributo runtime_debug_state_ por meio do deslocamento. Depois de examinar o código-fonte, também podemos
defini-lo por meio 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;
}

Tentei verificar através do método acima, configurei o isJavaDebuggable do pacote de teste para false e ele ainda travou. Então, anulei minha conjectura de que o método de execução causou o atraso.

Solução de problemas nativos demorados

Suspeito que a execução do método nativie seja demorada. Tente usar o simpleperf novamente para localizar o problema.

Conclusão: Basicamente, é demorado explicar a pilha no código de execução e não há outra pilha especial.

 

Alvejando

DEBUG_JAVA_DEBUGGABLE

Em seguida, pense em começar pela fonte depurável e estreitar gradualmente o escopo para localizar as variáveis ​​​​de influência.

O depurável no AndroidManifest afeta o processo do sistema para iniciar um runtimeFlags em nosso processo.


O sexto parâmetro do método start em frameworks/base/core/java/android/os/Process.java é runtimeFlags. Se for debuggableFlag, runtimeFlags serão adicionados com os seguintes sinalizadores.

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

Precisamos modificar os parâmetros de inicialização do nosso processo. Então você precisa conectar o processo do sistema. Isso envolve fazer root no telefone, instalar algumas operações da estrutura do gancho e, em seguida, fazer algumas modificações nos parâmetros durante o início do processo do gancho.

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


                }
            }
        }
);

Desta vez houve alguns resultados óbvios. O pacote de teste runtimeflags não fica mais travado após a remoção de DEBUG_JAVA_DEBUGGABLE. O pacote de produção, incluindo os aplicativos no mercado de aplicativos, ficou travado após a adição da marca DEBUG_JAVA_DEBUGGABLE. Então pode-se provar que isso é causado pela variável DEBUG_JAVA_DEBUGGABLE.

Alvejando

DesotimizarBootImage

Continue no código-fonte para observar o 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;
  }

A lógica aqui é o impacto de DEBUG_JAVA_DEBUGGABLE, e SetRuntimeDebugState foi testado antes. Não é
o impacto de DEBUG_GENERATE_MINI_DEBUG_INFO É tempo de execução->DeoptimizeBootImage()? Então usei o pacote com debugable como false para chamar ativamente o método DeoptimizeBootImage por meio de _ZN3art7Runtime19DeoptimizeBootImageEv, e então ele reproduziu!

Análise de causa

DeoptimizeBootImage converte o método de código AOT em bootImage em java depurável. Reinicialize o ponto de entrada do método e caminhe para a execução interpretada sem usar código AOT. Voltando ao
método Instrumentation::InitializeMethodsCode, ainda chegamos ao ponto de CanUseNterp(method) CanRuntimeUseNterp. Além disso, o Android 13 pode usar nterp e o Android 14 só pode usar switch.

Conectei o código novamente e pedi ao CanRuntimeUseNterp para retornar diretamente true, mas ele ainda travou. Eu descobri isso mesmo que eu o fisgasse. Os métodos a seguir ainda servem para alternar interpretação e execução. Pensando ao contrário, é porque meu gancho ficou para trás e DeoptimizeBootImage foi executado. Quando o método básico é chamado, a opção é executada.

 

Usei o pacote true depurável do Android 13 para teste, primeiro conectei CanRuntimeUseNterp com retorno false e, em seguida, executei DeoptimizeBootImage, e o atraso reapareceu.

Posicionamento preliminar: O método na imagem de inicialização é nterp no Android 13 e o método switch no Android 14. O método na imagem de inicialização é muito básico e fragmentado, portanto, a execução do método switch consome muito tempo.

A verificação é um problema do sistema

Se for um problema de sistema, todos deverão encontrá-lo, não apenas nosso aplicativo tem esse problema, então encontrei alguns amigos para ajudar a verificar o problema com o pacote de depuração. Com certeza, todos eles têm esse problema. A experiência de instalar o mesmo pacote no Android 14 e no Android 13 é completamente inconsistente.

Pergunta de feedback

Alguém relatou no issuetracker que o pacote de depuração do Android 14 está lento
https://issuetracker.google.com/issues/311251587. Mas ainda não houve resultado, então compensei o problema que identifiquei.

 

A propósito, também levantei um problema
https://issuetracker.google.com/issues/328477628

3. Solução temporária

Enquanto aguardo a resposta do Google, também estou pensando em como a camada App pode evitar esse problema e fazer com que a experiência do pacote de depuração volte a ser suave, por exemplo, como reotimizar o método na imagem de inicialização. Com essa ideia em mente, estudei o código artístico novamente e descobri que o Android 14 adicionou um novo
método UpdateEntrypointsForDebuggable. Este método irá redefinir o método de execução do método de acordo com as regras, como aot e nterp. . True Se você chamar UpdateEntrypointsForDebuggable novamente, não irá para o nterp novamente?

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

Experimentei de acordo com a ideia acima e ficou muito mais suave! ! !

Na verdade, ainda existem alguns problemas remanescentes com a solução acima. Comparado com o pacote com depurável definido como falso, ainda há algum atraso. Também descobri que os métodos em bootImage foram para o nterp, mas a maior parte do código no apk ainda mudou de interpretação e execução, então mudei de ideia.
Tudo bem se eu definir RuntimeDebugState como não depurável antes de chamar UpdateEntrypointsForDebuggable e, em seguida, definir RuntimeDebugState como depurável após chamar UpdateEntrypointsForDebuggable? O código final é o seguinte. A estrutura do gancho usa 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

Recentemente também vi um artigo de um engenheiro da Qualcomm na comunidade. Ele fez uma análise mais detalhada com base no problema que identifiquei e confirmou que o Google corrigirá esse problema no Android 15. Se for uma versão estrangeira dos dispositivos Android 14, o Google. planeja corrigir esse problema por meio de uma atualização do módulo com.android.artapex. No entanto, devido a problemas de rede na China, o impulso do Google não pode funcionar, por isso cada fabricante de telefones celulares precisa incorporar ativamente essas duas mudanças. [1]

Se precisar resolver temporariamente o problema de pacotes depuráveis ​​travados, você também pode resolvê-lo através do método acima.

 

Artigo de referência:

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

 

*Texto/ Wuyou

 

Este artigo é original da Dewu Technology. Para artigos mais interessantes, consulte: Site oficial da Dewu Technology.

 

A reimpressão sem a permissão da Dewu Technology é estritamente proibida, caso contrário, a responsabilidade legal será processada de acordo com a lei!

Linus resolveu resolver o problema por conta própria para evitar que os desenvolvedores do kernel substituíssem tabulações por espaços. Seu pai é um dos poucos líderes que sabe escrever código, seu segundo filho é o diretor do departamento de tecnologia de código aberto e seu filho mais novo é um núcleo. contribuidor de código aberto Huawei: Demorou 1 ano para converter 5.000 aplicativos móveis comumente usados ​​A migração abrangente para Hongmeng Java é a linguagem mais propensa a vulnerabilidades de terceiros Wang Chenglu, o pai de Hongmeng: Hongmeng de código aberto é a única inovação arquitetônica. no campo de software básico na China. Ma Huateng e Zhou Hongyi apertam as mãos para "remover rancores". Ex-desenvolvedor da Microsoft: o desempenho do Windows 11 é "ridiculamente ruim" " Embora o que Laoxiangji seja de código aberto não seja o código, as razões por trás disso são muito emocionantes. Meta Llama 3 é lançado oficialmente. Google anuncia uma reestruturação em grande escala.
{{o.nome}}
{{m.nome}}

Acho que você gosta

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