GNU/Linux >> Linux の 問題 >  >> Linux

Linux でエクスポートされた関数の名前とアドレスを取得する

Y で行っていることをオペレーティング システム X で行うにはどうすればよいかという質問を目にすると、かなりイライラします。

ほとんどの場合、これは有用なアプローチではありません。各オペレーティング システム (ファミリ) は問題に対して独自のアプローチを持っている傾向があるため、X in Y で機能するものを適用しようとすることは、丸い穴に立方体を詰め込むようなものです.

注意:ここでのテキストは、見下すようなものではなく、厳しいものを意図しています。私の英語力は思ったほど良くありません。私の経験では、技術以外の制限を克服するには、実際のヘルプと既知の有効なソリューションへのポインターを組み合わせた厳しさが最も効果的であるように思われます。

Linux では、テスト環境はすべき のようなものを使用してください

LC_ALL=C LANG=C readelf -s FILE

FILE のすべてのシンボルを一覧表示するには . readelf binutils パッケージの一部であり、システムで新しいバイナリをビルドする場合にインストールされます。これにより、移植可能で堅牢なコードが得られます。 Linux には複数のハードウェア アーキテクチャが含まれていることを忘れないでください。これらには実際に違いがあります。

Linux でバイナリをビルドするには、通常、binutils で提供されているいくつかのツールを使用します。 binutils がライブラリを提供する場合、または binutils で使用されるコードに基づく ELF ライブラリが存在する場合は、人間のユーティリティの出力を解析するよりも、それを使用する方がはるかに優れています。ただし、そのようなライブラリはありません (binutils が内部で使用する libbfd ライブラリは ELF 固有ではありません)。 [URL=http://www.mr511.de/software/english.html]libelf[/URL] ライブラリは優れていますが、主に 1 人の作者による完全に別の作業です。その中のバグは binutils に報告されていますが、この 2 つは関係がないため非生産的です。簡単に言えば、binutils が行うのと同じ方法で特定のアーキテクチャ上の ELF ファイルを処理するという保証はありません。したがって、堅牢性と信頼性を確保するには、間違いなく binutils を使用することをお勧めします。

テスト アプリケーションがある場合は、/usr/lib/yourapp/list-test-functions などのスクリプトを使用する必要があります。 、テスト関連の関数を一覧表示するには:

#!/bin/bash
export LC_ALL=C LANG=C
for file in "[email protected]" ; do
    readelf -s "$file" | while read num value size type bind vix index name dummy ; do
        [ "$type" = "FUNC" ] || continue
        [ "$bind" = "GLOBAL" ] || continue
        [ "$num" = "$[$num]" ] || continue
        [ "$index" = "$[$index]" ] || continue
        case "$name" in
            test_*) printf '%s\n' "$name"
                    ;;
        esac
    done
done

このように、癖のあるアーキテクチャがある場合 (binutils の readelf 特に出力形式)、スクリプトを変更するだけで済みます。このような単純なスクリプトの変更は難しくなく、スクリプトが正しく機能することを簡単に確認できます。生の readelf を比較するだけです。 スクリプト出力への出力。誰でもできます。

パイプを構築するサブルーチン fork() 子プロセスであり、子プロセスでスクリプトを実行し、たとえばgetline() 名前のリストを読み取るための親プロセスは、非常にシンプルで非常に堅牢です。これは脆弱な場所でもあるため、その外部スクリプトを使用して、ここでの癖や問題を簡単に修正できるようにしました (これらの癖をカバーするためにカスタマイズ/拡張可能で、デバッグも簡単です)。binutils 自体がバグ (出力フォーマットのバグ以外) がある場合、ビルドされたバイナリはほぼ確実に同じバグを示します。

