Como posso detectar SIGSEGV (falha de segmentação) e obter um rastreamento de pilha em JNI no Android?

92

Estou movendo um projeto para o novo Android Native Development Kit (ou seja, JNI) e gostaria de capturar o SIGSEGV, caso ocorra (possivelmente também SIGILL, SIGABRT, SIGFPE) para apresentar um bom diálogo de relatório de falha, em vez de (ou antes) o que acontece atualmente: a morte sem cerimônia imediata do processo e possivelmente alguma tentativa do SO de reiniciá-lo. ( Editar: O JVM / Dalvik VM capta o sinal e registra um rastreamento de pilha e outras informações úteis; eu só quero oferecer ao usuário a opção de enviar essas informações para mim, na verdade.)

A situação é: um grande corpo de código C que eu não escrevi faz a maior parte do trabalho neste aplicativo (toda a lógica do jogo) e embora seja bem testado em várias outras plataformas, é inteiramente possível que eu, no meu Android porta, irá alimentá-lo com lixo e causar uma falha no código nativo, então eu quero os despejos de memória (nativos e Java) que atualmente aparecem no log do Android (acho que seria stderr em uma situação não-Android). Estou livre para modificar o código C e Java arbitrariamente, embora os retornos de chamada (tanto entrando como saindo de JNI) totalizem cerca de 40 e, obviamente, pontos de bônus para pequenos diffs.

Já ouvi falar da biblioteca de encadeamento de sinal em J2SE, libjsig.so, e se eu pudesse instalar com segurança um manipulador de sinal como esse no Android, isso resolveria a parte contundente da minha dúvida, mas não vejo essa biblioteca para Android / Dalvik .

Chris Boyle
fonte
Se você pode iniciar o Java VM por meio de um script de wrapper, pode verificar se o aplicativo foi encerrado de forma anormal e fazer o relatório de erros. Isso permitiria a você capturar de forma limpa todos os tipos de saídas anormais, sejam elas SIGSEGV, SIGKILL ou qualquer outra. No entanto, não acho que isso seja possível com aplicativos Android de estoque, postando isso como um comentário (convertido da resposta).
sleske
Veja também: Não é possível executar um programa Java Android com Valgrind para saber como iniciar um aplicativo Android com um script de wrapper (no shell adb).
sleske
1
A resposta precisa ser atualizada. O código-fonte fornecido na resposta aceita resultará em comportamento indefinido devido à chamada de funções não assíncronas de sinal seguro. Veja aqui: stackoverflow.com/questions/34547199/…
user1506104

Respostas:

82

