范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

幽灵般的无栈Crash修复记

  作者:rayhunterli,腾讯IEG游戏客户端开发工程师
  | 导语   本文主要对一例无栈Crash,在内网不能重现,外网没有内存dump和墓碑文件情况下;怎么通过残留寄存器值,深入结合C++汇编层面函数调用原理,根据蛛丝马迹,定位解决问题。本文应同事邀请,经审批公开,但部分图片与内容为了信息安全,会进行打码和信息脱敏,只保留技术讨论,可能有些生硬,望谅解! 一: 问题信息与初步分析1.1 后台上报与简要分析
  新版本上线后,进行Crash总结时,发现CrashSight后台有不少无栈上报。JAVA层栈可以忽略,Native层栈只有一行,前面文章也提过崩溃的信息栈很少, 通常来说越难查 。
  简要分析: 只有一行栈,且PC值是一个奇怪的值,不属于某个模块.text代码段范围; 但应该是程序执行时,执行到错误的地址了;该问题栈是无法回溯,连那个模块引发的都无法确认,这个才让难度几何级增大。
  补充:Crash基本知识可以参考我以前写的一篇总结文章:《C++中Crash定位原理与常见案例反汇编分析》
  针对无栈问题,前不久我已经解决过一例安全模块引发的,相对简单很多,但属内网链接就不再公开。
  少量同学对C++函数再汇编层面调用过程理解有疑问,可参考《人人都能学的会C++协程原理剖析与自我实现》, 里面有函数调用过程讨论 1.2 常规行动与分析1.观察栈信息:只有一条栈,大量查看上报的PC寄存器值,发现基本没有什么规律性,有的很大,有的看真起来还接近.text段合理值,我随意截些图,如下图所示。总之来说,乱七八糟,各种各样,没有得出什么规律。
  可能有人看到上报错误信号不同,但BUS_ADRALN与SEGV_MAPERR都是访问到非法内存,只是内存没有对齐,可能就报BUS_ADRALN,否则SEGV_MAPERR;由于出错值目前看有点随机,当然对不齐比能对齐的多,所以本例BUS_ADRALN多。2.观察日志:CrashSight后台上报通常都带有crash发生时部分logcat日志,往常复杂的案例,通过阅读大量案例的日志,总能找到蛛丝马迹。这次我也不知道读了多少条,并没有得到特别有效的结论,除了发现大都在战斗中时出问题,别的并没有收获。3.观察其它线程栈:
  以前的经验,有时看看其它线程调用栈,可能也发现蛛丝马迹,但这个问题,也是失败告终。4.观察硬件及状态维度信息:比如出问题机器型号,众多,和型号无关;32位还64位也没用;游戏出问题时间,有几分钟的,有几十分钟的,也有一两小时无果,说明不是启动;观察状态:比如前后台,是加载阶段,退出阶段等,有时这个信息也挺有用,结合状态日志,确定了是战斗内,只有开始标识,没有结束标识。
  总结: 经过上面的行动处理,发现都失败,接下来有几个行动可选:1.读源码:
  首先无栈问题,不能确定是那个模块引发的。在我们项目主要是"xxxxxx_A.so","xxxxxx_B.so","xxxxxx_C.so"三大模块(名称隐藏),再加上很多其他小模块及第三方模块。这次是大版本更新,改动非常多,去看代码基本不太可能。最核心的是就算看了,你也不一定能发现,这种基本排除。2.大量测试:
  本问题如果复现了,有墓碑文件,跳过第一层栈,手动解析,基本可以秒杀。不过本案例量也不算少,也不是严重的不行,内部与少量同学沟通,进行测试,没有复现问题;这个问题没有那么容易复现,同时感觉还不到全力投入人力进行大批量测试。3.底层深入分析:
  本问题比较乱,出现问题点比较多,只要案例多,肯定能把规律性提取出来。我相信再搞一搞,结合汇编函数调用规律,全面分析,就可能成功,实在不行,再说。4.启用CrashSight实验功能:CrashSight也提功类似内存dump功能,但我们版本只有1Mb内存上传,100条线程左右估计要3Mb。基本会截断,需要他们手动解析二进制片段,还不一定有这个栈内存,非常麻烦。再加上这个功能只有一个项目用,可能有其它影响,和CrashSight同学讨论,这个做我方案3不成功的备选。
  我先选择3,假如失败后,计划进行2,4。二:在寄存器与汇编指令中寻找蛛丝马迹
  在一所有失败后,仅有最有效的信息,就剩32个寄存器,我们要从这仅有的信息中,尝试揪出本问题,还是比较难。
  2.1 探寻FP,LR,PC三个关键寄存器
  对于我们这种情况,最核心关联的三个寄存器就是FP,LR,PC,我们项目主要模块,进行C++编译时,没有通过-fomit-frame-pointer参数强制禁用FP;(禁用时回溯栈麻烦,需要用dwarf结合EFL的.eh_frame段存放CFI信息);通过大量阅读这些寄存器,稍微并整理了一下,大致分为下面5类类型1:lr,pc相同,看起来不正常,但FP看起来也不正常 r29=0x051f0076a392d140 r30=0x058e045c05220520 pc=0x058e045c05220520 具体CrashSight链接:xxxxxxxx(公开版隐藏)   类型2:lr,pc相同,看起来不正常,但FP看起来接近合理值 r29=0x0000007ad6ef07e0 r30=07x048304504722a88 pc=0x0483047504722a88 具体CrashSight链接:xxxxxxxx(公开版隐藏)   类型3:lr,pc相同,看起来接近合理值,但FP看起来不正常 r29=0x0485048404838210 r30=0x0000006c04880487 pc=0x0000006c04880487 具体CrashSight链接:xxxxxxxx(公开版隐藏)   类型4:lr,pc相同,看起来接近合理值,但FP看起来也接近合理值 r29=0x00000078f61faaa0 r30=0x0000007804da04d2 pc=0x0000007804da04d2 具体CrashSight链接:xxxxxxxx(公开版隐藏)  类型5:lr,pc不相同,且三个都不正常 r29=0x045d007823bf5170 r30=0x045b0459045c051f pc=0x005b0459045c051f 具体CrashSight链接:xxxxxxxx(公开版隐藏)
  结论:各种情况都有,没有发现特别意义,真的吗???也不是完全没有意义,且看下一小节2.2 深入ARM分析跳转指令---得出重要推论1
  由常识可明确本例发生在指跳转指令。
  别看这么多跳转指令,由2.1的信息,我们很快就能锁定。1.条件跳转指令排除:
  不看条件,他们实质是offset跳转,跳转的实际为固定值,BranchTo = PC + offset,他们不可能跳转到变态大的PC值,且LR值通常不会变(先强写一下LR除外),基本排除了。2.B指令排除:
  由于我们现在用4字节指令,留给B指令,只有26bit的偏移,不可能跳转到变态大的PC值;且这里出错时,PC相对地址不变,排除;加上LR不变,更加排除。3.BL指令排除:
  同上,光变态大PC的数据排除,LR实际为跳转时PC+4;这点也能排除4.BLR指令排除:
  这个可以满足PC变态大,但必然有有通用寄存器Xn保存PC值,通过大量观察上报寄存器,找不到Xn,排除5.BR指令排除:
  这个有时也用在函数结束,由LR提前从栈弹出,可以满足PC变态大,但必然有有通用寄存器Xn保存PC值,通过大量观察上报寄存器,找不到Xn,排除6.就你了:ret指令:
  一般用在函数结束时,从栈内存中先弹出高位寄存器(X19----X30,根据函数需要,编译实际保存不同),如果栈内存被改写,那么LR,FP都可能是错误的值,这样就能造成符合我们要求的无栈crash。
  ARM平台的ret指令和x86/x64平台,还是有点区别的,这点需要注意下。
  总结:到现在,我们已经明确是在函数结束时,由于栈越界改写破坏了LR,或FP,或两者,ret指令跳转失败;这有用吗???有用,但只有一点点。因为到现在我们连那个所属那个模块都不知道,好像又陷入僵局。2.3 再看FP与SP寄存器--离真相更近一步
  有了2.2小结的结论,函数返回,ret指令失败,有2.1小节我们问题是多种多样的,我们还可以观察FP与SP。
  假设A函数调用B函数,B函数结束,ret指令失败,这时B函数的栈已经平衡了,这时我们可以推出一个重要结论:
  SP:寄存器实际为A函数的栈顶,且不会被破坏
  FP:寄存器如果没有被越界改写的情况下,就是A的函数的栈底
  两者相减,就是函数A的栈大小,而一个函数的栈大小是固定的
  于是我就挑了一些FP看似处于正常值的案例和SP相减,就得到下面的结果:r29=0x0000006f29ca5d40 r30=0x0483047504727a88 sp=0x0000006f29ca5b00 = 240H/576  r29=0x051f0076a392d140 r30=0x058e045c05220520 sp=0x00000076a392d110 pc=0x058e045c05220520 pstate=0x0000000080000000 = 30H/48 不少  r29=0x0485048404831170 r30=0x0000007d04880487 sp=0x0000007e11601140 pc=0x0000007d04880487 pstate=0x0000000020000000 = 30H/48 侵入  r29=0x051f007838a0dab0 r30=0x058e045d045c04bc sp=0x0000007838a0da80 pc=0x058e045d045c04bc pstate=0x0000000080000000 = 30H/48 侵入  r29=0x0000007ad6ef07e0 r30=0x0483047504722a88 sp=0x0000007ad6ef05a0 pc=0x0483047504722a88 pstate=0x0000000060000000 = 240H/576  r29=0x0000007e9816de60 r30=0x048304750472ca88 sp=0x0000007e9816dc20 pc=0x048304750472ca88 pstate=0x0000000060000000 = 240H/576  r29=0x000000756acd27d0 r30=0x048304750472ca88 sp=0x000000756acd2590 pc=0x048304750472ca88 pstate=0x0000000060001000 = 240H/576
  函数A栈大小,基本上固定为240H和30H两种(其中部分30H,感觉最高16位,好像被写入奇怪的值,后面48位,看起来是合理的栈内存地址;我这里抛出最高16位相减),说明可能至少两个函数出现这种情况。用IDA搜一个主要的模块,其实栈内存为240H并不太多,30H就一大堆。
  总结:出问题的调用者函数栈内存大小为240H,也就是576个字节,或者48字节两种;这时结合其他寄存器观察,已经感觉到越界可能2字节一组(这点还不能完全实锤);但…好像还是没用,我们还是不知道那个模块,怎么破???2.4 不要放弃,再深入观察寄存器--结果呼之欲出
  为什么不要放弃呢?因为我们这次无栈出现的寄存器杂乱,各式各样,各种组合,像幽灵一样,特征似乎不那么明显,反过来这也是好处,说明可以遍历多种出问题的情况。如果是越界,很大可能可能是一个跳跃性越界,而不是连续越界。就是这样,加上特征不明显,只要我看的多,就可能找到漏网之鱼。
  我重点观察高位寄存器,对于异常值不断观察,再结合汇编知识,我们可以得到下面结论或推论。1.越界起始不是必然值:
  如果从栈变量写越界栈寄存器,连续写的话,对于一个函数来说,当写到栈内存保留的寄存器时,必然是特定的,但本例不是。2.越界值似乎有点意思:
  大量观察,发现特征也是2字节一组,且范围基本就是0x04xx,0x05xx较多。3. 寄存存越界分析:
  C++编译器对FP开启模式,通常将FP,LR保存栈顶,如果有其它高位寄存器要保存,必然存在下面可能。* a:越界先写坏其它高位寄存器,还没写坏后面的FP,LR。这种情况,能栈回溯,但访问高位寄存器可能引发crash,那么版本必然存在常规crash!!!* b:越界写坏LR寄存器。这种情况发生时,就是我们要查的无栈Crash* c:越界写坏不是本层函数栈,更高层次的函数栈,那么可能导致更高层函数崩溃。4. 我们最想要的漏网之鱼:
  由了上述3条结论后,我们可以寻找漏网之鱼,就是只有最高16位写坏的那种。反正本次够幽灵,规律性难概括,只要坚持看,肯定能找到,果不其然。// 很快找到,像这样越界从FP寄存器最高16bit,后面48bit是有效的 r29  =  0x051f0076a392d140   r30 =  0x058e045c05220520
  我们只要继续再找,如果越界是LR最高16bit开始,那么我们就将秒杀这个bug
  // 很快找到,像这样越界从LR寄存器最高16bit,后面48bit是有效的 r29  =  0x00000073313ca1d0   r30 =  0x0457007302d9a190   sp =  0x00000073313ca1a0    10   pc =  0x0057007302d9a190   pstate =  0x0000000080001000   查看模块地址: 72fe7ad000 -  73058aa000 r -  xp  00000000    103  :  13    1902513   libxxxxxxx_A .  so 可以推出结论:从45ED190地址出问题,分析相应汇编,即将秒杀!!! 7302d9a190 -  72fe7ad000  =   45ED190
  再找到类似的r28=0x000000742d7fc698 r29=0x00000074505bd810 r30=0x045700742ab04bb8 sp=0x00000074505bd5d0 pc=0x005700742ab04bb8 pstate=0x0000000080000000 也还是libxxxxxxx_A.so模块,可以推出结论:从45EEBB8地址出问题,分析相应汇编,即将秒杀!! 0x0742ab04bb8-0x7426516000 = 45EEBB8
  总结:到现在我们充分利用对汇编的理解,结合大量的寄存器的观测,不停的寻找,才得到本文最重要的推论3,4,也找到两处函数调用地址;到这基本上这个问题就秒杀了,我们只需从CrashSight后台找相关常规崩溃去验证,我们的推论成立与否就可以了。搜索蛛丝马迹到此就结束了,下面转到常规分析。三:一步一步验证推论
  虽然知道2.4小节的结论,可能秒杀这个问题。这有点利用假设站上帝视角,还存在假设,也为了在讲述角度更好示范解常规crash,先不直接用推论,进行更加常规点分析(我自己已先看那两个推论地址与反查出的函数名,CrashSight后台果然是有对应上报的,且相同业务上报位居高位)。先看本版本新增最多相同函数名的业务有栈崩溃,这个崩溃函数段就覆盖其中45EEBB8地址,虽地址有一偏差,但感觉是同一个问题。虽然用推论可能更快,但还是先解决已明确的问题把。不过最开始统计时相关同学告诉我已经修复了,最初我没有去管。3.1 业务模块有栈新常规问题分析
  示意图已经被我严重打码了,就是普通业务代码,一层一层的调用关系,虽看不到函数名,但这不影响技术本身。
  首先看了一下相关同学的修改,好像C#代码并没有特别大的可能崩溃风险,那么可能修改没用,于是我打开IDA进行0x00000000045eec7c附近反汇编。
  0x00000000045eec7c此处汇编比较简单(已打码屏掉相关业务名称),x8应该是定值,不可能出错,只有X26寄存器可能出错,打开寄存器
  从图上可以看出X8刚好是0x21150,符合条件;X26为0x0537053605350534,一看就是诡异不对的值;且从x24到x28都是0x05xx,这不是前面我们2.4小节的一个推论,这是巧合吗?
  我再向上看看IDA:本函数X26在崩溃点前面有访问,且本函数汇编直接改动X26可能点比较少,我都排除了,说明前面是正常访问的;略加观察又发现三条推论符合2.4小节中:手动剔除某些案例LR寄存器最高16bit,得到0x0742ab04bb8-0x7426516000 = 45EEBB8,这个45EEBB8地址就是在本函数中,这两个基本就是一个问题2.2小节中:我们得出一个重要结论,无栈崩溃就是函数返回ret指令修改,结合本例有栈崩溃前面能正常访问X26,且自己不会直接修改,后面不能访问,也只能是函数调用返回发生,巧合吧,这点在下面会细说2.3小节中:函数栈内存为240H或30H,本函数栈内存大小就是240H,巧合吧
  总之多么巧合啊,嘿嘿!
  话又说回来,这个有栈的问题,只看C#业务代码,很难修的。我们继续分析那里改坏寄存器的。3.2 缩小汇编范围,加速分析分析汇编范围
  如上文所述,动用我们2.4推论得到il2cpp的地址,可以直接定位出问题函数(lr地址需要-4),再验证跳转函数真正内容就可以了,这样有点变态。我们看怎么常规修复这个x26问题,展开更多汇编。
  如上图所示,在红框标注的X26之间出了问题,这里面有很多行汇编指令。
  先说一下:本函数C#源码有250行,转成C++源码有976行,由C++生成的汇编有1738行指令,看起来会很痛苦,我们要多结合C++源码与BL这种跳转指令及调用关系链,不要全部反汇编,浪费时间,也没必要。
  就算这样,我们卡了两个X26之间汇编,确定它们是顺序调用的,但里面涉及函数还是太多,且存在函数嵌套调用其他函数,用人工一个一个排除太费劲,另外还有形如虚函数,BLR Xn调用,这种我们是不知道函数名称的,对应C++源码是这样的,是无法读出来的,都需要我们结合C++及C#代码一点点推断。
  缩小汇编范围
  由前面信息,我敢肯定,肯定有更早一点的崩溃,毕竟我们的是幽灵模式,各种情况都有,只要我们广泛搜CrashSight后台就可以了。
  在CrashSight后台,通过对栈关键字搜索(函数名称),以及对最最近上报的观察,处理一下,我得到下面的信息//XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A实际为本函数函数名称,为了信息安全,我这里代替示意  pc 00000000045eec7c XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A 具体CrashSight链接:xxxxxxxx(公开版隐藏)  pc 00000000045eecdc XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A 具体CrashSight链接:xxxxxxxx(公开版隐藏)  pc 00000000045eec84 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A 具体CrashSight链接:xxxxxxxx(公开版隐藏)  pc 00000000045eebb8 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A 具体CrashSight链接:xxxxxxxx(公开版隐藏)  pc 00000000045eecf4 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A 具体CrashSight链接:xxxxxxxx(公开版隐藏)  lr 00000000045eecfc XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A 具体CrashSight链接:xxxxxxxx(公开版隐藏)
  针对每个崩溃点,我都去IDA看一眼,就是不同对应越界不同的寄存器,越界值刚好是一个不可访问的,不再截图示意。找PC最小的,直接将范围缩小到了00000000045eebb8,这是非常重要的结论,直接命中我们2.4小节漏网之鱼推论:0x0742ab04bb8-0x7426516000 = 45EEBB8,即将秒杀,哈哈。
  对于最后一个lr 00000000045eecfc,多说一句,如果blr Xn,这个寄存器Xn刚好是0,CrashSight的回溯是这样,只显示下一条的LR值。
  打开IDA,跳转过去,
  再看看寄存器值,X9是0x1F90,X8是0,符合猜测
  在看的过程中,我还发现其他规律://XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B实际为另一个函数函数名称,为了信息安全,我这里也是代替示意 //XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_C,XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_D,XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_E等都是不同的函数名称  pc 00000000045ed190 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B 具体CrashSight链接:xxxxxxxx(公开版隐藏)  pc 000000000085b41c XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_E // 实际为上面的变种,比XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B多了一层栈,其具体栈如下 1 pc 000000000085b41c XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_E [arm64-v8a] 2 pc 00000000045ed1a8 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B [arm64-v8a] 3 libil2cpp.so pc 000000000463c1a0 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_C 具体CrashSight链接:xxxxxxxx(公开版隐藏)  pc 000000000463c1b4 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_C // 比XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B少一层栈 具体CrashSight链接:xxxxxxxx(公开版隐藏)  pc 000000000463c1a4 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_C // 比XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B少一层栈 具体CrashSight链接:xxxxxxxx(公开版隐藏)  pc 0000000004ed9a20 XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_D // 比XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B少二层栈 具体CrashSight链接:xxxxxxxx(公开版隐藏)
  用IDA看一下XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B这个函数,崩溃点也和XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A没有实质区别,都是高位寄存器坏了,只是这个函数短很多,访问高位寄存器地方少,所以排名不是那么高而已。我们又有下面推论:2.4小节中:手动剔除最高16bit,得到7302d9a190-72fe7ad000 = 45ED190,这个45ED190地址就是在XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_B中,这不巧了!!!同上面XXXXXXXXXXXXXXXXXXXXXXXXXXXXX_A函数,这些都是对应高寄存器非法,又是函数返回ret指令修改,无栈崩溃也是,这不巧了2.3小节中:函数栈内存为240H或30H,本函数栈内存大小就是30H,两个都得到验证,哈哈2.4小节中:越界写坏不是本层函数,更高层次的函数栈,那么可能导致更高层函数崩溃,这不巧了,刚好符合,还引申一个下层栈也可能因收到错误信息而崩溃
  总结:本小节基本上把我们的汇编级推论验证完了,如果我们用汇编级去秒杀,可以更快。到这里本案例基本上80%的内容被搞定了,也说明验证了至少发现了两个A,B函数。接下来从汇编角度来进一步分析,他们都是虚函数,我们只需分析出这个虚函数到底是谁即可。3.3 揪出真凶,分析幽灵一样的原因
  从45EEBB8看,实际这是LR值,需退一条汇编指令,就是红框,虚函数调用。
  结合汇编上下文,C++源码上下文,C#源码,很容易推出X9就是XXXXXXXXXXXXXXXX_M函数地址,在IDA我已经标注;再汇编跳转过去
  看到汇编开头我就笑了,基本就是他了,居然把高位寄存器保存了一遍,还有0x70字节大小栈对象,为可以越界埋下伏笔,这里汇编不用看了(当然我自己为了实锤,还是会看),只需要看高级语言就行了。
  我们直接看C#代码,由于业务比较长,也存在多处可能越界点情况,我略去很多没用的代码,作一段伪码:protected override void XXXXXXXXXXXXXXXX_M(bool bIn) {     AAAA AInfo = new AAAA();     AInfo.Count = 0;      for (int i = 0; i < TestCount; i++)     {         unsafe         {             AInfo.Data[AInfo.Count++] = OriginData[i];         }     } }
  C#代码虽然是new,但结构体AAAA对象AInfo是栈对象,并非在堆区,这里在C#生成的C++代码及汇编都可以确认,我就不在贴更低层次的代码。它是unsafe的,看了一下改动,由于逻辑改动,确实导致可能数组不足,存在越界。但这里看起来像是连续的写入,越界为什么那么幽灵呢???
  回答这个问题之前,还要看AAAA结构[StructLayout(LayoutKind.Explicit, Pack = 8, Size = 82)] unsafe public struct AAAA {     [FieldOffset(0)] public fixed ushort Data[40];     [FieldOffset(80)] public byte Count; }
  OH,OH,OH!!! 明白了
  原来AInfo.Count是在数组AInfo.Data后面哇,栈越界先覆写AInfo.Count自己;然后for循环再次推动越界时,由于是AInfo.Count是byte型,是否再次越界取决于写入的值。数组大小为40,如果写入的AInfo.Count为30,就不越界,反而覆盖了原来的值;如果为42,就越界,开始破坏x28,以此类推;如果为100,就可能破坏更加上层的函数栈。加上逻辑层本身for循环次数TestCount由业务决定,也是不定的,AInfo.Count写入值更是不定,多层不定折腾下,是否越界就变得很玄学。越界了从哪里开始越也很玄,越多少字节也很玄。只有明确越界覆写是ushort,也就是16位。
  到此所有问题得解,一切结束。四: 总结与反思
  我们现在站在事后诸葛亮角度去分析:C++这种函数栈越界很好处理,编译器开启函数栈保护,只需牺牲一点性能,就可以搞定,是否平时尝试开启一下?项目是否能接入更高级的内存诊断工具,Google ASan,anitation, fuzzing!能否开启CrashSight的简易内存dump墓碑文件能够推进Crash修复需要近乎完全确认才能声明修复了,否则可能误导
  本文较长,可能技术点有错误或者不全面,欢迎指出与讨论。如果大家有更好方法,一起交流讨论。相比Google平台,感谢CrashSight平台提供寄存器,模块地址,日志等信息供追查。

