SIGSEGV MAPERR 无效内存访问故障模式详解

0 评论 116 浏览 0 收藏 17 分钟

在HarmonyOS应用开发领域,崩溃问题是影响用户体验的核心因素之一。根据故障数据统计,SIGSEGV(Segmentation Fault,段错误)是Native层崩溃的主要原因之一,而其中SEGV_MAPERR错误码占比显著。本文将深入解析SEGV_MAPERR故障模式的根因、分析思路、常见类型以及典型案例,帮助开发者快速定位和解决此类问题。

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

SIGSEGV MAPERR 无效内存访问故障模式详解-华为开发者话题 | 华为开发者联盟

 

前言

       在HarmonyOS应用开发领域,崩溃问题是影响用户体验的核心因素之一。根据故障数据统计,SIGSEGV(Segmentation Fault,段错误)是Native层崩溃的主要原因之一,而其中SEGV_MAPERR错误码占比显著。本文将深入解析SEGV_MAPERR故障模式的根因、分析思路、常见类型以及典型案例,帮助开发者快速定位和解决此类问题。

一、SEGV_MAPERR 根因描述

       SEGV_MAPERR是SIGSEGV信号中的一种具体错误代码,全称为Segmentation Violation – Map Error。该错误表示程序试图访问一个未映射到物理内存或虚拟内存空间的地址。操作系统检测到该地址不属于当前进程合法的内存范围,从而触发崩溃。

       从本质上讲,SEGV_MAPERR意味着程序持有了一个”无效指针”,这个指针指向的内存区域要么根本不存在,要么已经被释放。当程序尝试解引用这样的指针时,就会触发本故障模式。

二、问题分析思路

       遇到SEGV_MAPERR类崩溃时,建议按照以下步骤进行分析:

第一步:确认类型

       根据故障日志中的Reason字段信息,初步判断故障的具体类型。Reason字段通常会包含故障地址以及可能的原因提示。

第二步:调用分析

       结合调用栈信息和寄存器状态,进一步确认问题原因,缩小分析范围。重点关注栈顶帧的函数名称。

第三步:代码分析

       根据调用栈中的地址信息,查找对应代码行,结合代码上下文分析异常原因。

第四步:更多日志辅助

       可以结合流水日志、业务链路日志等进行辅助定位,还原故障场景。

三、常见问题类型

      1. 空指针解引用(Null Pointer Dereference)

       这是常见的SEGV_MAPERR原因。当指针值为NULL(0x0)或接近0的小地址时,尝试读取或写入该地址会触发此错误。

       典型特征:

   • 故障地址通常为0x0、0x4、0x8等极小值

   • 会出现”probably caused by NULL pointer dereference”的提示

       示例代码:

int *ptr = nullptr;
int value = *ptr;  // crash: SEGV_MAPERR, fault addr 0x0

       故障日志示例:

ReasonSignalSIGSEGV(SEGV_MAPERR)@0x0000000000000001  probably caused by NULL pointer dereference
Fault thread info:
Tid64855Name: example.dfx_test
#00 pc 00000000000f3484 /data/storage/el1/bundle/libs/arm64/libentry.so(TriggerInvalidMemoryMAPERR(napi_env__*, napi_callback_info__*)+28)(0a973579a606828fd6f3e99787bb7e4c444f8464)
#01 pc 000000000005f7b0 /system/lib64/platformsdk/libace_napi.z.so(panda::JSValueRef ArkNativeFunctionCallBack<true>(panda::JsiRuntimeCallInfo*)+240)(7fd796b76701cf17dca125280fa3cefd)
...

Registers:
x00000000000000000 x1: 0000007e2ec711c0 x2: 00000059e1c9f6c0 x30001000000000000
x80000000000000001 x90000000000000000 ...

​

       日志解读:故障地址0x1是一个极小值,且Reason中明确提示”NULL pointer dereference”,寄存器x0的值为0x0,这些都指向空指针解引用问题。

   2. 踩内存问题(Memory Corruption)

当发生内存踩踏问题,并且被踩踏的是某个指针指向的地址时,可能导致指针指向一个完全未映射的内存区域,从而报SEGV_MAPERR。

      3. 野指针或使用已释放内存(Use-After-Free)

