【问题标题】:getaddrinfo stucks forever when linked with sqlite3与 sqlite3 链接时,getaddrinfo 永远卡住
【发布时间】:2013-08-27 08:45:27
【问题描述】:

我有一个需要 DNS 查询和 sqlite3 数据库连接的程序。 我已经确定它在getaddrinfo() 呼叫中无限期挂起。所以我只用这个调用创建了一个测试程序(来自busybox的nslookup.c)。当我不链接 libsqlite3 时,它按预期工作。代码段如下:

#include <arpa/inet.h>
#include <netdb.h>
#include <resolv.h>
#include <string.h>
#include <signal.h>

static int sockaddr_to_dotted(struct sockaddr *saddr, char *buf, int buflen)
{
    if (buflen <= 0) return -1;
    buf[0] = '\0';
    if (saddr->sa_family == AF_INET)
    {
        inet_ntop(AF_INET, &((struct sockaddr_in*)saddr)->sin_addr, buf, buflen);
        return 0;
    }
    if (saddr->sa_family == AF_INET6)
    {
        inet_ntop(AF_INET6, &((struct sockaddr_in6*)saddr)->sin6_addr, buf, buflen);
        return 0;
    }
    return -1;
}
static int print_host(const char *hostname, const char *header)
{
    char str[128]; /* IPv6 address will fit, hostnames hopefully too */
    struct addrinfo *result = NULL;
    int rc;
    struct addrinfo hint;

    memset(&hint, 0, sizeof(hint));
    /* hint.ai_family = AF_UNSPEC; - zero anyway */
    /* Needed. Or else we will get each address thrice (or more)
     * for each possible socket type (tcp,udp,raw...): */
    hint.ai_socktype = SOCK_STREAM;
    // hint.ai_flags = AI_CANONNAME;
    printf("BEFORE GETADDRINFO\n");
    rc = getaddrinfo(hostname, NULL /*service*/, &hint, &result);
    printf("AFTER GETADDRINFO\n");
    if (!rc)
    {
        struct addrinfo *cur = result;
        // printf("%s\n", cur->ai_canonname); ?
        while (cur)
        {
            sockaddr_to_dotted(cur->ai_addr, str, sizeof(str));
            printf("%s  %s\nAddress: %s\n", header, hostname, str);
            str[0] = ' ';
            if (getnameinfo(cur->ai_addr, cur->ai_addrlen, str + 1,
                            sizeof(str) - 1, NULL, 0, NI_NAMEREQD))
                str[0] = '\0';
            puts(str);
            cur = cur->ai_next;
        }
    }
    else
    {
        printf("getaddrinfo('%s') failed: %s", hostname, gai_strerror(rc));
    }
    freeaddrinfo(result);
    return (rc != 0);
}

int main(int argc, char **argv)
{
    if (argc != 2)
        return -1;

    res_init();
    return print_host(argv[1], "Name: ");
}

我只能在输出中看到“BEFORE GETADDRINFO”。 我也尝试过跟踪程序。 (我的 dns 服务器是 192.168.11.11,并查询“www.google.com”)这是它暂停的地方:

socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.11.11")}, 16) = 0
send(3, "\0\2\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1", 32, 0) = 32
pselect6(4, [3], NULL, NULL, {10, 0}, 0) = 1 (in [3], left {9, 988000000})
recv(3, "\0\2\201\200\0\1\0\5\0\0\0\0\3www\6google\3com\0\0\1\0"..., 512, 0) = 112
close(3)                                = 0
rt_sigprocmask(SIG_SETMASK, NULL, [RTMIN], 8) = 0
rt_sigsuspend([]

我的编译器是bfin-linux-uclibc-gcc(gcc 版本 4.1.2) 我为bfin-linux-uclibc(版本3.6.23)交叉编译了sqlite3

感谢任何评论、帮助、调试程序建议。

strace -e trace=file mybinary的输出:

stat("/etc/ld.so.cache", {st_mode=S_IFREG|0644, st_size=1073, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY)      = 3
open("/lib/libsqlite3.so.0", O_RDONLY)  = 3
open("/lib/libstdc++.so.6", O_RDONLY)   = 3
open("/lib/libm.so.0", O_RDONLY)        = 3
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
open("/lib/libc.so.0", O_RDONLY)        = 3
open("/lib/libdl.so.0", O_RDONLY)       = 3
open("/lib/libpthread.so.0", O_RDONLY)  = 3
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
open("/lib/libc.so.0", O_RDONLY)        = 3
open("/lib/libm.so.0", O_RDONLY)        = 3
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
open("/lib/libc.so.0", O_RDONLY)        = 3
open("/lib/libc.so.0", O_RDONLY)        = 3
open("/lib/libc.so.0", O_RDONLY)        = 3
open("/lib/libc.so.0", O_RDONLY)        = 3
open("/lib/libc.so.0", O_RDONLY)        = 3
stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755, st_size=29824, ...}) = 0
open("/etc/resolv.conf", O_RDONLY)      = 3
open("/etc/hosts", O_RDONLY)            = 3

bfin-linux-uclibc-nm -g mybinary的输出

00004fc4 A ___bss_start  
         w ___deregister_frame_info@@GCC_3.0  
00004f10 D ___dso_handle  
00004fc4 A __edata  
00004fe0 A __end  
00000d60 T __fini  
         U _freeaddrinfo  
         U _gai_strerror  
         U _getaddrinfo  
         U _getnameinfo  
         U _inet_ntop  
00000534 T __init  
         w __Jv_RegisterClasses  
