android - 在 native 代码中捕获信号 (SIGSEGV) 后从 JNI 回调到 Java Android 应用程序代码

标签 android callback java-native-interface signals native

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

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

上面唯一不起作用的步骤是步骤 3.2。似乎在捕获 SIGSEGV 之后,当 native 代码尝试回调到 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

如果我使用调试器逐步执行程序并在 native signal_handler 中设置断点,我可以下到它第一次记录的行 Calling with signal...。在此之后,如果我跨过任何包含使用 JNIEnv(在本例中为 env)的调用的行,调试器将分离并且程序将完成。不过,您会注意到,从 logcat 输出中,我确实在使用 env 的调用之后得到了最后一个 native 日志行 Called with signal... 最重要的是回调到 java。

我在 stackoverflow 上看到其他实现基本上是这样做的,但我无法让它们中的任何一个工作。我也试过从 native 代码中抛出一个 java 异常,但最终也没有返回 javaland 并显示有关未决异常的消息。谁能看出这里出了什么问题?提前致谢!

最佳答案

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

Yes, you're doing something wrong: you're invoking undefined behavior by calling non-async-signal-safe functions from within a signal handler. Absent specific documentation supporting calling a function, such as the POSIX list of async-safe functions, you really can't make any calls from a signal handler. Footnote 188 of the C standard even states: "Thus, a signal handler cannot, in general, call standard library functions." POSIX provides a list of functions that are safe to call - under POSIX. Anything else is undefined behavior.

他之前在这里对这个问题给出了更详细的回答:https://stackoverflow.com/a/34553070/5251867

编辑:

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

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

我想这两者在技术上都是 IPC,因为它们都是让另一个进程访问信号处理程序在那里提供的信息的方法。将此信息发送到另一个进程,您可以在其中对信息执行某些操作似乎是唯一合适的前进方式。

关于android - 在 native 代码中捕获信号 (SIGSEGV) 后从 JNI 回调到 Java Android 应用程序代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54203717/

相关文章:

callback - EXTJS 4.0 : how to implement callback method for store. 同步()方法?

java - 如何动态加载新的jar文件到JavaVM

java - 弱引用的 NewGlobalRef 仍然阻止对象被垃圾收集

java - Android 计时关键游戏的触摸输入延迟问题。 onTouchEvent 调用之前是否有延迟?

android - Proguard Android 如何辨别?

android - 我们如何在java文件中设置 ListView 的高度?

facebook - 应用程序设置编辑器中的连接 URL

android - 将与菜单相关的事件称为 "callback function"是否有意义?

android - 在Android的OpenSL上缓冲URI

java - Android:使 ListView 中的单个项目可点击