getaddrinfo застревает навсегда, когда связано с sqlite3
У меня есть программа, которая требует DNS-запроса и подключения к базе данных sqlite3. Я определил, что он висит неопределенно getaddrinfo()
вызов. Итак, я создал тестовую программу (из busybox's 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: ");
}
Я могу видеть только "ДО 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) я кросс-скомпилированный sqlite3 для 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
1 ответ
Обновленная информация показывает libpthread
загружается, поэтому сценарий, скорее всего, SQLite был построен с включенной поддержкой pthread (по умолчанию на большинстве платформ), а ваш двоичный файл не был.
Подсказка - это наличие libpthread и зависание rt_sigsuspend()
это явное ожидание сигнала, и очень вероятно, что один поток ожидает выхода другого потока, что, конечно, никогда не происходит.
Основанием для этого является то, что, поскольку C и стандартная библиотека / libc предшествуют современной многопоточности, во многих случаях стандартная библиотека или API либо не являются повторно входящими, либо не потоко-безопасными, либо и то, и другое. Когда драконы бродили по земле, программисту приходилось явно вызывать альтернативные версии таких функций (имена с суффиксом "_r") или использовать альтернативные библиотеки (опять же обычно с суффиксом "_r"), чтобы гарантировать правильное поведение кода., pthreads изменил программный интерфейс в лучшую сторону, но поскольку безопасность потоков связана с затратами (производительность, иногда существенная и размер кода), он не будет включен, если вы не попросите об этом.
Когда вы используете -pthread
обычно происходит как минимум две вещи:
_REENTRANT
определяется как макрос препроцессора, это может изменить поведение во время компиляцииlibpthread
связан в (эквивалентно-lpthread
), это изменит поведение во время выполнения
Для уверенности потребовалась бы некоторая нетривиальная отладка, но, вероятно, произошло то, что ваш двоичный файл в итоге смешал функции заглушки pthread в uClibc с несколькими реальными функциями pthread. Это связано с тем, что libpthread не был загружен явно, были импортированы только символы pthread, на которые ссылается libsqlite.
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 не поддерживает это (стиль glibc libnss_xxx.so
), это одно исключено...
Есть еще одна возможность: PAM делает что-то подобное и может загружать несовместимую библиотеку (BerkeleyDB или, возможно, SQLite, как используется pam_userdb
или же pam-sqlite
). Тем не менее, ни uClibc, ни SQLite не используют PAM, и маловероятно, что он был связан случайно.)
поскольку dlopen()
используется, вы не увидите такие библиотеки (NSS или PAM) с ldd
работает под strace -e trace=file
должно помочь подтвердить, какие библиотеки используются, без обычного объема вывода.