你可能也会遇到:低级bug耗费12小时Fix
调试某程序非常简单的程序,简单到认为不可能存在缺陷,但该BUG处理时间超过12小时:程序属于后台进程,监控系统每隔15秒检查外设IO状态,IO异常后发出报警或复位外设,外设都在linux下有/sys/class等文件节点。程序有规律性4-5小时后崩溃程序崩溃原因也非常简单:某文件反复打开未释放,打开文件数超过linux单进程最大打开文件数。未对文件打开成功检查,持无效文件描述符写文件导致Segmentation faul下面是排除过程下面是程序运行打印信息,可看到文件在调用ti_ck_mutil函数第266行时崩溃TickStatusIO():124ti_ck_mutil():266ti_ck_mutil():272ti_ck_mutil():276TickStatusIO():105ti_ck_mutil():266Segmentation 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) - 1ret = fread( strout, sizeof(char), sizeof(strout) - 1, stream);strout[ret] = '\0';pclose(stream);// ...}return failcount;}复制代码测试4小时后程序不崩溃,但当IO异常后也既不报警也不复位,相当于程序不工作了,由上面代码可知必定是popen失败导致,函数内部一直continue。查看man popenRETURN VALUEThe popen() function returns NULL if the fork(2) or pipe(2) calls fail, or if it cannot allocate memory.man forkRETURN VALUEOn success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 isreturned 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解决欢迎微博@EEWORLD