9月起,退休人员4项待遇将迎来上调,看看你能从中受益吗?9月起,退休人员4项待遇将迎来上调,看看你能从中受益吗?今年七月份,各地陆续调整了退休人员基础养老金,并补发了今年一月份以来上涨的养老金,八月份,多数退休人员已经按照上调后的标准领赵露思穿辱华晚礼服引发热议,多位顶流被扒赵露思穿辱华晚礼服引发热议,礼服品牌是西太后,多位顶流被扒赵露思穿辱华晚礼服引发热议,礼服品牌是西太后,多位顶流被扒赵露思穿辱华晚礼服引发热议,礼服品牌是西太后,多位顶流被扒昨天网异世界舅舅第六集有点涩,舅舅惨的背后,是无数人的羡慕异世界舅舅第六集更新了,依然是足够爆笑!转头回想,世界上每多一个笑舅舅的人,舅舅的心理阴影就多一分。因为舅舅实在太惨了,惨到都觉得他转世过去异世界就是遭罪的。实惨的舅舅舅舅刚转世到稳健型指数再创新高!中基私募50指数最新周报来了一市场回顾上周,国内外重要经济数据出炉,美国7月CPI同比上涨8。5,依然维持在近40年高位,为应对居高不下的通胀,众议院通过了降低通胀法案,法案涉及医疗保障清洁能源税收等方面。中女子南瓜被老人偷窃回避这些人性的劣根性,避免自己越陷越深近日武汉一女子花费百万承包了一片南瓜地,本想回本给员工发工资,但村里接二连三的偷窃南瓜事件,让她欲哭无泪,她不得已卖了首饰,用来支付员工工资,但接下来发生的事更让她哭诉无门。偷盗南抓捕鳄雀鳝,谨防变成一场表演河南省汝州市中央公园云禅湖抓捕鳄雀鳝的行动仍在继续。从7月26日开始抽水,已经过去了将近一个月的时间,相关直播画面显示,超3000万网友在线围观。湖水即将被抽干,也基本锁定了鳄雀鳝马背上打下的江山在我国源远流长的历史中,有一个朝代被称为马背上打下的江山,大家知道这是哪一个伟大的朝代吗?为什么要说是在马背上打下来的呢?一个朝代的建立跟马有什么关系呢?那么,就让我们来了解一下这时隔28年,中美再次电磁对峙,从无力反击到平分秋色历史上中美之间有过多次海空电磁对峙,主要从上世纪90年代开始,当时解放军实力在世界军事强国中除战略核武器外,实际都存在严重的代差。对于电子战连入门级都算不上,对应美军通过冷战和苏联解析红山文化玉人红山文化出土玉人一般都是巫觋的形象,除牛河梁第十六地点出土的站立玉人外,比较可靠的还有以下四件,均为坐姿戴冠人身像。一件藏于故宫博物院,一件藏于英国剑桥大学菲兹威廉姆博物馆,一件藏怀疑男票出轨了?!救命啊闺蜜这波操作给爷看傻了众所周知,当代网友早已练就了一副金刚不坏之身严格贯彻劝和不劝分的原则面对感情问题时能不分就不分铁汁们身边有没有这样的姐妹分手的时候要死要活作为她的好姐妹好心劝诫结果第二天她告诉你和今日国内油价五连降!国际油价大涨,8月24日9295号汽油价格今天8月24日星期三,国内油价迎来五连降,国际油价大幅上涨,今天油价下调后92号汽油95号汽油0号柴油价格国内油价最新消息8月23日晚上的十二点,国内油价五连跌如期而至,国内汽油价
H3C未来有可能超过华为吗?感谢邀请,我和华为和H3C都有过接触,我认为H3C目前是远远不能超过华为的,主要的原因倒不是因为H3C技术不如华为,或者H3C没有芯片能力,而是H3C整体的企业运作策略非常的不健康大宝1周4个月,老公家拆迁政策多一个孩子多分25万。我怕二宝影响大宝,所以有点纠结,该不该备孕?看你的规划了,如果想要二胎,早生晚生都一样,早一点还能拿到25万。如果不想要二胎,也没必要为这么点钱去违背本心有这样的还不赶紧生,两个差不多大,一起长大感情才好,大的还小,没有独宠双减政策下,老师私下给自己孩子补课,是否应该制止这种行为?管得太宽了吧!人家老师白天辛辛苦苦上课,为别人家的孩子们呕心沥血,真是春蚕到死丝方尽,蜡炬成灰泪始干。这样的奉献,其实回到家已经累得不想说话了,可是为了自己的孩子,老师也出于父爱母DNF天空套的属性应该得到加强吗?你怎么看?我觉得应该适当加强了!因为天空套这点属性从当年的提升巨大到如今可有可无,已经无法匹配它最强时装的地位了!大家知道时装属性尤其是天空套从60版本到现在基本上没有变化过,但是老版本没有错过了淘宝,错过了微商,错过了比特币,你觉得未来的下一个风口在哪?错过了淘宝比特币微商下一步会是什么呢?5G这个就不讨论了,毕竟5G是早晚的事!不算一个风口了淘宝现在运营相对成熟,监管也在加强!将会稳定的运行下去但是微商没有监管鱼龙混杂,以次充好一般庄家要控盘一个股需要收集多少筹码?这个问题真乃股市不传之密!一般人还真不懂。懂的人也不会在这里写出来!不过,我曾在某机构做过技术培训,有幸看到了一些真实的交易数据。现在可以简单说一点给大家听。注意,这些数据可能比真五十岁后没有退休金,儿子指不上又没有存款该怎么生活?每一个人都有未来,不管未来怎么样,都应该为它打下一个良好的经济基础。你五十岁了才想起来没有退休金,儿子指不上,又没有存款,那你早干啥了,年轻时不知道工作吗?,不知道交养老保险将来有40岁能力一般,失业人还有什么出路吗?很多40岁以上的滴滴司机,已经完全把自己定向做一名网约车司机了,因为本身没有一技之长,去外面拼搏也是没有其他出路发展,身上还要承担起家庭的重担,跑滴滴毫无疑问是他们的首选,对于他们同一个班级同样的老师,学生之间成绩产生巨大差距的原因是什么?每一个学生都是独立的个体。学习的兴趣有所不同,学习的方法各异,记忆力的差距,家庭的学习氛围,平时一些知识的储备积累等,都会造成学生之间产生巨大的成绩差距。教师要因材施教,根据学生的短发怎么打理好看?短发是现代非常流行的发型,不少妹纸纷纷掉进短发的坑。虽然说短发看上去清爽时尚,但是短发和长发相比变化的可能性确实小很多。不过这可难不倒毛戈平形象设计艺术学校,今天小编带来的这款短发哪种身材的女人穿衬衣半裙好看?衬衫半裙很适合职场女性的穿搭,特别显精气神和气质。什么样的身材适合这种衬衫半裙的搭配呢?第一点上半身偏瘦的人上半身偏瘦的人穿衬衫容易穿出派头来,同时衬衫对于那些胸小的人来说更是友好