我有一个需要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)
我感谢任何评论,帮助,调试程序建议。
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
答案 0 :(得分:2)
更新的信息显示正在加载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中的stub pthread函数与少数真正的pthread函数混合在一起。这是因为没有显式加载libpthread,只导入了libsqlite引用的pthread符号。
uClibc包含(和glibc一样)虚拟pthread函数(在nm
上运行libc.so
来查看),这些被定义为“弱”符号,当显式加载真正的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,Berkeley,很可能SQLite。由于uClibc doesn't support this(glibc style libnss_xxx.so
),这是排除在外的一件事......
还有另一种可能性:PAM做类似的事情,并且可能加载不兼容的库(BerkeleyDB或可能是SQLite,由pam_userdb
或pam-sqlite
使用)。虽然uClibc和SQLite都没有使用PAM,但它不可能被意外链接。)
由于使用dlopen()
,您将看不到ldd
的{{1}}这样的库(NSS或PAM),在strace -e trace=file
下运行应该有助于确认正在使用的库,而不是通常的产量。