Edit: De Jelly Bean em diante, você não pode obter o rastreamento de pilha, porque READ_LOGSfoi embora . :-(

Na verdade, consegui um manipulador de sinais funcionando sem fazer nada muito exótico e lancei código usando-o, que você pode ver no github (editar: vinculando ao lançamento histórico; removi o manipulador de falhas desde então). Veja como:

  1. Use sigaction()para capturar os sinais e armazenar os manipuladores antigos. ( android.c: 570 )
  2. O tempo passa, um segfault acontece.
  3. No manipulador de sinal, chame JNI uma última vez e, em seguida, chame o manipulador antigo. ( android.c: 528 )
  4. Nessa chamada JNI, registre todas as informações de depuração úteis e chame startActivity()uma atividade que esteja sinalizada como necessitando estar em seu próprio processo. ( SGTPuzzles.java:962 , AndroidManifest.xml: 28 )
  5. Quando você volta do Java e chama aquele manipulador antigo, o framework Android se conecta debuggerdpara registrar um bom rastreio nativo para você, e então o processo morre. ( debugger.c , debuggerd.c )
  6. Enquanto isso, sua atividade de tratamento de falhas está começando. Na verdade, você deve passar o PID para que ele aguarde a conclusão do passo 5; Eu não faço isso. Aqui você pede desculpas ao usuário e pergunta se pode enviar um log. Em caso afirmativo, reúna o resultado de logcat -d -v threadtimee execute um ACTION_SENDcom destinatário, assunto e corpo preenchidos. O usuário terá que pressionar Enviar. ( CrashHandler.java , SGTPuzzles.java:462 , strings.xml: 41
  7. Fique atento para logcatfalhas ou demore mais do que alguns segundos. Encontrei um dispositivo, o T-Mobile Pulse / Huawei U8220, onde o logcat imediatamente entra no estado T(rastreado) e trava. ( CrashHandler.java:70 , strings.xml: 51 )

Em uma situação diferente do Android, algumas coisas seriam diferentes. Você precisaria coletar seu próprio rastreamento nativo, consulte esta outra pergunta , dependendo de que tipo de libc você tem. Você precisaria lidar com o despejo desse rastreio, iniciando seu processo separado de tratamento de falhas e enviando o e-mail de algumas maneiras apropriadas para sua plataforma, mas imagino que a abordagem geral ainda funcione.

Chris Boyle
fonte
2
O ideal é que você verifique se a falha ocorreu em sua biblioteca. Se isso ocorreu em outro lugar (digamos, dentro da VM), suas chamadas JNI do manipulador de sinal podem confundir as coisas de maneira bastante grave. Não é o fim do mundo, já que você está no meio de uma falha de qualquer maneira, mas pode tornar o diagnóstico de uma falha de VM mais difícil (ou causar uma falha bizarra de VM que termina em um relatório de bug do Android e confunde a todos).
fadden
Você é maravilhoso @Chris por compartilhar seu projeto de pesquisa sobre isso!
olafure
Obrigado, isso foi útil para descobrir onde meu JNI estava enlouquecendo. Além disso, olá de um ex-aluno da DCS!
Nick
3
Iniciar uma atividade em um novo processo a partir de um serviço também requer o seguinte código:newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Graeme
1
Esta solução ainda é válida no Jelly Bean? A etapa 6 não deixará de registrar as debuggerdsaídas de qualquer coisa ?
Josh
14

Eu sou um pouco tarde, mas eu tinha exatamente a mesma necessidade, e eu desenvolvi uma pequena biblioteca para enfrentá-lo, pela captura acidentes comuns ( SEGV, SIBGUS, etc.) dentro do código JNI , e substituí-los por regulares java.lang.Error exceções . Bônus, se o cliente estiver executando no Android> = 4.1.1, o rastreamento de pilha incorpora o rastreamento de volta resolvido da falha (um pseudo-rastreamento contendo o rastreamento de pilha nativo completo). Você não se recuperará de travamentos violentos (ou seja, se você corromper o alocador, por exemplo), mas pelo menos deve permitir que você se recupere da maioria deles. (por favor, relate sucessos e falhas, o código é novo)

Mais informações em https://github.com/xroche/coffeecatch (o código é a licença BSD 2-Clauses )

xroche
fonte
6

FWIW, o Google Breakpad funciona bem no Android. Eu fiz o trabalho de portabilidade e estamos enviando como parte do Firefox Mobile. Requer um pouco de configuração, uma vez que não fornece rastreamentos de pilha no lado do cliente, mas envia a memória da pilha bruta e faz a pilha do lado do servidor (para que você não precise enviar símbolos de depuração com seu aplicativo )

Ted Mielczarek
fonte
1
É quase impossível configurar o Breakpad considerando a documentação absolutamente ausente
shader
Realmente não é tão difícil e há muita documentação no wiki do projeto. Na verdade, para Android agora há um Makefile de compilação NDK e deve ser super fácil de usar: code.google.com/p/google-breakpad/source/browse/trunk/…
Ted Mielczarek
Você também precisa compilar um módulo que pré-processa arquivos de símbolos de depuração para Android e só pode compilá-lo no Linux. Quando você compila em um Mac - ele constrói apenas o pré-processador Mac / iOS dSym.
shader de
5

Em minha experiência limitada (não Android), SIGSEGV no código JNI geralmente travará o JVM antes que o controle seja retornado ao seu código Java. Lembro-me vagamente de ouvir sobre alguma JVM não-Sun que permite capturar SIGSEGV, mas AFAICR você não pode esperar conseguir fazer isso.

Você pode tentar capturá-los em C (consulte sigaction (2)), embora você possa fazer muito pouco depois de um manipulador SIGSEGV (ou SIGFPE ou SIGILL), pois o comportamento contínuo de um processo é oficialmente indefinido.

mas90
fonte
Bem, o comportamento é indefinido após "ignorar um sinal SIGFPE, SIGILL ou SIGSEGV que não foi gerado por kill (2) ou raise (3)", mas não necessariamente durante a captura de tal sinal. O plano atual é tentar um manipulador de sinal C que chama de volta para Java e, de alguma forma, termina o encadeamento sem terminar o processo. Isso pode ou não ser possível. :-)
Chris Boyle
1
C backtrace instruções: stackoverflow.com/questions/76822/…
Chris Boyle
1
... exceto que não posso usar backtrace (), porque o Android não usa glibc, ele usa Bionic. :-( Em vez disso, será necessário algo envolvendo _Unwind_Backtracede unwind.h.
Chris Boyle