GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Rufen Sie Namen und Adressen von exportierten Funktionen in Linux ab

Ich ärgere mich ziemlich, wenn ich Fragen sehe, die fragen, wie man etwas in Betriebssystem X macht, was man in Y macht.

In den meisten Fällen ist dies kein sinnvoller Ansatz, da jedes Betriebssystem (Familie) tendenziell einen eigenen Ansatz für Probleme hat. Der Versuch, etwas anzuwenden, das in X in Y funktioniert, ist also, als würde man einen Würfel in ein rundes Loch stecken. P>

Bitte beachten Sie:Der Text hier ist hart gemeint, nicht herablassend; Meine Englischkenntnisse sind nicht so gut, wie ich es gerne hätte. Härte in Kombination mit tatsächlicher Hilfe und Hinweisen auf bekannte funktionierende Lösungen scheint meiner Erfahrung nach am besten zu funktionieren, um nichttechnische Einschränkungen zu überwinden.

Unter Linux sollte eine Testumgebung vorhanden sein Verwenden Sie so etwas wie

LC_ALL=C LANG=C readelf -s FILE

um alle Symbole in FILE aufzulisten . readelf ist Teil des Pakets binutils und wird installiert, wenn Sie beabsichtigen, neue Binärdateien auf dem System zu erstellen. Dies führt zu portablem, robustem Code. Vergessen Sie nicht, dass Linux mehrere Hardwarearchitekturen umfasst, die echte Unterschiede aufweisen.