Microsoft 指向の人は、おそらくこのようなモジュール方式の利点を理解するのに苦労するでしょう。 (これは Microsoft に固有のものではなく、単一のベンダーが管理するエコシステムに固有のものであり、ベンダーが推進するアプローチは包括的なフレームワーク を介して行われます。 、およびクリーンだが非常に限定されたインターフェースを備えたブラックボックス。フレームワークの制限、またはベンダーが強制する壁に囲まれた庭、または刑務所の庭だと思います。良さそうですが、抜け出すのが大変です。私が説明しようとしているモジュラー アプローチの説明と歴史については、たとえば Wikipedia の Unix 哲学の記事を参照してください。)

以下は、あなたのアプローチが実際に Linux でも可能であることを示しています。この作業は、代わりに標準ツールを使用して行うことを意図しています。一般的に、これは適切なアプローチではありません。

インターフェイス、symbols.h 、見つかったシンボルごとに呼び出されるコールバック関数を使用して実装するのが最も簡単です:

#ifndef  SYMBOLS_H
#ifndef _GNU_SOURCE
#error You must define _GNU_SOURCE!
#endif
#define  SYMBOLS_H
#include <stdlib.h>

typedef enum {
    LOCAL_SYMBOL  = 1,
    GLOBAL_SYMBOL = 2,
    WEAK_SYMBOL   = 3,
} symbol_bind;

typedef enum {
    FUNC_SYMBOL   = 4,
    OBJECT_SYMBOL = 5,
    COMMON_SYMBOL = 6,
    THREAD_SYMBOL = 7,
} symbol_type;

int symbols(int (*callback)(const char *libpath, const char *libname, const char *objname,
                            const void *addr, const size_t size,
                            const symbol_bind binding, const symbol_type type,
                            void *custom),
            void *custom);

#endif /* SYMBOLS_H */

ELF シンボル バインドと型マクロはワード サイズに固有であるため、煩わしさを避けるために、上記の列挙型を宣言しました。興味のない型をいくつか省略しました (STT_NOTYPESTT_SECTIONSTT_FILE )、ただし。

実装、symbols.c :

#define _GNU_SOURCE
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <fnmatch.h>
#include <dlfcn.h>
#include <link.h>
#include <errno.h>
#include "symbols.h"

#define UINTS_PER_WORD (__WORDSIZE / (CHAR_BIT * sizeof (unsigned int)))

static ElfW(Word) gnu_hashtab_symbol_count(const unsigned int *const table)
{
    const unsigned int *const bucket = table + 4 + table[2] * (unsigned int)(UINTS_PER_WORD);
    unsigned int              b = table[0];
    unsigned int              max = 0U;

    while (b-->0U)
        if (bucket[b] > max)
            max = bucket[b];

    return (ElfW(Word))max;
}

