调用 FD_ISSET 时的未定义行为

Undefined behavior when calling FD_ISSET

我有一个程序正在尝试诊断带有 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) ) {
    ...
}

问题消失了。

很明显,我调用了某种未定义的行为。但是,我查看了手册页,但没有看到任何似乎相关的 warnings/requirements。到底是什么导致了这次崩溃?

编辑:运行 gdb 或 valgrind 下的程序使错误消失。我无法找到崩溃源的唯一方法是正常 运行 程序,然后从另一个终端使用 gdb 附加。

使用 fd_set/FD_SET/FD_ISSET 需要注意的一件事是这些集合的大小是固定的——fd_set 中的 FD_SETSIZE 个文件描述符只有足够的空间。在 Linux 上(你不说你正在使用什么 OS)FD_SETSIZE 是 1024,它匹配 1024 个文件描述符的默认 ulimit,所以你不会看到问题,除非你'我们已经为您的进程提高了 ulimit(1024 只是一个软限制——硬限制实际上要大得多)。

如果可能是这种情况,您应该始终检查以确保fd < FD_SETSIZE 调用FD_SET.类似于:

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;
    }
}

您可能还想确保 none 您的文件描述符是其他一些无效值(例如 -1 可能来自某些早期系统调用中的错误),因为这同样会导致如果您尝试将它与 FD_SET 或 FD_ISSET

一起使用,则 fd_set 中的越界访问