JNI SIGSEGV崩溃Java捕获失败,求解决方案及排查思路
我已将触发SIGSEGV的Native代码移至标记为android:process=":ffmpeg"的独立进程Service中,避免主进程崩溃,但尝试三种方案均无法在Java层捕获崩溃并上报:
- 在C信号处理器中调用
throwFfmpegHelperException(a, "throwFfmpegHelperException message")抛出Java异常 - 在C信号处理器中调用Java静态方法
nativeCrashed() - 为执行Native调用的Java线程设置
UncaughtExceptionHandler
目前只能通过服务回复超时判断崩溃,需要明确失败原因及替代方案。
方案失败原因分析
1. 信号处理器中直接抛出Java异常无效
JNI环境(JNIEnv*)是线程绑定的,信号处理器运行在信号处理上下文,而非原JNI调用线程,直接使用保存的JNIEnv*会导致未定义行为。此外,信号处理函数中不能安全调用大部分JNI函数(如ThrowNew),因为这些函数可能涉及线程状态、锁操作,在信号上下文执行会引发死锁或崩溃。
2. 信号处理器中调用Java方法无效
同理,信号上下文无法安全执行JNI的方法调用(CallStaticVoidMethod),且保存的jclass未做全局引用,可能已被GC回收,导致调用失败。
3. UncaughtExceptionHandler无法捕获Native崩溃
UncaughtExceptionHandler只能处理Java层未捕获的异常,Native层的SIGSEGV属于操作系统信号,会直接终止进程,不会转化为Java异常,因此该处理器无法触发。
可行解决方案
方案1:信号处理器中通过管道安全通知Java层
在信号处理器中,不要直接调用JNI方法,而是通过**管道(Pipe)**向Java层发送信号,Java层通过FileDescriptor监听管道读取事件,收到信号后触发崩溃上报逻辑。这种方式利用异步信号安全的write函数,避免JNI调用的风险。
方案2:Native崩溃栈捕获+进程退出感知
利用Android的libunwind或breakpad捕获Native崩溃栈并写入文件,Java层通过ServiceConnection的onServiceDisconnected回调感知独立进程退出,读取崩溃文件完成上报。
方案3:优化独立进程生命周期监听
结合onServiceDisconnected回调与超时逻辑:当服务断开且未收到预期回复时,判定为Native崩溃导致进程终止,触发上报。
代码修正示例(管道通知方案)
C层代码修改
#include <unistd.h> #include <signal.h> static int crash_pipe[2]; void Segfault_Handler(int signo) { // 向管道写入信号标识,通知Java层 char msg = 'c'; write(crash_pipe[1], &msg, 1); // 恢复默认信号处理,让进程正常退出 signal(signo, SIG_DFL); raise(signo); } jint JNI_OnLoad(JavaVM *jvm, void *reserved) { JNIEnv *env = NULL; jclass cls; if ((*jvm)->AttachCurrentThread(jvm, &env, NULL) != JNI_OK) { return JNI_ERR; } cls = (*env)->FindClass(env, "com/schokoladenbrown/Smooth"); (*env)->RegisterNatives(env, cls, g_native_methods, sizeof(g_native_methods)/sizeof(g_native_methods[0])); nativeCrashed = (*env)->GetStaticMethodID(env, cls, "nativeCrashed", "()V"); // 初始化管道 pipe(crash_pipe); // 设置SIGSEGV信号处理器 signal(SIGSEGV, Segfault_Handler); return JNI_VERSION_1_6; } // 暴露方法给Java层获取管道读端文件描述符 JNIEXPORT jint JNICALL Java_com_schokoladenbrown_Smooth_getCrashPipeReadFd(JNIEnv *env, jclass clazz) { return crash_pipe[0]; }
Java层Service监听管道
public class FfmpegBoundService extends Service { static final int MSG_SAY_HELLO = 1; static final int MSG_CRASH_REPORT = 11; Messenger clientMessenger; private FileDescriptor crashPipeFd; private InputStream crashInputStream; private Thread crashMonitorThread; static class IncomingHandler extends Handler { Bitmap tb; @Override public void handleMessage(Message msg) { // 原有消息处理逻辑... } } Messenger mMessenger; @Override public void onCreate() { super.onCreate(); // 初始化崩溃监听管道 initCrashMonitor(); } private void initCrashMonitor() { int fd = Smooth.getCrashPipeReadFd(); crashPipeFd = new FileDescriptor(); crashPipeFd.setInt$(fd); crashInputStream = new FileInputStream(crashPipeFd); crashMonitorThread = new Thread(() -> { byte[] buffer = new byte[1]; try { // 阻塞等待管道数据 if (crashInputStream.read(buffer) != -1) { reportCrash("Native SIGSEGV crash detected"); stopSelf(); } } catch (IOException e) { e.printStackTrace(); } }); crashMonitorThread.start(); } private void reportCrash(String msg) { Log.e("FfmpegService", msg); // 通知主进程崩溃事件 if (clientMessenger != null) { try { Message crashMsg = Message.obtain(null, MSG_CRASH_REPORT, msg); clientMessenger.send(crashMsg); } catch (RemoteException e) { e.printStackTrace(); } } } @Override public IBinder onBind(Intent intent) { mMessenger = new Messenger(new IncomingHandler()); return mMessenger.getBinder(); } @Override public void onDestroy() { super.onDestroy(); try { crashInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
内容的提问来源于stack exchange,提问作者usilo

