c - 与sqlite3链接时,getaddrinfo永远卡住

标签 c sqlite getaddrinfo nslookup uclinux

我有一个需要DNS查询和sqlite3 DB连接的程序。
我已经确定它在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交叉编译了sqlite3(版本3.6.23)
我感谢任何评论,帮助,调试程序的建议。
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  

最佳答案

更新的信息显示正在加载libpthread,因此该场景很可能是在启用pthread支持的情况下构建的(在大多数平台上是默认的),而您的二进制文件则不是。
线索是LIPpTrand的存在和挂在rt_sigsuspend(),这是一个明确的等待信号,很可能是一个线程等待另一个线程退出,这当然是永远不会发生的。
其背景是,由于C和标准库/libcpre-date contemporary threading,在许多情况下,标准库或API不是re-entrant就是不thread-safe或者两者都不是。当龙在陆地上漫游时,程序员通常必须显式地调用这些函数的替代版本(名称以“_r”为后缀)或使用替代库(通常以“_r”为后缀)来确保代码正确运行。pthreads更好地改变了编程接口,但是由于线程安全是要付出代价的(性能,有时是很重要的,代码大小),除非您要求,否则不会启用它。
当您使用-pthread时,通常会发生至少两种情况:
_REENTRANT被定义为预处理器宏,这可能会更改编译时行为
libpthread被链接(相当于-lpthread),这将改变运行时行为
这将需要一些非平凡的调试来确定,但可能发生的是,您的二进制文件最终将UCLBC中的存根p螺纹函数与少量的实际p螺纹函数混合。这是因为没有显式加载libpthread,只导入了libsqlite引用的pthread符号。
uClibc包含(和glibc一样)伪pthread函数(在nm上运行libc.soto see),这些函数被定义为“弱”符号,当真正的libpthread被显式加载时,它用它的“强”符号接管所有入口点。(这些存根的存在使得线程感知的库可以在不改变的情况下与非线程程序一起工作。)
使用显式-pthread构建二进制文件可以消除这种不匹配,并解决问题。
调试:
对编译后的二进制文件运行nm -gldd(uClibc版本),检查哪些符号在哪个库中,并查看是否可以发现不匹配。在运行程序时设置LD_DEBUG=all也应该很有用(您可能需要为此重定向stderr,会有很多输出)。
SQLite库有一个.init部分,但据我所知,它是一个不调用任何内部函数的存根,因此简单的链接不应该导致SQLite代码执行。
由于SQLite使用线程,请确保构建的线程是安全的,并且使用的是动态库。
当您链接到您的SQLite构建时,请确保同时使用.so(编译时)和-L(运行时)库路径,通常在编译前这样做&link将起作用(根据需要修改路径):

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库运行时/编译时库不匹配:当您使用系统时,会涉及到NSS(名称服务开关)。这将-R支持各种用户/组/主机数据库的各种库,具体取决于getaddrinfo(),包括本地文件、DNS、LDAP、Berkeley和很可能的SQLite。既然uClibcdoesn't support this(glibc风格dlopen()),那就排除了一件事。。。
还有另一种可能性:PAM做了类似的事情,可能加载了一个不兼容的库(BerkeleyDB或SQLite,如/etc/nsswitch.conflibnss_xxx.so所用)。不过,uClibc和SQLite都没有使用PAM,而且很可能是偶然链接的。)
由于使用了pam_userdb,您不会看到这样的库(NSS或PAM)带有pam-sqlite,在dlopen()下运行应该有助于确认正在使用的库,而不需要通常的输出量。

关于c - 与sqlite3链接时,getaddrinfo永远卡住,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18460896/

相关文章:

sql - sqlite3中的where子句

c++ - Linux c++ : getaddrinfo failed with EAI_AGAIN, curl 无法解析主机

c++ - printf 如何从 float 中提取数字?

c - 验证输入文件的内容

c++ - 在 C++ 中为 C 样式对象创建透明包装类

c - IPv6 的 getaddrinfo() 没有意义

linux - getaddrinfo 在使用 Yocto 构建的发行版上返回 EAI_ADDRFAMILY

c - 在 C : Infix to Prefix conversion 中使用指针反转字符串

sqlite - TortoiseSVN 结账 - "sqlite disk i/o error (S10)"

java - 构建文件夹紧凑树的最佳方法是什么?