我有一个带有 Heisenbug 的程序,我正在尝试诊断。结合使用 gdb 和 Ghidra,我已经能够追踪到特定部分的崩溃。这是我的代码的要点:
FD_ZERO(&readfds);
FD_SET(sock1, &readfds);
max_fd = sock1;
if ( some_condition ) {
FD_SET(sock2, &readfds);
if ( sock2 > max_fd ) {
max_fd = sock2;
}
}
if ( select(max_fd+1, &readfds, NULL, NULL, &timer) == -1 ) {
goto error;
}
if ( FD_ISSET(sock1, &readfds) ) {
...
}
if ( FD_ISSET(sock2, &readfds) ) {
...
}
我已经能够将崩溃范围缩小到最后一个
FD_ISSET
宏的扩展。具体来说,它调用 __fdelt_chk
,最终导致我的 shell 报告
*** buffer overflow detected ***: terminated
但是,如果我将代码更改为
bool using_sock2 = false;
...
if ( some_condition ) {
using_sock2 = true;
...
}
...
if ( using_sock2 && FD_ISSET(sock2, &readfds) ) {
...
}
问题就消失了。
显然,我调用了某种未定义的行为。但是,我查看了手册页,没有看到任何似乎相关的警告/要求。究竟是什么导致了这次崩溃?
编辑:在 gdb 或 valgrind 下运行程序会使错误消失。我无法找到崩溃根源的唯一方法是正常运行程序,然后从另一个终端附加 gdb。
使用 fd_set/FD_SET/FD_ISSET 需要注意的一件事是,这些集合的大小是固定的——fd_set 中只有足够的空间容纳 FD_SETSIZE 文件描述符。在Linux上(你没有说你正在使用什么操作系统)FD_SETSIZE是1024,它与1024个文件描述符的默认ulimit匹配,所以你不会看到问题,除非你已经提高了进程的ulimit(1024是只是一个软限制——硬限制实际上要大得多)。
如果出现这种情况,您应该始终检查以确保在调用 FD_SET 之前fd < FD_SETSIZE
。比如:
FD_ZERO(&readfds);
if (sock1 >= FD_SETSIZE) {
error("too many file descriptors!");
abort(); }
FD_SET(sock1, &readfds);
max_fd = sock1;
if ( some_condition ) {
if (sock2 >= FD_SETSIZE) {
error("too many file descriptors!");
abort(); }
FD_SET(sock2, &readfds);
if ( sock2 > max_fd ) {
max_fd = sock2;
}
}
您可能还想确保您的文件描述符中没有任何其他无效值(例如可能来自某些早期系统调用中的错误的-1),因为这同样会导致文件描述符中的越界访问fd_set 如果您尝试将其与 FD_SET 或 FD_ISSET 一起使用