static symbol_bind elf_symbol_binding(const unsigned char st_info)
{
#if __WORDSIZE == 32
    switch (ELF32_ST_BIND(st_info)) {
#elif __WORDSIZE == 64
    switch (ELF64_ST_BIND(st_info)) {
#else
    switch (ELF_ST_BIND(st_info)) {
#endif
    case STB_LOCAL:  return LOCAL_SYMBOL;
    case STB_GLOBAL: return GLOBAL_SYMBOL;
    case STB_WEAK:   return WEAK_SYMBOL;
    default:         return 0;
    }
}

static symbol_type elf_symbol_type(const unsigned char st_info)
{
#if __WORDSIZE == 32
    switch (ELF32_ST_TYPE(st_info)) {
#elif __WORDSIZE == 64
    switch (ELF64_ST_TYPE(st_info)) {
#else
    switch (ELF_ST_TYPE(st_info)) {
#endif
    case STT_OBJECT: return OBJECT_SYMBOL;
    case STT_FUNC:   return FUNC_SYMBOL;
    case STT_COMMON: return COMMON_SYMBOL;
    case STT_TLS:    return THREAD_SYMBOL;
    default:         return 0;
    }
}

static void *dynamic_pointer(const ElfW(Addr) addr,
                             const ElfW(Addr) base, const ElfW(Phdr) *const header, const ElfW(Half) headers)
{
    if (addr) {
        ElfW(Half) h;

        for (h = 0; h < headers; h++)
            if (header[h].p_type == PT_LOAD)
                if (addr >= base + header[h].p_vaddr &&
                    addr <  base + header[h].p_vaddr + header[h].p_memsz)
                    return (void *)addr;
    }

    return NULL;
}

struct phdr_iterator_data {
    int  (*callback)(const char *libpath, const char *libname,
                     const char *objname, const void *addr, const size_t size,
                     const symbol_bind binding, const symbol_type type,
                     void *custom);
    void  *custom;
};

static int iterate_phdr(struct dl_phdr_info *info, size_t size, void *dataref)
{
    struct phdr_iterator_data *const data = dataref;
    const ElfW(Addr)                 base = info->dlpi_addr;
    const ElfW(Phdr) *const          header = info->dlpi_phdr;
    const ElfW(Half)                 headers = info->dlpi_phnum;
    const char                      *libpath, *libname;
    ElfW(Half)                       h;

    if (!data->callback)
        return 0;

    if (info->dlpi_name && info->dlpi_name[0])
        libpath = info->dlpi_name;
    else
        libpath = "";

    libname = strrchr(libpath, '/');
    if (libname && libname[0] == '/' && libname[1])
        libname++;
    else
        libname = libpath;

    for (h = 0; h < headers; h++)
        if (header[h].p_type == PT_DYNAMIC) {
            const ElfW(Dyn)  *entry = (const ElfW(Dyn) *)(base + header[h].p_vaddr);
            const ElfW(Word) *hashtab;
            const ElfW(Sym)  *symtab = NULL;
            const char       *strtab = NULL;
            ElfW(Word)        symbol_count = 0;

            for (; entry->d_tag != DT_NULL; entry++)
                switch (entry->d_tag) {
                case DT_HASH:
                    hashtab = dynamic_pointer(entry->d_un.d_ptr, base, header, headers);
                    if (hashtab)
                        symbol_count = hashtab[1];
                    break;
                case DT_GNU_HASH:
                    hashtab = dynamic_pointer(entry->d_un.d_ptr, base, header, headers);
                    if (hashtab) {
                        ElfW(Word) count = gnu_hashtab_symbol_count(hashtab);
                        if (count > symbol_count)
                            symbol_count = count;
                    }
                    break;
                case DT_STRTAB:
                    strtab = dynamic_pointer(entry->d_un.d_ptr, base, header, headers);
                    break;
                case DT_SYMTAB:
                    symtab = dynamic_pointer(entry->d_un.d_ptr, base, header, headers);
                    break;
                }

            if (symtab && strtab && symbol_count > 0) {
                ElfW(Word)  s;

                for (s = 0; s < symbol_count; s++) {
                    const char *name;
                    void *const ptr = dynamic_pointer(base + symtab[s].st_value, base, header, headers);
                    symbol_bind bind;
                    symbol_type type;
                    int         result;

                    if (!ptr)
                        continue;

                    type = elf_symbol_type(symtab[s].st_info);
                    bind = elf_symbol_binding(symtab[s].st_info);
                    if (symtab[s].st_name)
                        name = strtab + symtab[s].st_name;
                    else
                        name = "";

                    result = data->callback(libpath, libname, name, ptr, symtab[s].st_size, bind, type, data->custom);
                    if (result)
                        return result;
                }
            }
        }

    return 0;
}

int symbols(int (*callback)(const char *libpath, const char *libname, const char *objname,
                            const void *addr, const size_t size,
                            const symbol_bind binding, const symbol_type type,
                            void *custom),
            void *custom)
{
    struct phdr_iterator_data data;

    if (!callback)
        return errno = EINVAL;

    data.callback = callback;
    data.custom = custom;

    return errno = dl_iterate_phdr(iterate_phdr, &data);
}

上記をコンパイルするときは、忘れずに dl にリンクしてください。

gnu_hashtab_symbol_count() が見つかるかもしれません 上記の興味深い機能。テーブルの形式は、私が見つけることができるどこにも十分に文書化されていません。これは、i386 および x86-64 アーキテクチャの両方で動作することがテストされていますが、製品コードで依存する前に、GNU ソースに対して吟味する必要があります。繰り返しになりますが、より良いオプションは、ヘルパー スクリプトを介してこれらのツールを直接使用することです。これらのツールは、任意の開発マシンにインストールされるためです。

技術的には、DT_GNU_HASH テーブルは最初の動的シンボルを示し、任意のハッシュ バケットの最大のインデックスは最後の動的シンボルを示しますが、DT_SYMTAB のエントリが シンボル テーブルは常に 0 から始まります (実際には、0 エントリは「なし」です)。上限のみを考慮します。

ライブラリと関数の名前を一致させるには、strncmp() を使用することをお勧めします ライブラリの接頭辞一致 (ライブラリ名の先頭から最初の . までの一致) )。もちろん、 fnmatch() を使用できます glob パターンを好む場合、または regcomp()+regexec() 正規表現を使用する場合 (これらは GNU C ライブラリに組み込まれているため、外部ライブラリは必要ありません)。

これがプログラムの例です example.c 、すべてのシンボルを出力するだけです:

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <errno.h>
#include "symbols.h"

static int my_func(const char *libpath, const char *libname, const char *objname,
                   const void *addr, const size_t size,
                   const symbol_bind binding, const symbol_type type,
                   void *custom __attribute__((unused)))
{
    printf("%s (%s):", libpath, libname);

    if (*objname)
        printf(" %s:", objname);
    else
        printf(" unnamed");

    if (size > 0)
        printf(" %zu-byte", size);

    if (binding == LOCAL_SYMBOL)
        printf(" local");
    else
    if (binding == GLOBAL_SYMBOL)
        printf(" global");
    else
    if (binding == WEAK_SYMBOL)
        printf(" weak");

    if (type == FUNC_SYMBOL)
        printf(" function");
    else
    if (type == OBJECT_SYMBOL || type == COMMON_SYMBOL)
        printf(" variable");
    else
    if (type == THREAD_SYMBOL)
        printf(" thread-local variable");

    printf(" at %p\n", addr);
    fflush(stdout);

    return 0;
}

int main(int argc, char *argv[])
{
    int  arg;

    for (arg = 1; arg < argc; arg++) {
        void *handle = dlopen(argv[arg], RTLD_NOW);
        if (!handle) {
            fprintf(stderr, "%s: %s.\n", argv[arg], dlerror());
            return EXIT_FAILURE;
        }

        fprintf(stderr, "%s: Loaded.\n", argv[arg]);
    }

    fflush(stderr);

    if (symbols(my_func, NULL))
        return EXIT_FAILURE;

    return EXIT_SUCCESS;
}

上記をコンパイルして実行するには、例を使用します

gcc -Wall -O2 -c symbols.c
gcc -Wall -O2 -c example.c
gcc -Wall -O2 example.o symbols.o -ldl -o example
./example | less

プログラム自体のシンボルを表示するには、-rdynamic を使用します すべてのシンボルをダイナミック シンボル テーブルに追加するためのリンク時のフラグ:

gcc -Wall -O2 -c symbols.c
gcc -Wall -O2 -c example.c
gcc -Wall -O2 -rdynamic example.o symbols.o -ldl -o example
./example | less

私のシステムでは、後者が出力されます

 (): stdout: 8-byte global variable at 0x602080
 (): _edata: global at 0x602078
 (): __data_start: global at 0x602068
 (): data_start: weak at 0x602068
 (): symbols: 70-byte global function at 0x401080
 (): _IO_stdin_used: 4-byte global variable at 0x401150
 (): __libc_csu_init: 101-byte global function at 0x4010d0
 (): _start: global function at 0x400a57
 (): __bss_start: global at 0x602078
 (): main: 167-byte global function at 0x4009b0
 (): _init: global function at 0x4008d8
 (): stderr: 8-byte global variable at 0x602088
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): unnamed local at 0x7fc652097000
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): unnamed local at 0x7fc652097da0
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): __asprintf: global function at 0x7fc652097000
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): free: global function at 0x7fc652097000
...
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): dlvsym: 118-byte weak function at 0x7fc6520981f0
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): unnamed local at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): unnamed local at 0x7fc651cf14a0
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): unnamed local at 0x7fc65208c740
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _rtld_global: global variable at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): __libc_enable_secure: global variable at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): __tls_get_addr: global function at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _rtld_global_ro: global variable at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _dl_find_dso_for_object: global function at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _dl_starting_up: weak at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _dl_argv: global variable at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): putwchar: 292-byte global function at 0x7fc651d4a210
...
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): vwarn: 224-byte global function at 0x7fc651dc8ef0
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): wcpcpy: 39-byte weak function at 0x7fc651d75900
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): unnamed local at 0x7fc65229b000
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): unnamed local at 0x7fc65229bae0
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): _dl_get_tls_static_info: 21-byte global function at 0x7fc6522adaa0
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): GLIBC_PRIVATE: global variable at 0x7fc65229b000
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): GLIBC_2.3: global variable at 0x7fc65229b000
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): GLIBC_2.4: global variable at 0x7fc65229b000
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): free: 42-byte weak function at 0x7fc6522b2c40
...
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): malloc: 13-byte weak function at 0x7fc6522b2bf0
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): _dl_allocate_tls_init: 557-byte global function at 0x7fc6522adc00
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): _rtld_global_ro: 304-byte global variable at 0x7fc6524bdcc0
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): __libc_enable_secure: 4-byte global variable at 0x7fc6524bde68
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): _dl_rtld_di_serinfo: 1620-byte global function at 0x7fc6522a4710

