最近遇到一个C++工程段错误的问题,因日志记录有回溯栈的信息,排查比较容易,解决也不难,但过程还是值得记录的。
起因年后第一天上班的头几分钟,运维同事发消息反馈,某一机子的一个程序每天凌晨2点准时段错误重启。随后下载并查询日志,发现是一个不怎么使用的服务出问题,当时移交时,这个服务已明确不使用了,但我不敢删除,刚好这个服务开了一个端口,恰好接收不合法数据时又不判断指针结束,从而出现了段错误。自去年部门调整接手代码至今,已发生多次疑难杂症,比如内存泄漏,比如sqlite3数据库文件被无故写入日志数据,比如这次段错误。本命年估计不太顺利。
排查及解决 代码定位出现问题的代码示例如下:
int split_cmd(char *src, const char *separeter){ char *ptrsrc = src + 1; int idx = 0; while (1) { if (*ptrsrc != ',') { ptrsrc++; continue; } else { *ptrsrc = 0; //tmpp[idx]=ptrsrc; if (2 == ++idx) break; } if (*ptrsrc == 0) { printf("error cmdn"); return -1; } }}
问题如下:
入参src为指针,但没有判断是否合法,而且代码中将指针直接+1,这是非法危险的。separeter本意是指定分割的字符,但并没有使用之。结束条件if (*ptrsrc != ',')过简单,段错误即发现在此处。循环使用while(1),而不是通过长度遍历。 重现问题
工程是已成型的服务,在网上找了几个 socket 调试助手,但无法连接成功,后使用 linux 的 nc 命令,发送命令如下:
printf "bad cmd" | nc 127.0.0.1 1314
重现问题。出错代码如下:
if (*ptrsrc != ',') { ptrsrc++; continue; }
解决方法根据上述列出的问题就能快速找到解决方法,这段代码是解析通过 socket 接收的数据,数据是有长度的,但代码并没有沿用,因为不敢对代码大改,因此只改了判断条件。如下:
int RemoteMonitor::SplitCMDbuf(char *src, const char *separeter){ if (src == NULL) return -1; ... while (1) { if (*ptrsrc != '' && *ptrsrc != ',') { ptrsrc++; continue; } ... }}
另外打印了远程机器IP和数据(十六进制格式),这样能知道是谁发了什么数据。示例代码如下:
client_len = sizeof(client_addr);if ((client_sockfd = accept(serverfd, (struct sockaddr *)&client_addr, &client_len)) < 0){ printf("accept error"); close(serverfd); continue;}char * client_ip = inet_ntoa(client_addr.sin_addr);// int client_port = ntohs(client_addr.sin_port);printf( recv from client %sn", client_ip);
因开年第一天上班,因此会议比较多,但还是在晚上升级好版本运行。翌日早上观察日志,结果在2点接收到非法指令,但没有出现段错误,分析得知,是一台机器发现了 http 的 GET 请求,将信息反馈至运维同事,后未有消息。
小结写代码这么久,对出错处理十分小心。函数体第一件事就是判断参数的合法,包括指针是否为空,数值是否在合理范围,等。
nc 命令在某些场合比较方便,比如本次调试,直接在同一机器上执行即可,不需要额外的工具。
对于 socket 的连接,个人觉得加上远程 IP 地址(或端口)会比较好,起码知道连接到哪台服务器,被哪个客户端连接。本例中就新加了客户端的 IP 的打印。