文档章节

GCC优化引发的一场血案

P
 Polarix
发布于 2018/11/03 21:47
字数 1689
阅读 467
收藏 0

       正所谓人在江湖飘,那有不挨刀。从事软件行业,不写几个惊天地泣鬼神的BUG都不好意思说自己干过软件。
       本人从事C/C++开发工作,日常就是看看文档发发呆写写BUG。在一个风和日丽的上午,日常上班中,突然客户丢过来一个BUG,是System halt,灾难性BUG啊!通过Sorce Dump,最终出现问题的代码锁定在以下位置:
       因为项目保密需求,代码经过变形和加工,仅作示例,大家领会精神。

#defien STACK_SIZE_MAX  50

//此处省略一万字…… 

DATA_TYPE_ST* g_pstStack[STACK_SIZE_MAX];

//此处省略一万字…… 

while((NULL != g_pstStack[uiIndex]->pstData) && (uiIndex < STACK_SIZE_MAX))  
{  
    for(unsigned int uiIdx=0; uiIdx!=g_pstStack[uiIndex]->pstData->uiIdx; uiIdx++)  
    {  
        //此处省略一万字……  
    }
    uiIndex++;
}  

       介入运算的变量为:

g_pstStack;    //全局变量,指针数组。
uiIndex;       //局部变量,unsigned int型,循环索引。
STACK_SIZE_MAX //宏定义,值为50。

       看上去没啥异常的代码,通过Source Dump分析,崩溃的地方为以下代码:

    for(unsigned int uiIdx=0; uiIdx!=g_pstStack[uiIndex]->pstData->uiIdx; uiIdx++)  

       但是这句话貌似也没啥,一句循环而已,如果非要说有问题,那就是指针解引时候的问题。但是由于是交叉编译,手里又没有调试器,所以加上几行Log,对再次对现象进行再现。

while((NULL != g_pstStack[uiIndex]->pstData) && (uiIndex < STACK_SIZE_MAX))  
{  
    DBG_LOG("[DBG_LOG]uiIndex = %u.", uiIndex);
    for(unsigned int uiIdx=0; uiIdx!=g_pstStack[uiIndex]->pstData->uiIdx; uiIdx++)  
    {  
        //此处省略一万字……  
    }
    uiIndex++;
}  

       编译,再次触发BUG,我傻了,因为Log里赫然写着:

[DBG_LOG]uiIndex = 50.

       这啥情况啊,循环条件为((NULL != g_pstStack[uiIndex]->pstData) && (uiIndex < STACK_SIZE_MAX)),我反复看了几遍,没问题啊!
       现在问题已经找到了,对野指针进行解引,导致了System Halt。但是,野指针是因为索引越界导致的,换句话说,(uiIndex < STACK_SIZE_MAX)这个条件根本没起作用!为了防止眼花,我改了一下Log输出:

while((NULL != g_pstStack[uiIndex]->pstData) && (uiIndex < STACK_SIZE_MAX))  
{  
    DBG_LOG("[DBG_LOG]uiIndex = %u, Array size is %d.", uiIndex, STACK_SIZE_MAX);
    for(unsigned int uiIdx=0; uiIdx!=g_pstStack[uiIndex]->pstData->uiIdx; uiIdx++)  
    {  
        //此处省略一万字……  
    }
    uiIndex++;
}  

       再次运行,触发BUG,然后,Log文件里写着:

[DBG_LOG]uiIndex = 50, Array size is 50.

       完蛋了,灵异了……
       就在我觉得无从下手时候,迷茫之中,我将wihle中的两个条件对换了一下以下位置,代码变成了:

while((uiIndex < STACK_SIZE_MAX) && (NULL != g_pstStack[uiIndex]->pstData))  
{  
    DBG_LOG("[DBG_LOG]uiIndex = %u, Array size is %d.", uiIndex, STACK_SIZE_MAX);
    for(unsigned int uiIdx=0; uiIdx!=g_pstStack[uiIndex]->pstData->uiIdx; uiIdx++)  
    {  
        //此处省略一万字……  
    }
    uiIndex++;
}  

       BUG消失了!!!!!!!!!!
       无数只草泥马呼啸而过……
       但是很快,我冷静了下来,早前我遇到过本来正常稳定运行的程序,在O3优化开启后,重新编译,程序崩溃的情况,职业直觉告诉我,这次应该也类似,这应该是编译器的锅,或者再准确点,这是Makefile的锅。
       于是我开始找工程中的所有makefile和ruler,终于在xxxx.mk中找到了一句话:

