GNU/Linux >> Znalost Linux >  >> Linux

Získejte názvy a adresy exportovaných funkcí v linuxu

Docela mě štve, když vidím otázky, jak udělat něco v operačním systému X, co děláte v Y.

Ve většině případů to není užitečný přístup, protože každý operační systém (rodina) má tendenci mít svůj vlastní přístup k problémům, takže pokusit se aplikovat něco, co funguje v X v Y, je jako nacpat krychli do kulaté díry.

Vezměte prosím na vědomí:text zde je zamýšlen jako drsný, nikoli blahosklonný; moje znalost angličtiny není tak dobrá, jak bych si přál. Podle mých zkušeností se zdá, že drsnost v kombinaci se skutečnou nápovědou a odkazy na známá fungující řešení nejlépe fungují při překonávání netechnických omezení.

V systému Linux by mělo testovací prostředí použijte něco jako

LC_ALL=C LANG=C readelf -s FILE

vypíše všechny symboly v FILE . readelf je součástí balíčku binutils a instaluje se, pokud máte v úmyslu v systému vytvářet nové binární soubory. To vede k přenosnému robustnímu kódu. Nezapomeňte, že Linux zahrnuje několik hardwarových architektur, které se skutečně liší.

K sestavení binárních souborů v Linuxu obvykle používáte některé nástroje poskytované v binutils. Pokud by binutils poskytoval knihovnu nebo existovala knihovna ELF založená na kódu používaném v binutilech, bylo by mnohem lepší použít to, než analyzovat výstup lidských utilit. Žádná taková knihovna však neexistuje (knihovna libbfd, kterou binutils používá interně, není specifická pro ELF). Knihovna [URL=http://www.mr511.de/software/english.html]libelf[/URL] je dobrá, ale je to zcela samostatné dílo převážně jednoho autora. Chyby v něm byly hlášeny binutilům, což je neproduktivní, protože tyto dva spolu nesouvisí. Jednoduše řečeno, neexistují žádné záruky, že zachází se soubory ELF na dané architektuře stejně jako binutils. Z důvodu robustnosti a spolehlivosti budete určitě chtít používat binutils.

Pokud máte testovací aplikaci, měla by používat skript, řekněme /usr/lib/yourapp/list-test-functions , zobrazí se seznam funkcí souvisejících s testem:

#!/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

Tímto způsobem, pokud existuje architektura, která má zvláštnosti (v readelf binutils výstupní formát), stačí pouze upravit skript. Úprava takto jednoduchého skriptu není obtížná a je snadné ověřit, zda skript funguje správně – stačí porovnat nezpracovaný readelf výstup na výstup skriptu; to může udělat každý.

Podprogram, který vytváří potrubí, fork() je podřízený proces, provádí skript v podřízeném procesu a používá např. getline() v nadřazeném procesu číst seznam jmen, je poměrně jednoduchý a extrémně robustní. Vzhledem k tomu, že toto je také jediné křehké místo, velmi snadno jsme zde opravili jakékoli výstřednosti nebo problémy pomocí externího skriptu (který je přizpůsobitelný/rozšiřitelný tak, aby pokryl tyto výstřednosti, a lze jej snadno ladit). Pamatujte, že pokud se binutils sám obsahuje chyby (jiné než chyby formátování výstupu), všechny vytvořené binární soubory téměř jistě také vykazují stejné chyby.

Jako člověk orientovaný na Microsoft budete mít pravděpodobně problémy s pochopením výhod takového modulárního přístupu. (Není to specifické pro Microsoft, ale specifické pro ekosystém kontrolovaný jedním dodavatelem, kde je přístup prosazovaný dodavatelem prostřednictvím překlenujících rámců a černé skříňky s čistým, ale velmi omezeným rozhraním. Myslím, že je to rámcové omezení, nebo prodejcem vynucená zděná zahrada nebo vězeňská zahrada. Vypadá to dobře, ale dostat se ven je těžké. Popis a historii modulárního přístupu, který se snažím popsat, naleznete například v článku o filozofii Unixu na Wikipedii.)

Následující ukazuje, že váš přístup je skutečně možný i v Linuxu -- i když neohrabaný a křehký; tyto věci jsou určeny k provádění pomocí standardních nástrojů. Obecně to prostě není správný přístup.

Rozhraní, symbols.h , je nejjednodušší implementovat pomocí funkce zpětného volání, která se volá pro každý nalezený symbol:

#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 */

Vazba symbolu ELF a typová makra jsou specifická pro velikost slova, takže abych se vyhnul potížím, uvedl jsem výše uvedené typy výčtu. Vynechal jsem některé nezajímavé typy (STT_NOTYPE , STT_SECTION , STT_FILE ), nicméně.

Implementace, 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);
}

