1

看来,我在 Windows 中发现了一个错误……好吧,别这么可怜了。我正在尝试对 UDP 进行通用 sendto() 操作,偶尔发现 WinXP(32 位,SP3,在真实和虚拟机上检查)返回使用 WSAGetLastError() 发送的“-1”字节作为错误 10014(又名 WSAEFAULT)。仅在 IPv4 地址上发生(与 IPv6 目标相同的代码完美运行)。重现的主要条件是使用在全局范围内声明的“const struct sockaddr_in”。这是 VS2010 的纯 C 代码(我也尝试使用 Eclipse+MinGW,得到相同的结果):

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <winsock2.h>
#include <stdint.h>

#pragma comment(lib, "Ws2_32.lib")

#define INADDR_UPNP_V4 0xEFFFFFFA
#define htons(x) ((((uint16_t)(x) & 0xFF00) >> 8) | (((uint16_t)(x) & 0x00FF) << 8))
#define htonl(x) ((((uint32_t)(x) & 0xFF000000) >> 24) | (((uint32_t)(x) & 0x00FF0000) >> 8) | (((uint32_t)(x) & 0x0000FF00) << 8) | (((uint32_t)(x) & 0x000000FF) << 24))

// Magic "const" qualifier, causes run-time error
const struct sockaddr_in addr_global = {
    AF_INET,
    htons(1900),
    {
            htonl(INADDR_UPNP_V4)
    },
    {0},
};

int main(int argc, char** argv)
{
#define CR_LF "\r\n"

    // these two lines to un-buffer console window output at Win32, see URL below for details
    // http://wiki.eclipse.org/CDT/User/FAQ#Eclipse_console_does_not_show_output_on_Windows
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    printf("Started\n");

    const struct sockaddr_in addr_local = {
            AF_INET,
            htons(1900),
            {
                    htonl(INADDR_UPNP_V4)
            },
            {0},
    };

    const char *MSEARCH_REQUEST_V4 = "M-SEARCH * HTTP/1.1"CR_LF
    "Host:239.255.255.250:1900"CR_LF
    "MAN:\"ssdp:discover\""CR_LF
    "ST:ssdp:all"CR_LF
    "MX:3"CR_LF
    CR_LF;

    const int MSEARCH_LEN = strlen(MSEARCH_REQUEST_V4);

    WSADATA wsaData;
    int res = WSAStartup(MAKEWORD(2, 2), &wsaData);

    int af = AF_INET;
    int sock_id = socket(af, SOCK_DGRAM, IPPROTO_UDP);
    if (-1 == sock_id) {
        printf("%s: socket() failed with error %i/%i\n", __FUNCTION__,
                errno, WSAGetLastError());
        return 1;
    }

    int data_sent = 0;

    printf("1st sendto()\n");
    data_sent = sendto(sock_id, MSEARCH_REQUEST_V4,
            MSEARCH_LEN, 0,
            (const struct sockaddr * const)&addr_local,
            sizeof(struct sockaddr_in));
    if (data_sent < 0) {
        printf("%s: sendto(local) failed with error %i/%i\n", __FUNCTION__,
                errno, WSAGetLastError());
    }

    printf("2nd sendto(), will fail on WinXP SP3 (32 bit)\n");
    data_sent = sendto(sock_id, MSEARCH_REQUEST_V4,
            MSEARCH_LEN, 0,
            (const struct sockaddr * const)&addr_global,
            sizeof(struct sockaddr_in));
    if (data_sent < 0) {
        printf("%s: sendto(global) failed with error %i/%i\n", __FUNCTION__,
                errno, WSAGetLastError());
    }

    closesocket(sock_id);

    res = WSACleanup();
    printf("Finished\n");
    return 0;
}

因此,例如,如果您在 Win7 上运行此代码,则绝对没问题。但是,如果 WinXP 配备了“const”限定符(参见上面的“Magic”注释),则 WinXP 无法使用 addr_global。此外,“输出”窗口显示:

SendtoBugXP.exe 中 0x71a912f4 处的第一次机会异常:0xC0000005:访问冲突写入位置 0x00415744。

在“Autos”窗口的帮助下,很容易找出 0x00415744 位置是 addr_global.sin_zero 字段的地址。看来,WinXP 在那里写零并违反内存访问标志。或者这只是愚蠢的我,试图走错门?

非常感谢您的评论。提前致谢。

4

2 回答 2

1

是的,你发现了一个错误。sendto() 将该参数声明为 const,但还是写入了它。不过祝你好运。提示:它可能在您的防病毒软件或防火墙中。

于 2013-05-02T20:05:28.200 回答
0

总结其他论坛的结果:是的,这是 Windows 错误,在“桌面”和 Win2003 中存在于“桌面”部分的 WinXP 和“服务器”部分。WinSock 代码确实尝试用零强制填充“sin_zero”字段。并且“const”全局范围会导致内存访问冲突。堆栈跟踪是这样的:

线程 [1] 0(暂停:信号:SIGSEGV:分段错误)
WSHTCPIP!WSHGetSockaddrType() at 0x71a912f4
0x71a52f9f
WSAConnect() at 0x71ab2fd7
main() at tests_main.c:77 0x401584

其他人在 bind() 上观察到的相同行为。

于 2013-05-05T17:43:18.050 回答