调试某程序非常简单的程序,简单到认为不可能存在缺陷,但该BUG处理时间超过12小时:
- 程序属于后台进程,监控系统每隔15秒检查外设IO状态,IO异常后发出报警或复位外设,外设都在linux下有/sys/class等节点。
程序有规律性4-5小时后崩溃
程序崩溃原因也非常简单:
- 某文件反复打开未释放,打开文件数超过linux单进程最大打开文件数。
- 未对文件打开成功检查,持无效文件描述符写文件导致Segmentation fault
下面是排除过程
下面是程序运行打印信息,可看到文件在调用ti_ck_mutil函数第
266行时崩溃
TickStatusIO():124
ti_ck_mutil():266
ti_ck_mutil():272
ti_ck_mutil():276
TickStatusIO():105
ti_ck_mutil():266
Segmentation fault (core dumped)
gdb打开coredump查看栈状态
(gdb) bt
#0 0x401b28e0 in
vfwprintf () from /lib/libc.so.6
#1 0xe92d47f0 in ?? ()
#2 0x00009d10 in
ti_ck_mutil (ptiem=0xbebffa4c, len=1)
at src/ckself.c:268
#3 0x00008e2c in TickStatusIO () at src/initgpio.c:106
#4 0x00009238 in main (argc=1, argv=0xbebffbf4) at src/initgpio.c:304
最终显示在c库函数vfwprintf里崩溃,并在在二行的栈地址以及崩溃了,显示" in ?? ()",他的调用者正是ti_ck_mutil,并且更进一步显示崩溃点在
268行而不是266行。
如下是ti_ck_mutil的主要代码
- int ti_ck_mutil(struct ck_self *ptiem, int len)
- {
- FILE *stream;
- char strout[256];
- int ret;
- int failcount = 0;
- for (int i = 0; i < len; i++) {
- printf("%s()%d\n", __FUNCTION__, __LINE__); //226行
- stream = popen(ptiem[i].dir, "r"); //未检查文件是否成功
- ret = fread( strout, sizeof(char), sizeof(strout), stream);
- strout[ret] = '\0';
- pclose(stream);
- // ...
- }
- return failcount;
- }
复制代码
fread输入参数只有4个,可能存在的错误无非是3点:
- 被编译器优化后strout的缓存不是256(纯属胡乱猜测,可以通过查看map文件看到具体大小)
- fread写入最后一个字符时溢出(实际我读写的文件不超过64byte,不应该超过256)
- stream文件描述符无效
于是首先对fread修改,保证最后一个字符不被fread填写。
ret = fread( strout, sizeof(char), sizeof(strout)
- 1, stream);
测试依旧崩溃
接着对stream检查,代码如下
- int ti_ck_mutil(struct ck_self *ptiem, int len)
- {
- FILE *stream;
- char strout[256];
- int ret;
- int failcount = 0;
- for (int i = 0; i < len; i++) {
- printf("%s()%d\n", __FUNCTION__, __LINE__); //226行
- stream = popen(ptiem[i].dir, "r"); //未检查文件是否成功
- if (NULL == stream) {
- continue;
- }
- // 读出内容,并在末尾添加字符串终结符号
- //存在溢出,应该用 sizeof(strout) - 1
- ret = fread( strout, sizeof(char), sizeof(strout) - 1, stream);
- strout[ret] = '\0';
- pclose(stream);
- // ...
- }
- return failcount;
- }
复制代码
测试4小时后程序
不崩溃,但当IO异常后也既不报警也不复位,相当于程序不工作了,由上面代码可知必定是popen失败导致。
查看man popen
RETURN VALUE The popen() function returns NULL if the fork(2) or pipe(2) calls fail, or if it cannot allocate memory.
|
man fork
RETURN VALUE On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately. |
问题很明显popen的失败是由于
fork函数失败或
内存不足,而这两个失败的原因是
不能创建子进程。
该程序运行内存极小,不可能引发内存不足。
根据经验linux对每个用户默认打开文件数做了限制,也预示着代码里有未释放的文件描述符
为了证明再次运行应用程序,接着每秒查看程序打开的文件个数
watch -n 1 ls /proc/<pid>/fd
果不其然文件个数每15s递增一个。当达到最大限制1024后文件打开失败,ti_ck_mutil只能continue,也就是原始程序在大约4小时崩溃的原因(15s打开新文件,大约4.2小时达到1024上限)
全工程搜索open关键词,看各调用后是否存在close,
果然有个opendir后忘记closedir。
加上后bug解决
本文来自论坛,点击查看完整帖子内容。