0

DNS クエリと sqlite3 DB 接続を必要とするプログラムがあります。getaddrinfo()通話中に無期限にハングすることが判明しました。nslookup.cそこで、この呼び出しだけで(busybox の から) テスト プログラムを作成しました。リンクしないと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  
4

1 に答える 1

2

更新された情報はlibpthreadロードされていることを示しているため、このシナリオは、SQLite が pthread サポートを有効にしてビルドされた可能性が高く (ほとんどのプラットフォームのデフォルト)、バイナリはそうではありませんでした。

手がかりは libpthread の存在と でのハングrt_sigsuspend()です。これはシグナルの明示的な待機であり、あるスレッドが別のスレッドの終了を待っている可能性が非常に高く、もちろんこれは決して起こりません。

この背景には、C と標準ライブラリ/libcが最新のスレッド化よりも古いため、標準ライブラリまたは API が再入可能でないか、スレッドセーフでないか、またはその両方である場合が多いことが挙げられます。ドラゴンが土地を歩き回っていた頃、プログラマーがそのような関数の代替バージョン ("_r" の接尾辞が付いた名前) を明示的に呼び出すか、代替ライブラリ (通常は "_r" 接尾辞が付いたもの) を使用して、コードが正しく動作することを確認することが一般的でした。 . pthreads はプログラミング インターフェイスをより良いものに変更しましたが、スレッド セーフには代償が伴うため (パフォーマンス、場合によってはかなりの量、およびコード サイズ)、ユーザーが要求しない限り有効になりません。

-pthread通常、少なくとも 2 つのことを使用すると、次のことが起こります。

  • _REENTRANTプリプロセッサ マクロとして定義されているため、コンパイル時の動作が変わる可能性があります
  • libpthreadにリンクされている ( と同等-lpthread)。これにより、実行時の動作が変更されます。

確かに重要なデバッグが必要ですが、おそらく起こったことは、バイナリが uClibc のスタブ pthread 関数と少数の実際の pthread 関数を混在させてしまったことです。これは、libpthread が明示的にロードされておらず、libsqlite によって参照される pthread シンボルのみがインポートされたためです。uClibc には (glibc と同様に) ダミーの pthread 関数が含まれています (実行nmlibc.soて確認します)。これらは「弱い」シンボルとして定義されます。実際の libpthread が明示的にロードされると、「強い」シンボルですべてのエントリ ポイントを引き継ぎます。(これらのスタブは、スレッド対応ライブラリーが非スレッド化プログラムを変更せずに処理できるようにするために存在します。)

バイナリを明示的にビルドすると、-pthreadこの不一致が解消され、問題が解決されます。


デバッグの場合:

コンパイルされたバイナリに対してnm -gand 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はこれ(glibc スタイル) をサポートしていないため、これlibnss_xxx.soは除外されたものの 1 つです...

別の可能性があります: PAM は同様のことを行い、互換性のないライブラリ ( または で使用される BerkeleyDB またはおそらく SQLite) をロードする可能性がありpam_userdbますpam-sqlite。ただし、uClibc も SQLite も PAM を使用しておらず、偶然にリンクされている可能性は低いです。)

が使用されているため、 ではdlopen()そのようなライブラリ (NSS または PAM) は表示されません。lddstrace -e trace=file

于 2013-08-27T12:51:40.403 に答える