Android JNI中signal 11(SIGSEGV)崩溃的调试及原因排查
调试Android JNI中SIGSEGV(signal 11, SEGV_MAPERR)随机崩溃指南
一、错误本身解析
- signal 11 (SIGSEGV):原生代码访问了无效内存地址,触发段错误
- code 1 (SEGV_MAPERR):访问的地址完全未映射到当前进程的内存空间
- fault addr 0x3634323064373235:将该十六进制地址按字节转ASCII,得到
6420d725(小端序下需反向字节),这个字符串大概率和你JNI层调用Keystore时用到的密钥别名、ID等字符串相关,说明可能存在把字符串内容错误当作指针访问的情况。
二、可能的诱因
- JNI指针误用:
- 将Java字符串/对象错误转换为原生指针,比如把
GetStringUTFChars返回的字符串内容强转为指针,或是使用已释放的字符串指针 - 访问已被GC回收的Java对象对应的原生指针(JNI引用管理错误导致)
- 将Java字符串/对象错误转换为原生指针,比如把
- 内存越界:
- 原生层数组操作、内存拷贝越界,覆盖了其他内存区域的数据(比如破坏了系统UI库内部的
std::tree结构,导致后续__tree_remove时崩溃)
- 原生层数组操作、内存拷贝越界,覆盖了其他内存区域的数据(比如破坏了系统UI库内部的
- 线程安全问题:
- Keystore操作线程与UI渲染线程(堆栈中显示的
RenderThread)共享未同步的资源,并发修改导致内存结构破坏
- Keystore操作线程与UI渲染线程(堆栈中显示的
- JNI引用管理错误:
- 局部引用未及时释放超过阈值,或全局引用未清理,导致内存泄漏、对象被错误回收
三、调试方法
1. 聚焦JNI层Keystore代码
- 排查字符串转换逻辑:检查
GetStringUTFChars/NewStringUTF的使用,确保没有把字符串内容强转为指针,且使用后及时调用ReleaseStringUTFChars释放 - 检查引用管理:对不再使用的局部引用调用
DeleteLocalRef,全局引用在不需要时调用DeleteGlobalRef,避免对象被GC回收后原生层仍持有指针 - 验证内存操作:确保
malloc/free、new/delete配对使用,数组操作时严格校验索引范围
2. 利用工具定位问题
- AddressSanitizer(ASAN):
编译原生模块时开启ASAN,可精准检测内存越界、使用已释放内存等问题,给出详细错误栈:set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address") - GDB/LLDB调试:
附加调试器到应用进程,设置内存断点监控可疑变量,崩溃时查看寄存器、堆栈变量追踪错误指针来源:# 附加到进程 lldb -n com.your.package # 设置内存读写断点 watch set variable <目标变量地址> - Android Studio Profiler:
用Memory Profiler监控原生内存分配/泄漏情况,CPU Profiler查看线程调用链,确认Keystore操作与UI线程的交互逻辑
3. 结合堆栈的针对性排查
堆栈显示崩溃在系统UI库的Choreographer VSync处理逻辑中,说明之前的内存破坏影响了系统组件的数据结构:
- 检查JNI代码是否误操作了Display相关系统对象,或修改了全局内存区域
- 在Keystore操作前后添加线程ID、关键变量日志,对比崩溃上下文,定位触发崩溃的操作序列
四、临时验证思路
- 注释JNI层Keystore相关代码,确认崩溃是否消失,锁定问题范围
- 将Keystore操作放到独立线程执行,与UI线程完全隔离,避免并发冲突
内容的提问来源于stack exchange,提问作者Ghostop
相关产品推荐
相关产品推荐