00000aa4 T _main  
         U _printf  
         U _puts  
         w ___register_frame_info@@GCC_3.0  
         U ___res_init  
00000e18 R __ROFIXUP_END__  
00000de0 R __ROFIXUP_LIST__  
00000670 T ___self_reloc  
00020000 A __stacksize  
0000060c T __start  
         U ___uClibc_main  

【问题讨论】:

    标签: c sqlite getaddrinfo nslookup uclinux


    【解决方案1】:

    更新的信息显示 libpthread 正在加载,因此该场景很可能是在构建 SQLite 时启用了 pthread 支持(大多数平台上的默认设置),而您的二进制文件没有。

    线索是 libpthread 的存在和rt_sigsuspend() 处的挂起,这是显式等待信号,很可能是一个线程在等待另一个线程退出,当然这永远不会发生。

    其背景是,由于 C 和标准库/libc pre-date contemporary threading,在很多情况下标准库或 API 不是re-entrant 或不是thread-safe,或两者兼而有之。回到当龙在这片土地上漫游时,程序员必须显式调用此类函数的替代版本(名称以“_r”为后缀)或使用替代库(同样通常带有“_r”后缀)以确保代码行为正确是很常见的. pthreads 改进了编程接口,但由于线程安全是有代价的(性能、有时是实质性的和代码大小),除非您要求,否则它不会启用。

    当您使用-pthread 时,通常至少会发生两件事:

    • _REENTRANT 被定义为预处理器宏,这可能会改变编译时行为
    • libpthread 被链接到(相当于-lpthread),这将改变运行时行为

    需要一些不平凡的调试才能确定,但​​可能发生的事情是你的二进制文件最终将 uClibc 中的存根 pthread 函数与少数真正的 pthread 函数混合在一起。这是因为 libpthread 没有显式加载,只导入了 libsqlite 引用的 pthread 符号。 uClibc 包含(和 glibc 一样)虚拟 pthread 函数(在 libc.so 上运行 nm 以查看),这些被定义为“弱”符号,当显式加载真正的 libpthread 时,它会以其“强”接管所有入口点符号。 (这些存根的存在是为了让线程感知库无需更改即可与非线程程序一起使用。)

    使用显式 -pthread 构建二进制文件可消除这种不匹配并解决问题。


    用于调试:

    对已编译的二进制文件运行 nm -g 和 ldd(uClibc 版本),检查哪些符号在哪个库中,看看是否能发现不匹配的地方。在运行程序时设置LD_DEBUG=all 也应该很有用(您可能希望为此重定向stderr,会有很多输出)。

    SQLite 库有一个 .init 部分,但据我所知,它是一个不调用任何内部函数的存根,因此简单的链接不应导致 SQLite 代码执行。

    由于 SQLite 使用线程,请确保您构建线程安全,并使用 .so 动态库。

    当您链接您的 SQLite 构建时,请确保同时使用 -L(编译时)和 -R(运行时)库路径,通常在编译和链接之前使用类似这样的方法(根据需要修改路径):

    export CFLAGS=-L/usr/local/sqlite3/lib
    export LDFLAGS=-R/usr/local/sqlite3/lib
    

    测试程序:

    #include<stdio.h>
    #include<sqlite3.h>
    
    int main(int argc,char *argv[]) {
        printf("SQLite version (compile): %s\n",SQLITE_VERSION);
        printf("SQLite version (API): %s\n",sqlite3_libversion());
    }
    

    如果你运行它并获得不同的版本,那么你的构建环境肯定有问题。


    这些猜测并不能直接解决这个问题,但我还是留在这里记录一下吧:

    通常我的第一个猜测通常是 NSS 库运行时/编译时库不匹配:当您使用系统时,getaddrinfo() 涉及 NSS(名称服务切换)。这将dlopen() 各种库支持各种用户/组/主机数据库,取决于/etc/nsswitch.conf,包括本地文件、DNS、LDAP、伯克利和很可能SQLite。由于 uClibc doesn't support this(glibc 风格 libnss_xxx.so),所以排除了一件事......

    还有另一种可能性:PAM 做了类似的事情,并且可能加载不兼容的库(BerkeleyDB 或可能的 SQLite,如 pam_userdb 或 pam-sqlite 使用的那样)。但是 uClibc 和 SQLite 都没有使用 PAM,而且它不可能被意外链接。)

    由于使用了dlopen(),您不会看到带有ldd 的此类库(NSS 或PAM),在strace -e trace=file 下运行应该有助于确认正在使用哪些库,而没有通常的输出量。

    【讨论】:

    • 我在编译和运行时环境(相同版本和 md5)上有相同的 libsqlite3.so。 “ldd”的输出:libsqlite3.so.0、libstdc++.so.6、libm.so.0、libgcc_s.so.1、libc.so.0、/lib/ld-uClibc.so.0。 'nm' 没有什么奇怪的。
    • 好的,libpthread,我猜想使用 libsqlite 和它的 libpthread 依赖关系会出错。 getaddrinfo() 是线程安全的,并且与 pthread 的行为不同。尝试使用gcc -pthread 构建代码,或使用-DSQLITE_THREADSAFE=0 构建sqlite。
    • 链接 pthread 解决了我的问题。但这对我来说没有意义,为什么这会使 dns 查询卡住?您还可以将其添加到原始答案中,以便我可以将其设置为已接受吗?
    猜你喜欢
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-31
    • 2021-04-14
    • 1970-01-01
    相关资源
    最近更新 更多