#此处省略一万字……  
OPTIMIZATION_FLAG =? -O2
#此处省略一万字……  

       在确认其他地方没有额外的优化参数后,我确定了,问题就出在这里。但是为什么,不清楚……
       冷静一下,把之前修正的代码改回来,这个参数修改为O1,再次编译……
       BUG也没有再现!
       这下可以确定,就是这个-O2搞的鬼,那么下一步的工作也清楚了:搞清楚-O2都干了什么!
       这个项目使用的是GCC4.8.3,那么,去GCC官网,下载相应版本的文档,查看O2优化的具体说明,内容如下:

-O2: Optimize even more. GCC performs nearly all supported optimizations that do not involve a space-speed tradeoff. As compared to ‘-O’, this option increases both compilation time and the performance of the generated code.
‘-O2’ turns on all optimization flags specified by ‘-O’.  It also turns on the following optimization flags:
	-fthread-jumps
	-falign-functions -falign-jumps
	-falign-loops -falign-labels
	-fcaller-saves
	-fcrossjumping
	-fcse-follow-jumps -fcse-skip-blocks
	-fdelete-null-pointer-checks
	-fdevirtualize
	-fexpensive-optimizations
	-fgcse -fgcse-lm
	-fhoist-adjacent-loads
	-finline-small-functions
	-findirect-inlining
	-fipa-sra
	-foptimize-sibling-calls
	-fpartial-inlining
	-fpeephole2
	-fregmove
	-freorder-blocks -freorder-functions
	-frerun-cse-after-loop
	-fsched-interblock -fsched-spec
	-fschedule-insns -fschedule-insns2
	-fstrict-aliasing -fstrict-overflow
	-ftree-switch-conversion -ftree-tail-merge
	-ftree-pre
	-ftree-vrp

       简单点说,-O2优化就是在-O优化的基础上,额外再追加这些优化选项,大概三十几个的样子吧。
       根据这个说明,我把Makefile改成了这样:

#此处省略一万字……  
OPTIMIZATION_FLAG = -O
OPTIMIZATION_FLAG += -fthread-jumps
OPTIMIZATION_FLAG += -falign-functions -falign-jumps
OPTIMIZATION_FLAG += -falign-loops -falign-labels
OPTIMIZATION_FLAG += -fcaller-saves
OPTIMIZATION_FLAG += -fcrossjumping
OPTIMIZATION_FLAG += -fcse-follow-jumps -fcse-skip-blocks
OPTIMIZATION_FLAG += -fdelete-null-pointer-checks
OPTIMIZATION_FLAG += -fdevirtualize
OPTIMIZATION_FLAG += -fexpensive-optimizations
OPTIMIZATION_FLAG += -fgcse -fgcse-lm
OPTIMIZATION_FLAG += -fhoist-adjacent-loads
OPTIMIZATION_FLAG += -finline-small-functions
OPTIMIZATION_FLAG += -findirect-inlining
OPTIMIZATION_FLAG += -fipa-sra
OPTIMIZATION_FLAG += -foptimize-sibling-calls
OPTIMIZATION_FLAG += -fpartial-inlining
OPTIMIZATION_FLAG += -fpeephole2
OPTIMIZATION_FLAG += -fregmove
OPTIMIZATION_FLAG += -freorder-blocks -freorder-functions
OPTIMIZATION_FLAG += -frerun-cse-after-loop
OPTIMIZATION_FLAG += -fsched-interblock -fsched-spec
OPTIMIZATION_FLAG += -fschedule-insns -fschedule-insns2
OPTIMIZATION_FLAG += -fstrict-aliasing -fstrict-overflow
OPTIMIZATION_FLAG += -ftree-switch-conversion -ftree-tail-merge
OPTIMIZATION_FLAG += -ftree-pre
OPTIMIZATION_FLAG += -ftree-vrp
#此处省略一万字……  

       按照GCC文档中的说法,此方法等同于-O2优化。
       重新编译,再试,BUG再现了!
       这些增加的项目太多了,逐一读文档排查太慢,于是乎将这些编译条件对半注释,重编译,最终,将BUG的问题锁定在这个编译优化选项上:

OPTIMIZATION_FLAG += -ftree-vrp

       然后,GCC文档中对-ftree-vrp这个项目的说明如下:

Perform Value Range Propagation on trees. This is similar to the constant propagation pass, but instead of values, ranges of values are propagated. This allows the optimizers to remove unnecessary range checks like array bound checks and null pointer checks. This is enabled by default at ‘-O2’ and higher. Null pointer check elimination is only done if ‘-fdelete-null-pointer-checks’ is enabled.

       然后看一下代码,问题明了了!
       上述说明的大概意思是,-ftree-vrp选项会自动分析代码逻辑,对一些范围可以判定的索引值边界进行优化(也就是删除)。
       那么再来看下代码:

#defien STACK_SIZE_MAX  50

//此处省略一万字…… 

DATA_TYPE_ST* g_pstStack[STACK_SIZE_MAX];

//此处省略一万字…… 

while((NULL != g_pstStack[uiIndex]->pstData) && (uiIndex < STACK_SIZE_MAX))  
{  
    for(unsigned int uiIdx=0; uiIdx!=g_pstStack[uiIndex]->pstData->uiIdx; uiIdx++)  
    {  
        //此处省略一万字……  
    }
    uiIndex++;
}  

       符号STACK_SIZE_MAX被同时用来声明数组长度和边界判断,而且边界判断条件又在数组索引之后,编译器很自然的就认为uiIndex必然为有效值,从而忽略了uiIndex < STACK_SIZE_MAX这一判断,最终导致循环条件失去控制,解引了一个野指针。交换了判定条件后,由于编译过程中对条件的判断是由左至右,索引边界判断在索引内容判断之前,优化项目无法裁定,所以两个条件都为有效。        知道了问题和原因就好办了,将while条件中的索引边界判断提至最前即可,同时盘点其他代码,对相应问题进行修改。
       至此,问题解决,也算加了一个经验点。

© 著作权归作者所有

P

Polarix

粉丝 3
博文 4
码字总数 7151
作品 2
大连
程序员
私信 提问
加载中

评论(1)

有理想的猪
有理想的猪
厉害,学习到了
一场版本升级引发的性能血案 - 王者归来

上次老码农在一场版本升级引发的性能血案 - 之数据历险一文中讲得口吐白沫, 最后还是没有一个结果, 反而被 OSCer 们各种吐槽: "看得我快快精尽人亡了""看着一头雾水""一脸懵逼进来,一脸懵逼...

罗格林
2018/06/04
211
3
Mybatis 错误Should be: #{propName,attr1=val1,attr2

Improper inline parameter map format. Should be: #{propName,attr1=val1,attr2; <update id="updateByPrimaryKeySelective" parameterType="com.wlyd.fmcgwms.persistence.beans.basic.W......

boonya
2016/04/22
212
0
ADB server didn't ACK问题

遇到了ADB Serverdidn't ACK的问题,重启电脑也不管用。 参考一个豌豆荚引发的血案,才知道,5037端口被占用会导致adb服务失效。 按照上述博文中的命令查找,发现是一个叫做tadb的进程占用了...

walk273
2013/05/25
348
0
为什么 redux 要返回一个新的 state 引发的血案(二)

Redux 的内幕(二) 一堆废话 index.js createStore.js 来个小彩蛋 未待完续 相关链接 Redux 的内幕(二) 一堆废话 继上一篇文章《为什么 redux 要返回一个新的 state 引发的血案》之后,过...

彭道宽
01/03
0
0
64位进程和32位进程通信问题,接收端收不到 SendMessage发送的消息

最近在做一个项目的时候,采用了win32的SendMessage方法来发送数据,本来都没问题,后来增加了一个项目,必须采用的目标平台是x64的,没想到居然没办法通信了。 网上找了很久解决方案,整整尝...

sharestone
2018/07/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何倒放视频

很多视频通过倒放制作,可以到达不可思议的视觉效果。那这些神奇的倒放视频上如何制作的呢?其实制作方法非常的简单,下面就一起来看看如何倒放视频吧! 具体步骤如下: 第一步: 首先打开手...

白米稀饭2019
32分钟前
2
0
可能是国内第一篇全面解读 Java 现状及趋势的文章

作者 | 张晓楠 Dragonwell JDK 最新版本 8.1.1-GA 发布,包括全新特性和更新! **导读:**InfoQ 发布《2019 中国 Java 发展趋势报告》,反映 Java 在中国发展的独特性,同时也希望大家对 Ja...

阿里巴巴云原生
32分钟前
3
0
Mybatis 配置详解

完整配置 mybatis-config.xml <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.......

xiaolyuh
33分钟前
4
0
与Windows用户共享Mac文件

与 Windows 用户共享 Mac 文件 若要允许用户从 Windows 电脑连接到您的 Mac,请打开文件共享和启用 SMB 共享。 在 Mac 上设置文件共享 在 Mac 上,选取苹果菜单 >“系统偏好设置”,然后点按...

W_Lu
40分钟前
6
0
CaaS: 内容是新的基础设施 Content-as-a-Service

内容是每家企业的必争之地,根据 CMI 的数据报告,88% 的 B2B 企业每天至少产生一篇内容。内容正在成为新的基础设施,Content as a Service 可以被简单理解为一种 CMS(Content Management ...

Authing
45分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部