指针指向的内存已经被free或delete释放,但指针未被置空,后续再次访问该地址时触发崩溃。

       典型特征:

    •最后访问的指针地址通常以”6b”开头

       示例代码:

int tmp = 1;
int **p = new int*[10];
*p = &tmp;
delete [] p;
**p = 2;  // crash: SEGV_MAPERR after UAF

       故障日志示例:

ReasonSignalSIGSEGV(SEGV_MAPERR)@0x006b1e7f9352c001
Fault thread info:
Tid54719Name: example.dfx_test
#00 pc 00000000000f36a8 /data/storage/el1/bundle/libs/arm64/libentry.so(napi_env__*, napi_callback_info__*)+108)(61595c468ddd8bf122407e3c291e4d1b07fbcacf)
...

Registers:
x0: 00000059d3a02400 x1: 6b6b1e7f9352c001 x2: 00000059e1c9f6c0 x30001000000000000
x9: 6b6b1e7f9352c001 x10: 6b6b000000000000 ...

​

日志解读:寄存器x1和x9的值均以”6b”开头,这是UAF问题的典型特征。地址中包含的”6b”是已释放内存的标记。

      4. 数组越界(Out-of-Bounds Access)

       访问数组时索引超出分配的范围,且越界后的地址落在了未分配的内存页中。

    5. 动态库被提前释放

       动态库被dlclose释放后,如果仍调用库内的函数,函数指针会指向无效地址,触发SEGV_MAPERR。

       典型特征:

   • 栈顶显示为”Not mapped”

   • 顶指针不落在任何动态库的可执行段

       故障日志示例:

ReasonSignalSIGSEGV(SEGV_MAPERR)@0x0000007adc3817ac
Fault thread info:
Tid27689Name: com.dfr.dfx
#00 pc 0000007adc3817ac Not mapped
#01 pc 000000000000eef0 /data/storage/el1/bundle/libs/arm64/libentry.so(6648c6a709ee9454c89f7a4f24629f08350ffcad) 
#02 pc 000000000005f820 /system/lib64/platformsdk/libace_napi.z.so(panda::JSValueRef ArkNativeFunctionCallBack<true>(panda::JsiRuntimeCallInfo*)+240)(3469b98d825ff0165844454647e06a37)
...

Maps:
...
7adaea0000-7adc24a000 r--p 00000000 /system/fonts/HarmonyOS_Sans_SC.ttf
7adc24a000-7adc24c000 ---p 00000000 [anon:guard:27776]
...

       日志解读:00帧显示”Not mapped”,表明要执行的地址不在任何有效的可执行段中,这是动态库被提前释放的典型特征。

    6. 栈溢出(Stack Overflow)

       递归过深或局部变量过大导致栈空间耗尽,访问了边界之外的未映射内存。

       典型特征:

   • Reason字段中会有”current thread stack low address”相关字样

   • 递归调用导致的调用中会有大量重复的

       故障日志示例:

ReasonSignalSIGSEGV(SEGV_MAPERR)@0x0000007e6f57afb4  current thread stack low address = 0x0000007e6f57b000, probably caused by stack-buffer-overflow
Fault thread info:
Tid13753Name: com.dfr.dfx
#00 pc 000000000000c50c /data/storage/el1/bundle/libs/arm64/libentry.so(4077e1c7e7f6d108a2601e77d5e88f16386c1416) 
#01 pc 000000000000c540 /data/storage/el1/bundle/libs/arm64/libentry.so(4077e1c7e7f6d108a2601e77d5e88f16386c1416) 
#02 pc 000000000000c540 /data/storage/el1/bundle/libs/arm64/libentry.so(4077e1c7e7f6d108a2601e77d5e88f16386c1416) 
...
#254 pc 000000000000c540 /data/storage/el1/bundle/libs/arm64/libentry.so(4077e1c7e7f6d108a2601e77d5e88f16386c1416) 
#255 pc 000000000000c540 /data/storage/el1/bundle/libs/arm64/libentry.so(4077e1c7e7f6d108a2601e77d5e88f16386c1416) 

