在以本机代码捕获信号(SIGSEGV)后,从JNI回调到Java Android应用程序代码

时间:2019-01-15 17:09:58

标签: android callback java-native-interface signals native

我使用启用c ++支持的新项目向导创建了一个最小的Android应用程序。该应用程序的目的是允许c ++在捕获到信号(SIGSEGV)后再调用java。该程序的序列简短而甜美,伪代码如下:

  1. 输入本机方法handleSegv()
    1. 本机代码作为测试回调到Java
    2. 本机代码设置SIGSEGV处理程序
  2. 输入本机方法sendSegv()
    1. 本机代码引发/发送SIGSEGV
  3. 输入本机方法signal_handler
    1. 本机代码捕获信号并将其记录
    2. 本机代码回调到Java
    3. 本机代码再次记录以显示其已跨过回调

上面唯一不起作用的步骤是步骤3.2。似乎在捕获SIGSEGV之后,当本机代码尝试回调Java时,什么也没有发生。我已经在模拟器和设备上尝试了相同的结果。在这一点上,我不确定是否做错了什么,或者在处理信号方面是否存在一些基本问题,这些问题使我在捕获到信号后无法回调到Java中。

我有演示此代码的代码,可以从repo from on 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);
    }
}

native-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的输出时,会看到以下内容:

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。 / p>

我在这里已经在stackoverflow上看到了其他实现方式,这些实现方式基本上可以做到这一点,但是我无法使它们中的任何一个都起作用。我也尝试过从本机代码中抛出一个Java异常,但最终也没有返回有关未决异常的消息回到Javaland。任何人都可以看到这里出了什么问题吗?预先感谢!

1 个答案:

答案 0 :(得分:1)

@Andrew Henle的评论是正确的答案:

  

是的,您做错了什么:您通过从信号处理程序中调用非异步信号安全函数来调用未定义的行为。由于缺少支持调用函数的特定文档,例如异步安全函数的POSIX列表,因此您实际上无法从信号处理程序进行任何调用。 C标准的脚注188甚至指出:“因此,信号处理程序通常不能调用标准库函数。” POSIX在POSIX下提供了可以安全调用的功能列表。其他都是未定义的行为。

他以前曾在这里对此问题提供了更详细的答案:https://stackoverflow.com/a/34553070/5251867

编辑:

查看可用的功能,似乎有两种途径可以说服。

  1. 使用openwriteclose删除一个文件,其中包含有关捕获到的信号的相关信息,并稍后处理此文件(在应用重启或从其他服务启动时)监视此文件的更改)
  2. 利用connectbindsend通过套接字将详细信息发送给其他进程

我猜这两者在技术上都是IPC,因为它们都是让另一个进程访问信号处理程序放入那里的信息的方法。将此信息带到另一个可以对信息进行处理的过程中,这似乎是前进的唯一合适方法。