背景家喻户晓,堆crash dump是最难剖析的dump类型之一。此类crash最大的问题在于,造成谬误的代码无奈在产生堆毁坏时被发现。线上采集到的minidump,仅能提供非常无限的信息。当调试工具报告了堆毁坏、堆内存拜访违例后,即使是有教训的开发人员也会感觉头疼。 剪映专业版及其依赖的音视频编辑SDK、特效模块均采纳MD的形式链接规范库,这意味着任何一个模块呈现了堆损坏都会相互影响。从crash的地位回溯堆毁坏的源头,是一个十分有挑战性的工作。剪映业务模块较常见的是Use-after-free,而音视频编辑SDK和特效模块这类底层算法特效模块更多的是Buffer-overflow,不同团队模块间的堆谬误相互影响,导致问题难以定位。
GWP-ASan是Google主导开发的用于检测堆内存问题的调试工具。它基于经典的Electric Fence Malloc调试器原理,概率采样内存调配行为,抓取内存问题并生成上传解体报告。说到这里,兴许你会好奇它和ASan(Address Sanitizer)的区别。ASan是一种编译器调试工具,监控所有内存调配行为,能够发现栈、堆和全局内存问题,但它性能开销很高(2-3倍),不适宜线上应用。GWP-ASan相较于ASan,尽管无奈发现栈内存和全局内存问题,但因为它是采样监控,性能耗费能够忽略不计,更实用于线上场景。目前,GWP-ASan可检测的谬误有:
Use-after-freeBuffer-underflowBuffer-overflowDouble-freefree-invalid-address
Electric Fence Malloc调试器:https://linux.die.net/man/3/efenceGWP-ASan有多种实现计划,本计划基于Windows平台阐明,字节外部APM-PC平台相较于市面上其余计划的亮点有:
无侵入式接入,能够检测特定类型三方库的内存调配。反对无感知监测,发现异常后过程可持续运行。反对调整检测所用的堆页面个数配置和采样率配置,灵便调整性能耗费。剪映专业版接入字节外部APM-PC平台的GWP-ASan性能后,帮忙业务、音视频编辑SDK、特效模块解决30余例疑难堆crash。GWP-ASan dump比原生dump提供了更丰盛的信息,并指出了堆crash关联的信息细节,升高了疑难crash的排查难度,无效缩短了研发排查、修复问题的工夫。
技术计划监控原理检测原理概述创立受爱护内存池:首先,咱们须要保留一块间断的n*page size的受爱护内存池。其中,可分配内存的page是Slot,不可分配内存的page是Guard Page。Slot和Guard Page距离散布,整个内存池最前和最初都是Guard Page,所有的Slot都受到Guard Page爱护,之后利用调配的堆内存将随机采样调配到这些Slot上。
采样监控内存调配行为,记录堆栈:之后,hook利用堆内存调配行为,每次调配堆内存时,随机决定指标内存是走GWP-ASan调配——调配在一个闲暇的Slot上,还是走零碎原生调配。如果走GWP-ASan调配,那么指标内存会被随机左对齐/右对齐调配在一个闲暇的Slot上,同时记录分配内存的堆栈信息。
而当开释内存时,会先判断指标内存是否在GWP-ASan受爱护内存池上,如果是,那么开释这块内存和其所在的Slot,同时记录开释内存的堆栈。slot闲暇后,能够从新被用于调配。堆栈信息记录在metadata中。
继续监测,记录异样:
首先,咱们须要晓得Guard Page和闲暇的Slot都是不可读写的。接下来咱们看看GWP-ASan是如何发现异常的:Use-after-free: Slot上未分配内存时,是不可读写的。当拜访到不可读写的Slot时,利用抛出异样,此时查看该Slot是否刚开释过内存,如果开释过内存,那么能够断定此异样为Use-after-free。Buffer-underflow:当内存左对齐调配在Slot上时,如果产生了underflow,利用会拜访到Slot左侧不可读写的Guard Page,利用抛出异样,此异样为Buffer-underflow。Buffer-overflow:当内存右对齐调配在Slot上时,如果产生了overflow,利用会拜访到Slot右侧不可读写的Guard Page,利用抛出异样,此异样为Buffer-overflow。Double-free:利用开释内存时,首先查看指标内存地址是否位于受爱护内存池区间内,如是,由GWP-ASan开释内存,开释前查看指标内存地址所在Slot是否曾经被开释,如是,那么能够断定此异样为Double-free。Free-invalid-address: 利用开释内存时,首先查看指标内存地址是否位于受爱护内存池区间内,如是,由GWP-ASan开释内存,开释前先查看要开释的内存地址和之前调配返回的内存地址是否相等,如果不相等,那阐明指标开释地址是非法地址。此异样为Free-invalid-address。堆内存调配API后面曾经提到,GWP-ASan用于检测堆内存问题,为了检测堆内存问题,必须先感知利用内存调配行为。很天然的,咱们会想到hook内存调配办法,然而该hook哪个办法呢?
下图形容了Windows利用调配堆内存的可用办法:
GlobalAlloc/LocalAlloc是为了兼容Windows旧版本的API,当初根本不实用,所以不监控。HeapAlloc/HeapFree个别用于过程分配内存,不监控。VirtualAlloc是应用层内存调配的底层实现,开发个别不间接用此API分配内存,它离利用调配堆内存行为太远,堆栈参考意义不大;且Windows GWP-ASan须要基于此实现,因而,也不监控。
最终选定Hook malloc/free等系列办法,hook malloc/free后,能感知到用户调配的堆内存。
### Hook计划 上面的计划都是应用层的Hook计划,内核层Hook仅实用于x86平台。 Detours库作为微软官网出品的hook库,兼容性佳,稳定性好,是最佳抉择。然而还须要留神的是,Windows下,运行时库配置会影响hook后果,Detours只能无侵入式hook/MD库的内存调配行为,/MT库须要提供本身内存调配的函数指针能力hook。
堆栈记录首先要阐明的是,GWP-ASan监控依赖解体监控。Use-after-free、Buffer-underflow、Buffer-overflow都是在客户端产生异样后,联合GWP-ASan的metadata去断定的。目前字节外部APM-PC平台的解体报告格局为minidump。一个minidump文件由多种streams组成,如thread_list_stream、module_list_stream和exception_stream等等。不同stream记录了不同信息,咱们能够将GWP-ASan采集到的异样信息视为独自的gwpasan_stream,附加到minidump文件中。
GWP-ASan采集的信息次要包含:谬误类型、调配地址和大小、调配堆栈、开释堆栈(如有)、受爱护内存池起止地址。这些信息基于Protobuf协定序列化后,被增加到minidump文件中。GWP-ASan通过Windows native API CaptureStackBackTrace API在客户端回溯 “开释/调配” 堆栈。minidump上传到平台后,平台抽取出GWP-ASan信息,联合minidump中loaded module list,联合相干模块的符号表,符号化GWP-ASan调配/开释堆栈。GWP-ASan信息联合minidump本来的信息,根本就能定位问题。
监控流程
拓展场景无解体计划GWP-ASan检测到异样后,会被动解体导致客户端过程退出,给用户带来了不良体验。无解体的GWP-ASan检测到异样后,再将对应内存页标注为可读写的(如为use-after-free/buffer-underflow/buffer-overflow),仅生成上传解体报告,不被动终结过程,客户端标注异样已解决。用户无感知,程序持续运行。须要留神的是,客户端在UEF里标记拜访区域内存页为可读写内存页可能影响后续的GWP-ASan检测。
实战分享Use-After-Free:开释后应用理论案例 1咱们看下惯例的dump输入,windbg告知咱们程序crash在25行。
因为12行有空指针查看,能够排除空指针问题。
执行.ecxr复原异样现场也能够证实,此crash和空指针无关。只是一个内存拜访违例。
汇编指定地址,能够晓得这个crash动作是在读取类的虚指针,读取内存的过程中crash了。
00007ffb`d422e4a0 498b06 mov rax,qword ptr [r14]00007ffb`d422e4a3 488bd5 mov rdx,rbp00007ffb`d422e4a6 498bce mov rcx,r1400007ffb`d422e4a9 ff10 call qword ptr [rax]查看问题代码:
...