二个64位的数据,相减后如果为64位的负数,用int强转以后,结果是为正数。
char *buffer = calloc(sz*2+1, sizeof(char));
先分配一块内存,长度是要 dump 的数据长度两倍加一。然后循环
sprintf(buffer+i*2, "%02x", data[i]);
这就是我们看了几次没留意的 bug 所在:data 是 const char 类型,有符号的。当 data[i] 是一个负数时, %02x 不一定只输出 3 个字节(别忘记字符串结尾的 )。buffer 这块内存就被写越界了。
一起 select 引起的崩溃
rabbitmq-c 为了控制 connect 的超时时间,使用了非阻塞 connect ,然后使用 select 判断 fd 是否可写来判定连接是否建立。 select 调用未考虑 sockfd 大于 FD_SETSIZE 。linux 的 select 手册中有这样一行注释:
Anfd_set is a fixed size buffer. Executing FD_CLR() or FD_SET() with a value of fd that is negative or is equal to or larger than FD_SETSIZE will result in undefined behavior. Moreover, POSIX requires fd to be a valid file descriptor.
这是一个很容易被程序员忽视的坑。FD_SET 其实是一个位数组,linux 默认是 1024 bit 。而 FD_SET 只是简单的把 fd 当作一个序号按位向位数组写数据。select 应该也是如此。所以当 fd 大于 1024 时,就会写越界。正确的方法是当需要 select 的 fd 大于等于 1024 时,最好在堆上分配 FD_SET ,分配空间应至少保证 fd/8 + 1 以上;或者用 poll / WSAPoll 更好。
一般的程序不会有这个问题是因为通常一个进程需要的文件 fd 不会超过 1024 。而我们的系统管理了大量的外部连接,超过 1024 轻而易举。之前,机房间的网络正常,只在程序启动时创建连接一次连接。这时尚无外部连接,这个连接的 fd 一定是小于 1024 的,而之后也不会发生重连。
http://blog.codingnow.com/2014/02/select_bug.html#more