【发布时间】:2019-06-09 18:02:45
【问题描述】:
我有一个使用启用了 c++ 支持的新项目向导创建的最小 Android 应用程序。该应用程序的目的是允许 c++ 在捕获信号 (SIGSEGV) 后回调到 java。程序序列短小精悍,伪代码如下:
- 输入本机方法
handleSegv()- 本机代码回调到 java 作为测试
- 本机代码设置 SIGSEGV 处理程序
- 输入本机方法
sendSegv()- 本机代码引发/发送 SIGSEGV
- 输入本机方法
signal_handler- 本机代码捕获信号并记录它
- 本机代码回调到 java
- 本机代码再次记录以显示其已通过回调
上面唯一不起作用的步骤是步骤3.2。似乎在捕获 SIGSEGV 之后,当本机代码尝试回调到 java 时,什么都没有发生。我在模拟器和设备上都试过这个,结果相同。在这一点上,我不确定我是否做错了什么,或者是否有一些基本的处理信号不允许我在捕获它后回调到 java。
我有演示此代码的代码,可以从 repo from on github 克隆,但实际上只有两个源文件:
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);
}
}
#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;
}
当我运行程序并查看logcats 输出时,我看到以下内容:
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 并显示有关未决异常的消息。谁能看到这里有什么问题?提前致谢!
【问题讨论】:
-
是的,您做错了:您通过从信号处理程序中调用非异步信号安全函数来调用未定义的行为。缺少支持调用函数的特定文档,例如异步安全函数的 POSIX 列表,您真的无法从信号处理程序进行 any 调用。 Footnote 188 of the C standard 甚至声明:“因此,信号处理程序通常不能调用标准库函数。” POSIX 提供了在 POSIX 下可以安全调用的函数列表。其他任何事情都是未定义的行为。
-
@Michael 我确实已经看到了你的第一个链接,我的代码基本上是等价的。第二个链接更多地说明了我关于从根本上可能与不可能的问题。第二个似乎是说,是的,在捕获信号后回调到 java 是行不通的。我将对这里可以做的事情做更多的研究。谢谢。
标签: android callback java-native-interface signals native