当前位置:首页 > 公众号精选 > 程序员写个解
[导读]在我看来最不值得一提的BUG是那种可以重复复现的,他的稳定复现通常排查起来没啥技术含量, 早些年我处理一个不值得一提的BUG,BUG也很好复现,难点是复现时间固定在4小时左右,BUG由于文件资源未释放引起进程访问文件数目受限而崩溃,早期Android系统用该BUG获取到root权限, 本文向你分享,如何根据错误提示和参考手册找到故障点,指导新码农如何正确阅读Linux帮助手册(man page), 最后总结我的排查过程给小白一点实用的建议。好下面开始不如步入正题。需要调试的是一个监控程序,代码非常简单,2个线程执行不同的任务,每个任务都是间隔15秒执行一次,程序固定在大约4小时后崩溃。代码简单到用不着任何同步机制、没有任何通信,极少的内存访问,按理来说他就不应该存在BUG,然而还是发生了。

最不值得一提的BUG

在我看来最不值得一提的BUG是那种可以重复复现的,他的稳定复现通常排查起来没啥技术含量, 早些年我处理一个不值得一提的BUG,BUG也很好复现,难点是复现时间固定在4小时左右,BUG由于文件资源未释放引起进程访问文件数目受限而崩溃,早期Android系统用该BUG获取到root权限, 本文向你分享,如何根据错误提示和参考手册找到故障点,指导新码农如何正确阅读Linux帮助手册(man page), 最后总结我的排查过程给小白一点实用的建议。好下面开始不如步入正题。需要调试的是一个监控程序,代码非常简单,2个线程执行不同的任务,每个任务都是间隔15秒执行一次,程序固定在大约4小时后崩溃。代码简单到用不着任何同步机制、没有任何通信,极少的内存访问,按理来说他就不应该存在BUG,然而还是发生了。

第1个4小时:缩小排查范围,是什么引起段错误

在源码若干位置加上打印执行的函数、行号, 打开调试选项重新编译应用程序,开启coredump选项,耐心等待4小时后故障复现。gdb打开coredump 确认段错误(Segmentation fault),栈溯确认崩溃现场调用栈。段错误位于ti_ck_mutil函数第266行之后。
TickStatusIO():105ti_ck_mutil():266Segmentation fault (core dumped) (gdb) bt#0  0x401b28e0 in vfwprintf () from /lib/libc.so.6#1  0x00009d10 in ti_ck_mutil (cmdstr=0xbebffa4c, len=1) at src/ti.c:268#2  0x00008e2c in TickStatusIO () at src/initgpio.c:106#3  0x00009238 in main (argc=1, argv=0xbebffbf4) at src/initgpio.c:304