Um Binärdateien unter Linux zu erstellen, verwenden Sie normalerweise einige der in binutils bereitgestellten Tools. Wenn binutils eine Bibliothek bereitstellt oder es eine ELF-Bibliothek gibt, die auf dem in binutils verwendeten Code basiert, wäre es viel besser, diese zu verwenden, als die Ausgabe der menschlichen Dienstprogramme zu parsen. Es gibt jedoch keine solche Bibliothek (die libbfd-Bibliothek, die binutils intern verwendet, ist nicht ELF-spezifisch). Die [URL=http://www.mr511.de/software/english.html]libelf[/URL]-Bibliothek ist gut, aber es ist eine völlig separate Arbeit von hauptsächlich einem einzigen Autor. Fehler darin wurden an binutils gemeldet, was unproduktiv ist, da die beiden nicht verwandt sind. Einfach ausgedrückt, es gibt keine Garantie dafür, dass es die ELF-Dateien auf einer bestimmten Architektur genauso behandelt wie binutils. Aus Gründen der Robustheit und Zuverlässigkeit sollten Sie daher auf jeden Fall binutils verwenden.

Wenn Sie eine Testanwendung haben, sollte sie ein Skript verwenden, z. B. /usr/lib/yourapp/list-test-functions , um die testbezogenen Funktionen aufzulisten:

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

Auf diese Weise, wenn es eine Architektur gibt, die Macken hat (in der readelf Ausgabeformat), müssen Sie nur das Skript ändern. Das Ändern eines so einfachen Skripts ist nicht schwierig, und es ist einfach zu überprüfen, ob das Skript korrekt funktioniert – vergleichen Sie einfach den rohen readelf Ausgabe an die Skriptausgabe; das kann jeder.

Eine Unterroutine, die eine Pipe konstruiert, fork() ist ein untergeordneter Prozess, führt das Skript im untergeordneten Prozess aus und verwendet z. getline() im übergeordneten Prozess zum Lesen der Namensliste, ist recht einfach und äußerst robust. Da dies auch die einzige Schwachstelle ist, haben wir es sehr einfach gemacht, alle Macken oder Probleme hier zu beheben, indem wir dieses externe Skript verwenden (das anpassbar/erweiterbar ist, um diese Macken abzudecken, und das leicht zu debuggen ist). Denken Sie daran, ob binutils selbst Fehler hat (außer Ausgabeformatierungsfehlern), werden alle erstellten Binärdateien mit ziemlicher Sicherheit dieselben Fehler aufweisen.

Als Microsoft-orientierte Person werden Sie wahrscheinlich Schwierigkeiten haben, die Vorteile eines solchen modularen Ansatzes zu erfassen. (Es ist nicht spezifisch für Microsoft, sondern spezifisch für ein von einem einzelnen Anbieter kontrolliertes Ökosystem, in dem der vom Anbieter vorangetriebene Ansatz über übergreifende Frameworks erfolgt , und Black Boxes mit sauberen, aber sehr begrenzten Schnittstellen. Ich denke, es ist die Rahmenbeschränkung oder der vom Verkäufer erzwungene ummauerte Garten oder der Gefängnisgarten. Sieht gut aus, aber das Aussteigen ist schwierig. Für eine Beschreibung und Geschichte des modularen Ansatzes, den ich zu beschreiben versuche, siehe zum Beispiel den Unix-Philosophie-Artikel bei Wikipedia.)

Das Folgende zeigt, dass Ihr Ansatz tatsächlich auch unter Linux möglich ist -- wenn auch klobig und zerbrechlich; Dieses Zeug soll stattdessen mit den Standardwerkzeugen erledigt werden. Es ist einfach nicht der richtige Ansatz im Allgemeinen.

Die Schnittstelle, symbols.h , lässt sich am einfachsten mit einer Callback-Funktion implementieren, die für jedes gefundene Symbol aufgerufen wird:

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

Die ELF-Symbolbindung und die Typmakros sind wortgrößenspezifisch, also habe ich, um den Ärger zu vermeiden, die Enum-Typen oben deklariert. Ich habe einige uninteressante Typen weggelassen (STT_NOTYPE , STT_SECTION , STT_FILE ), jedoch.

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

Denken Sie beim Kompilieren des Obigen daran, gegen dl zu verlinken Bibliothek.

Möglicherweise finden Sie den gnu_hashtab_symbol_count() Funktion oben interessant; Das Format der Tabelle ist nirgendwo gut dokumentiert, was ich finden kann. Dies wurde getestet, um sowohl auf i386- als auch auf x86-64-Architekturen zu funktionieren, aber es sollte anhand der GNU-Quellen überprüft werden, bevor man sich darauf im Produktionscode verlässt. Auch hier ist es die bessere Option, diese Tools einfach direkt über ein Hilfsskript zu verwenden, da sie auf jedem Entwicklungscomputer installiert werden.

Technisch gesehen ein DT_GNU_HASH Tabelle sagt uns das erste dynamische Symbol, und der höchste Index in jedem Hash-Bucket sagt uns das letzte dynamische Symbol, aber seit den Einträgen in DT_SYMTAB Symboltabellen beginnen immer bei 0 (eigentlich ist der 0-Eintrag "none"), ich berücksichtige nur die Obergrenze.

Um Bibliotheks- und Funktionsnamen abzugleichen, empfehle ich die Verwendung von strncmp() für eine Präfixübereinstimmung für Bibliotheken (Übereinstimmung am Anfang des Bibliotheksnamens bis zum ersten . ). Natürlich können Sie fnmatch() verwenden wenn Sie Kugelmuster bevorzugen, oder regcomp()+regexec() wenn Sie reguläre Ausdrücke bevorzugen (sie sind in die GNU C-Bibliothek integriert, es werden keine externen Bibliotheken benötigt).

Hier ist ein Beispielprogramm, example.c , das nur alle Symbole ausgibt:

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

Um obiges zu kompilieren und auszuführen, verwenden Sie zum Beispiel

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

Um die Symbole im Programm selbst zu sehen, verwenden Sie den -rdynamic Flag zur Verbindungszeit, um alle Symbole zur dynamischen Symboltabelle hinzuzufügen:

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

Auf meinem System wird letzteres ausgedruckt

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

Ich habe ... verwendet um zu markieren, wo ich viele Zeilen entfernt habe.

Fragen?


Um eine Liste der exportierten Symbole aus einer gemeinsam genutzten Bibliothek (eine .so ) unter Linux gibt es zwei Möglichkeiten:die einfache und eine etwas schwierigere.

Am einfachsten ist es, die bereits verfügbaren Konsolen-Tools zu verwenden:objdump (in GNU-Binutils enthalten):

$ 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
...

Der etwas schwierigere Weg ist die Verwendung von libelf und schreiben Sie ein C/C++-Programm, um die Symbole selbst aufzulisten. Schauen Sie sich den elfutils an -Paket, das ebenfalls aus der libelf-Quelle erstellt wird. Es gibt ein Programm namens eu-readelf (die elfutils-Version von readelf, nicht zu verwechseln mit binutils readelf). eu-readelf -s $LIB listet exportierte Symbole mit libelf auf, also sollten Sie in der Lage sein, das als Ausgangspunkt zu verwenden.


Linux
  1. Ich habe Strg-Alt-F6 unter Linux verwendet und kann meinen Bildschirm nicht zurückbekommen

  2. Wie erhalte ich den Namen und die Version der Linux-Distribution?

  3. Welche Zeichen sind in Windows- und Linux-Verzeichnisnamen verboten?

  4. Erhalten Sie IPv6-Adressen in Linux mit ioctl

  5. Rufen Sie Funktionsnamen in einer gemeinsam genutzten Bibliothek programmgesteuert ab

Linux Mint 19.3 „Tricia“ veröffentlicht:Hier ist, was neu ist und wie man es bekommt

Linux Mint 19.2 „Tina“ veröffentlicht:Hier ist, was neu ist und wie man es bekommt

Linux-Kernel und seine Funktionen

Linux-Prozesse – Speicherlayout, Exit und _exit C-Funktionen

Was sind Linux-Systemaufrufe und Bibliotheksfunktionen?

Die 10 besten Linux-VPN-Clients und -Dienste für Ihren Schutz