Při kompilaci výše uvedeného nezapomeňte odkazovat na dl knihovna.

Můžete najít gnu_hashtab_symbol_count() funkce výše zajímavé; formát tabulky není nikde dobře zdokumentován. Toto je testováno, aby fungovalo na architekturách i386 i x86-64, ale než se na to spolehnete v produkčním kódu, mělo by to být prověřeno podle zdrojů GNU. Opět platí, že lepší možností je použít tyto nástroje přímo prostřednictvím pomocného skriptu, protože se nainstalují na jakýkoli vývojový stroj.

Technicky vzato, DT_GNU_HASH tabulka nám říká první dynamický symbol a nejvyšší index v libovolném segmentu hash nám říká poslední dynamický symbol, ale protože položky v DT_SYMTAB tabulka symbolů vždy začíná na 0 (ve skutečnosti je položka 0 "žádná"), beru v úvahu pouze horní hranici.

Pro shodu názvů knihoven a funkcí doporučuji použít strncmp() pro shodu předpony pro knihovny (shoda na začátku názvu knihovny až do prvních . ). Samozřejmě můžete použít fnmatch() pokud dáváte přednost vzorům glob nebo regcomp()+regexec() pokud dáváte přednost regulárním výrazům (jsou vestavěny do knihovny GNU C, nejsou potřeba žádné externí knihovny).

Zde je příklad programu example.c , který pouze vytiskne všechny symboly:

#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;
}

Pro kompilaci a spuštění výše uvedeného použijte například

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

Chcete-li zobrazit symboly v samotném programu, použijte -rdynamic flag v čase odkazu pro přidání všech symbolů do tabulky dynamických symbolů:

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

V mém systému se poslední vytiskne

 (): 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

Použil jsem ... pro označení místa, kde jsem odstranil spoustu řádků.

Otázky?


Chcete-li získat seznam exportovaných symbolů ze sdílené knihovny (a .so ) pod Linuxem existují dva způsoby:snadný a o něco těžší.

Nejjednodušší je použít již dostupné nástroje konzoly:objdump (zahrnuto v 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
...

O něco těžší způsob je použít libelf a napište si program v C/C++, abyste si vypsali symboly sami. Podívejte se na elfutils balíček, který je také vytvořen ze zdroje urážky na cti. Existuje program nazvaný eu-readelf (elfutils verze readelf, nezaměňovat s binutils readelf). eu-readelf -s $LIB uvádí exportované symboly pomocí libelf, takže byste to měli být schopni použít jako výchozí bod.


Linux
  1. Jak získat informace o systému a hardwaru v linuxu

  2. Přejmenujte všechny soubory a názvy adresářů na malá písmena v Linuxu

  3. Získejte seznam názvů funkcí ve skriptu shellu

  1. Jak definovat a používat funkce v Linux Shell Script

  2. Získejte IPv6 adresy v linuxu pomocí ioctl

  3. Získejte názvy funkcí ve sdílené knihovně programově

  1. Použil jsem Ctrl-Alt-F6 v Linuxu a nemohu obnovit obrazovku

  2. Jak získat název a verzi distribuce Linuxu?

  3. Jaké znaky jsou v názvech adresářů Windows a Linux zakázány?