Maps:
...
7e6f579000-7e6f57b000 ---p 00000000 [guard]
7e6f57b000-7e6fd79000 rw-p 00000000 [stack]

       日志解读:故障地址0x0000007e6f57afb4已经不在空间7e6f57b000-7e6fd79000范围内,且调用出现大量重复帧(#00到#255完全一致),这是溢出的典型特征。

     7. 虚拟机多线程问题

       JavaScript本身是单线程的,对JS对象的操作必须在创建该JS线程的原始线程上进行。如果违反这一规则,会导致多线程安全问题。

       典型特征:

   • 栈顶是libark_jsruntime.so、stub.an、libace_napi.z.so等操作JS对象的库

      8. STL容器非线程安全

       多线程操作STL容器时,由于STL容器本身非线程安全,出现竞争时可能产生异常。

      9. 栈不可信(Stack Unreliable)

       当栈上的LR(链接寄存器)被踩之后,函数返回时读取的地址非法,触发崩溃。此类问题的调用在#03帧之后不可信。

       典型特征:

   • LastFatalMessage中会有”Failed to unwind stack, try to get unreliable call stack”字样

       故障日志示例:

ReasonSignalSIGSEGV(SEGV_MAPERR)@0x0000000000000000  probably caused by NULL pointer dereference
LastFatalMessageFailed to unwind stack, try to get unreliable call stack from #03 by reparsing thread stack.
Fault thread info:
Tid57988Name: com.dfr.dfx
#00 pc 00000000000b5130 /system/lib/ld-musl-aarch64.so.1(__stack_chk_fail+4)(eba18e3edbd2282fc49d075f29b463be) 
#01 pc 000000000000ec70 /data/storage/el1/bundle/libs/arm64/libentry.so(4cc475bcdd22d4c19688a625640e9ba1acb72961)
#02 pc 000041414141413d Not mapped
#03 pc 000000000000f814 /system/lib64/platformsdk/libipc_common.z.so(OHOS::BinderConnector::WriteBinder(unsigned long, void*)+124)(92288469d39640684884b665d0cb81de)
...

       日志解读:02帧的地址0x000041414141413d明显是一个被踩的地址(0x41是字符’A’的ASCII码),LastFatalMessage明确提示不可信,从#03帧开始需要重新解析。

四、关键日志关键字

       分析CppCrash故障日志时,关注以下关键字:

问题类型 故障地址特征 关键标识
空指针解引用 0x0、0x1、0x4、0x8 probably caused by NULL pointer dereference
UAF 以6b开头 寄存器值含6b
栈溢出 不在栈范围内 current thread stack low address
动态库释放 Not mapped Maps无匹配段
栈不可信 栈帧地址异常 0x41重复出现、Failed to unwind stack

五、开发建议

       问题排查建议

   • 追踪指针生命周期:通过串联业务日志,查找指针的创建、使用、释放过程是否合理

   • 检查多线程访问:排查是否有多个线程同时访问共享资源的情况

   • 使用ASan复现:当常规手段无法定位时,使用AddressSanitizer版本进行问题复现

       编码规范建议

   • 指针有效性检查:指针在使用前进行有效性判断

   • 避免裸指针:使用智能指针(shared_ptr、unique_ptr)进行内存管理

   • RAII机制:利用C++的RAII机制进行对象生命周期管理

   • 及时置空:手动管理的指针在释放后将指针赋值为nullptr

   • 禁止跨线程传递指针:多线程环境下使用智能指针或加锁保护

   • 数组边界检查:进行数组访问前需要进行边界检查

六、总结

       SEGV_MAPERR作为常见的崩溃类型,其核心问题是”无效内存访问”。通过掌握本文介绍的分析思路和常见类型,开发者可以更加高效地定位和解决此类问题。建议开发团队在日常编码中遵循内存安全规范,使用智能指针和RAII机制,避免裸指针操作,从源头减少此类崩溃的发生。

       掌握故障日志的解读技巧是解决问题的关键。建议开发者平时多分析典型案例,积累经验,这样才能在遇到问题时快速定位根因,提升应用稳定性。

———————————————————————————————————-

       如需更多技术细节,欢迎关注故障日志分析专题后续文章。

 

🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5

🔗 社区DFX专题文章:  https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001

【扫码加入 HarmonyOS DFX 技术交流群】

本文由 @华为开发者联盟 授权发布于人人都是产品经理。未经作者许可,禁止转载

题图来自Unsplash,基于CC0协议

该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
评论
评论请登录
  1. 目前还没评论,等你发挥!