审查ti_ck_mutil函数内226行之后的代码,结合栈底位置是vfwprintf函数入口,基本可以确定导致崩溃位置是fread函数,fread可能会有什么错误呢?
int ti_ck_mutil(char *cmdstr, int count){ FILE *stream; char strout[256]; int ret, failcount = 0;  for (int i = 0; i < count; i++) { printf("%s()%d\n", __FUNCTION__, __LINE__);//226行 stream = popen(cmdstr, "r");//未检查文件是否成功 ret = fread(strout, sizeof(char), sizeof(strout), stream); // 228行  strout[ret] = '\0'; pclose(stream); // ... } return failcount;}
fread输入参数只有4个,猜测可能存在的失败原因有3点:
1、被编译器优化后strout的缓存不是256

但后面用的是算数表达式sizeof,就算被优化也不会造成错误。
观点:暂时不去瞎想2、fread写入最后一个字符时溢出。

strout后第256地址也被填写了,实际我读写的文件不超过64byte,不应该超过256。
即使第256地址被fread写了,相当于内存访问越接。访问越接发生什么错误都不奇怪,轻微越接会影响附近变量的值,比如ret和stream的值改变,大范围越界破坏调用栈。观点:猜测fread可能访问越限,但绝对没破坏调用栈。若破坏调用栈,那么栈不会是整整齐齐打印4个函数,而是输出若干问号(“?? ()”),找不到函数名称标签。
#0  0x000028e0 in ?? () #1  0x000038e8 in ?? () #2  0x000048ec in ?? () #3  0x000068e0 in ?? () #4  0x00009d10 in ti_ck_mutil (cmdstr=0xbebffa4c, len=1) at src/ti.c:268#5  0x00008e2c in TickStatusIO () at src/initgpio.c:106#6  0x00009238 in main (argc=1, argv=0xbebffbf4) at src/initgpio.c:304

3、stream文件描述符无效观点:有可能,源码未对popen返回结果做判断。

第2个4小时:是内存越界?还是资源不足?

于是结合猜测2和3,对源码做2处理修改:1、不向fread传递完整内存长度,保证最后一个字符不被fread填写 2、判断popen返回值
stream = popen(cmdstr, "r");ret = fread(strout, sizeof(char), sizeof(strout), stream); 修改后 stream = popen(cmdstr, "r");if (stream == 0) { perror("popen error:");}ret = fread(strout, sizeof(char), sizeof(strout) - 1, stream);

继续等待4小时,程序依旧崩溃,输出崩溃前提示执行popen失败,返回值0,错误原因记录在errno里,errno指示打开太多文件,资源不足。
	
popen error:: Too many open files

机理分析:为什么文件打开太多?

进一步定位到故障点在popen函数上,问题是:啥叫文件打开太多?查看popen帮助介绍:man popen。或许能给我解释
RETURN VALUEThe popen() function returns NULL if the fork(2) or pipe(2) calls fail, or if it cannot allocate memory.
本质上popen是个“壳",它返回0的原因有两个:1、它间接调用fork()创建子进程执行脚本,间接调用pipe()创建管道,子进程输出信息从管道传递到父进程。2、没有足够的内存分配。从第2点:没有足够的内存方向去排查,无非是内存泄漏咯,通常是申请内存有释放干净导致。c语言标准内存分配函数有malloc、calloc、realloc、reallocarray,对应的释放函数只有free。我应该在源码上搜索,是否所有“分配函数和释放函数都一一配对”,哦~别忘了,小白可能还不清楚,除了常用的malloc外,还有像mmap这样的内存分配函数,它有专用的释放函数munmap从搜索结果上看,数目是能对得上的,暂且粗略的判定不存内存泄漏。更仔细的排查方向应该是:确定代码执行流真的执行到释放函数,而不是单纯地看数目是否匹配



在继续阅读popen的errors段落描述。
ERRORSThe popen() function does not set errno if memory allocation fails. If the underlying fork(2) or pipe(2) fails, errno is set appropriately. If the type argument is invalid, and this condition is detected, errno is set to EINVAL.
popen不会因为内存分配失败而在errno记录错误码,如果是fork()或pipe()函数执行失败则在errno设置相应错误码。忙半天忙个寂寞,年轻人,别学会写一、二、三,就自以为无师自通懂得写四、写一万。读完man全文再入手好不好!既然errno提示具体错误信息,就不可能是内存泄漏,执行失败原因一定是Too many open files的字面意思。回想以前初学Linux时有个知识点:为了防止某用户打开过多的文件,系统对进程件访问数目有限制,默认是10242016年4月参加宋宝华的线下培训,他说Android刚出来时有个提权的方法(root权限):创建1024个无用子进程资源且不释放,第1025个进程就能得到root权限命令查看应用程序运行一段时间后,有多少文件描述符号(file descriptor)没有释放。果然,每间隔15秒文件描述符就多一个。256分钟后达到1024个文件描述符,时间上和软件4小时崩溃很接近。
watch -n 1 ls -l /proc/PID/fd

再用之前的筛选方法:排查open和close的函数是一一匹配。发现open关键词筛选出
6行,close作为关键词筛出5行。opendir没有对应的close。


捂脸!!!“Linux下一切皆是文件”我还没理解透彻,没意识到打开目录(opendir)也是文件资源,应用程序某线程每间隔15秒就访问一次目录。man opendir确认closedir是它的配对关闭函数。
SEE ALSOopen(2), closedir(3), dirfd(3), readdir(3), rewinddir(3), scandir(3), seekdir(3), telldir(3)
添加上closedir后故障得以修复。

顺带提一下

贴图用的搜索工具不是grep而是我自己写的脚本jgrep,它的用法和grep完全一样,输入前面的数字能打开对于文件所在行,对于搜索源码、系统配置文件检索、跳转特别适用。如果你对jgrep感兴趣的话,在我的公众号“程序员写个解”发送 “20220411” 可获取。




总结建议

BUG成功得以修复,它本是不应该犯的错误,在这里我给自己和读者建议:1、以后使用不熟悉的API,首先查阅他的帮助手册2、对于内存分配函数有相互独立的API,比如malloc对应free、mmap对应munmap。跟着手册建议的API去掉释放资源,避免不可预知的故障发生。最后,如果你觉得文章对你有所帮助,有启发作用。欢迎点击,把今天的内容分享给你的好友,和他一起讨论学习。
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