Обратный вызов в код приложения Java Android из JNI после перехвата сигнала (SIGSEGV) в собственном коде - PullRequest
0 голосов
/ 15 января 2019

У меня есть минимальное приложение для Android, созданное с помощью мастера новых проектов с включенной поддержкой c ++. Цель приложения - позволить c ++ перезвонить в java после перехвата сигнала (SIGSEGV). Последовательность программы короткая и приятная, псевдокод будет выглядеть так:

  1. Введите собственный метод handleSegv()
    1. Собственный код возвращает обратно в Java в качестве теста
    2. Собственный код устанавливает обработчик SIGSEGV
  2. Введите собственный метод sendSegv()
    1. Собственный код повышает / отправляет SIGSEGV
  3. Введите собственный метод signal_handler
    1. Собственный код ловит сигнал и регистрирует его
    2. Собственный код перезванивает в Java
    3. Собственный код снова регистрируется, чтобы показать, что он прошел мимо обратного вызова

Единственный вышеприведенный шаг, который не работает, это шаг 3.2. Кажется, что после перехвата SIGSEGV ничего не происходит, когда нативный код пытается перезвонить в java. Я пробовал это как в эмуляторе, так и на устройстве с одинаковыми результатами. На данный момент я не уверен, что делаю что-то не так или есть что-то фундаментальное в обработке сигнала, которое не позволит мне перезвонить в java после его перехвата.

У меня есть код, демонстрирующий это, который можно клонировать из репозитория из на github , но на самом деле есть только два исходных файла:

CrashActivity.java

package com.kevinkreiser.crashtest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class CrashActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_crash);

        //setup segv handler
        handleSegv();
        //cause a segv
        sendSegv();
    }

    /**
     * Sets up signal handler for SIGSEGV which will call the callback function below
     * @return true if the handler was set
     */
    public native boolean handleSegv();

    /**
     * Raises the SIGSEGV signal which will cause the handler to be called
     */
    public native void sendSegv();

    /**
     * A function that the native code will call back when it receives SIGSEGV
     * as an illustration it just logs
     *
     * @param message  The message coming back from c++
     */
    public void callback(String message) {
        Log.e("CrashActivity.callback", message);
    }
}

нативной lib.cpp :

#include <android/log.h>
#include <jni.h>
#include <string.h>
#include <signal.h>
#include <string>

//globals persisting between calls from javaland
static JavaVM* vm = NULL;
static jobject activity = NULL;
static jmethodID callback = NULL;

//gets called first when a signal is sent to the running pid
static void signal_handler(int signal, siginfo_t*, void*) {
    //get an env so we can call back to java
    JNIEnv* env;
    if(vm->AttachCurrentThread(&env, NULL) != JNI_OK)
        return;

    //call back to java with a message
    __android_log_print(ANDROID_LOG_ERROR, "native-lib.signal_handler", "Calling with signal %d", signal);
    std::string message = "Got signal " + std::to_string(signal);
    jstring msg = env->NewStringUTF(message.c_str());
    env->CallVoidMethod(activity, callback, msg);
    __android_log_print(ANDROID_LOG_ERROR, "native-lib.signal_handler", "Called with signal %d", signal);
}

extern "C" JNIEXPORT void JNICALL
Java_com_kevinkreiser_crashtest_CrashActivity_sendSegv(JNIEnv*, jobject) {
    raise(SIGSEGV);
}

extern "C" JNIEXPORT jboolean JNICALL
Java_com_kevinkreiser_crashtest_CrashActivity_handleSegv(JNIEnv* env, jobject obj) {
    //get java hooks we need to make the callback
    env->GetJavaVM(&vm);
    activity = env->NewGlobalRef(obj);
    if (!activity)
        return false;
    jclass activity_class = env->GetObjectClass(activity);
    if (!activity_class)
        return false;
    callback = env->GetMethodID(activity_class, "callback", "(Ljava/lang/String;)V");
    if (!callback)
        return false;

    //try calling back to java with a message
    jstring message = env->NewStringUTF("No signal yet");
    env->CallVoidMethod(activity, callback, message);

    //register for SIGSEGV
    struct sigaction action;
    memset(&action, 0, sizeof(struct sigaction));
    action.sa_sigaction = signal_handler;
    action.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &action, NULL);

    return true;
}

Когда я запускаю программу и смотрю вывод logcat s, я вижу следующее:

2019-01-15 11:59:50.795 11183-11183/com.kevinkreiser.crashtest E/CrashActivity.callback: No signal yet
2019-01-15 11:59:50.795 11183-11183/com.kevinkreiser.crashtest E/native-lib.signal_handler: Calling with signal 11
2019-01-15 11:59:50.795 11183-11183/com.kevinkreiser.crashtest E/native-lib.signal_handler: Called with signal 11

Если я перехожу через программу с помощью отладчика и устанавливаю точку останова в нативном signal_handler, я могу перейти к строке, в которой он впервые регистрируется Calling with signal.... После этого, если я перейду через любую строку, которая включает вызов с использованием JNIEnv (env в данном случае), отладчик отключится и программа завершит работу. Однако вы заметите, что из вывода logcat я получаю последнюю собственную строку журнала Called with signal... после вызовов, использующих env и, что наиболее важно, обратный вызов в java.

Я видел другие реализации здесь в stackoverflow, которые делают это по существу, но я не смог заставить ни одну из них работать. Я также пытался выдать исключение Java из нативного кода, но в итоге я не возвращаюсь в javaland с сообщением об ожидающих исключениях. Кто-нибудь может увидеть, что здесь не так? Заранее спасибо!

1 Ответ

0 голосов
/ 16 января 2019

@ Комментарий Эндрю Хенле был правильным ответом:

Да, вы делаете что-то не так: вы вызываете неопределенное поведение, вызывая не асинхронно безопасные для сигнала функции из обработчика сигнала. В отсутствие конкретной документации, поддерживающей вызов функции, такой как список асинхронно-безопасных функций POSIX, вы действительно не можете делать какие-либо вызовы из обработчика сигнала. В сноске 188 стандарта C даже говорится: «Таким образом, обработчик сигнала в общем случае не может вызывать функции стандартной библиотеки». POSIX предоставляет список функций, которые безопасно вызывать - в POSIX. Все остальное - неопределенное поведение.

Ранее он давал более подробный ответ на этот вопрос здесь: https://stackoverflow.com/a/34553070/5251867

EDIT:

Просматривая доступные функции, кажется, что есть два пути, которыми можно следовать.

  1. используйте open, write, close, чтобы удалить файл с соответствующей информацией о сигнале, который был пойман, и обработать этот файл позже (при перезапуске приложения или из другой службы, которая отслеживает изменения в этом файле). )
  2. использовать connect, bind, send для отправки подробностей через сокет другому процессу

Я полагаю, что оба они технически являются IPC, поскольку оба являются средством, позволяющим другому процессу получить доступ к информации, которую обработчик сигнала размещает там. Передача этой информации другому процессу, где вы можете что-то сделать с этой информацией, кажется единственным подходящим способом продвинуться вперед.

...