... を使用しました 多くの行を削除した場所をマークします。

質問がありますか?


共有ライブラリからエクスポートされたシンボルのリストを取得するには (.so ) Linux では、簡単な方法と少し難しい方法の 2 つの方法があります。

簡単なのは、すでに利用可能なコンソール ツールを使用することです:objdump (GNU binutils に含まれています):

$ objdump -T /usr/lib/libid3tag.so.0
00009c15 g    DF .text  0000012e  Base        id3_tag_findframe
00003fac g    DF .text  00000053  Base        id3_ucs4_utf16duplicate
00008288 g    DF .text  000001f2  Base        id3_frame_new
00007b73 g    DF .text  000003c5  Base        id3_compat_fixup
...

少し難しい方法は、 libelf を使用することです シンボルを自分でリストする C/C++ プログラムを作成します。 elfutils を見てください このパッケージも libelf ソースからビルドされています。 eu-readelf というプログラムがあります (readelf の elfutils バージョン。binutils readelf と混同しないでください)。 eu-readelf -s $LIB libelf を使用してエクスポートされたシンボルをリストしているので、それを出発点として使用できるはずです。


Linux
  1. Linuxでシステムとハードウェアの情報を取得する方法

  2. Linuxですべてのファイルとディレクトリ名を小文字に変更

  3. cpを取得する理由:Linuxでのディレクトリエラーの省略と解決方法

  1. Linux ディストリビューション名とバージョンを取得するには?

  2. Windows および Linux のディレクトリ名で禁止されている文字は何ですか?

  3. プログラムで共有ライブラリの関数名を取得する

  1. Linuxシェルスクリプトで関数を定義して使用する方法

  2. Windows に Linux のホスト名を認識させたり、その逆を行うにはどうすればよいですか?

  3. Linux での ATA および SATA ディスクの名前