GNU/Linux >> Tutoriels Linux >  >> Linux

Obtenir les noms et adresses des fonctions exportées sous Linux

Je suis assez ennuyé quand je vois des questions demandant comment faire quelque chose dans le système d'exploitation X que vous faites dans Y.

Dans la plupart des cas, ce n'est pas une approche utile, car chaque système d'exploitation (famille) a tendance à avoir sa propre approche des problèmes, donc essayer d'appliquer quelque chose qui fonctionne dans X dans Y revient à enfoncer un cube dans un trou rond.

Veuillez noter :le texte ici se veut dur et non condescendant ; ma maîtrise de la langue anglaise n'est pas aussi bonne que je le souhaiterais. D'après mon expérience, la dureté combinée à une aide réelle et à des pointeurs vers des solutions de travail connues semble être la meilleure solution pour surmonter les limitations non techniques.

Sous Linux, un environnement de test devrait utilisez quelque chose comme

LC_ALL=C LANG=C readelf -s FILE

pour lister tous les symboles dans FILE . readelf fait partie du package binutils et est installé si vous avez l'intention de créer de nouveaux fichiers binaires sur le système. Cela conduit à un code portable et robuste. N'oubliez pas que Linux englobe plusieurs architectures matérielles qui présentent de réelles différences.

Pour construire des binaires sous Linux, vous utilisez normalement certains des outils fournis dans binutils. Si binutils fournissait une bibliothèque, ou s'il existait une bibliothèque ELF basée sur le code utilisé dans binutils, il serait bien préférable de l'utiliser plutôt que d'analyser la sortie des utilitaires humains. Cependant, il n'existe pas de bibliothèque de ce type (la bibliothèque libbfd que binutils utilise en interne n'est pas spécifique à ELF). La bibliothèque [URL=http://www.mr511.de/software/english.html]libelf[/URL] est bonne, mais c'est un travail complètement séparé réalisé principalement par un seul auteur. Des bogues ont été signalés à binutils, ce qui est improductif, car les deux ne sont pas liés. En termes simples, il n'y a aucune garantie qu'il gère les fichiers ELF sur une architecture donnée de la même manière que binutils. Par conséquent, pour la robustesse et la fiabilité, vous voudrez certainement utiliser binutils.

Si vous avez une application de test, elle doit utiliser un script, par exemple /usr/lib/yourapp/list-test-functions , pour lister les fonctions liées au test :

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

De cette façon, s'il y a une architecture qui a des bizarreries (dans le readelf de binutils format de sortie en particulier), il vous suffit de modifier le script. Modifier un script aussi simple n'est pas difficile, et il est facile de vérifier que le script fonctionne correctement -- il suffit de comparer le readelf brut sortie vers la sortie du script ; n'importe qui peut le faire.

Un sous-programme qui construit un tube, fork() s un processus enfant, exécute le script dans le processus enfant et utilise par ex. getline() dans le processus parent pour lire la liste des noms, est assez simple et extrêmement robuste. Puisqu'il s'agit également du seul point fragile, nous avons rendu très facile la résolution de toutes les bizarreries ou problèmes ici en utilisant ce script externe (qui est personnalisable/extensible pour couvrir ces bizarreries et facile à déboguer). Rappelez-vous, si binutils lui-même a des bogues (autres que des bogues de formatage de sortie), tous les binaires construits présenteront presque certainement ces mêmes bogues également.

Étant une personne orientée Microsoft, vous aurez probablement du mal à saisir les avantages d'une telle approche modulaire. (Ce n'est pas spécifique à Microsoft, mais spécifique à un écosystème contrôlé par un seul fournisseur où l'approche poussée par le fournisseur se fait via des frameworks globaux , et des boîtes noires avec des interfaces propres mais très limitées. Je pense que c'est la limitation du cadre, ou le jardin clos imposé par le vendeur, ou le jardin de la prison. Ça a l'air bien, mais sortir est difficile. Pour une description et un historique de l'approche modulaire que j'essaie de décrire, voir par exemple l'article sur la philosophie Unix sur Wikipedia.)

Ce qui suit montre que votre approche est également possible sous Linux - bien que maladroite et fragile; ce truc est destiné à être fait en utilisant les outils standard à la place. Ce n'est tout simplement pas la bonne approche en général.

L'interface, symbols.h , est plus facile à implémenter en utilisant une fonction de rappel qui est appelée pour chaque symbole trouvé :

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

La liaison de symbole ELF et les macros de type sont spécifiques à la taille des mots, donc pour éviter les tracas, j'ai déclaré les types enum ci-dessus. J'ai omis certains types inintéressants (STT_NOTYPE , STT_SECTION , STT_FILE ), cependant.

L'implémentation, 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);
}

Lors de la compilation de ce qui précède, n'oubliez pas de créer un lien avec le dl bibliothèque.

Vous pouvez trouver le gnu_hashtab_symbol_count() fonction ci-dessus intéressante; le format du tableau n'est pas bien documenté partout où je peux trouver. Ceci est testé pour fonctionner à la fois sur les architectures i386 et x86-64, mais il doit être vérifié par rapport aux sources GNU avant de s'y fier dans le code de production. Encore une fois, la meilleure option consiste à utiliser ces outils directement via un script d'assistance, car ils seront installés sur n'importe quelle machine de développement.

Techniquement, un DT_GNU_HASH table nous indique le premier symbole dynamique, et l'index le plus élevé dans n'importe quel seau de hachage nous indique le dernier symbole dynamique, mais depuis les entrées dans le DT_SYMTAB la table des symboles commence toujours à 0 (en fait, l'entrée 0 est "aucun"), je ne considère que la limite supérieure.

Pour faire correspondre les noms de bibliothèque et de fonction, je recommande d'utiliser strncmp() pour une correspondance de préfixe pour les bibliothèques (correspondance au début du nom de la bibliothèque, jusqu'au premier . ). Bien sûr, vous pouvez utiliser fnmatch() si vous préférez les modèles glob, ou regcomp()+regexec() si vous préférez les expressions régulières (elles sont intégrées à la bibliothèque GNU C, aucune bibliothèque externe n'est nécessaire).

Voici un exemple de programme, example.c , qui imprime simplement tous les symboles :

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

Pour compiler et exécuter ce qui précède, utilisez par exemple

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

Pour voir les symboles dans le programme lui-même, utilisez le -rdynamic drapeau au moment du lien pour ajouter tous les symboles à la table des symboles dynamiques :

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

Sur mon système, ce dernier imprime

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

J'ai utilisé ... pour marquer où j'ai supprimé beaucoup de lignes.

Des questions ?


Pour obtenir une liste des symboles exportés depuis une bibliothèque partagée (un .so ) sous Linux, il y a deux manières :la plus simple et la plus difficile.

Le plus simple est d'utiliser les outils de la console déjà disponibles :objdump (inclus dans les binutils GNU) :

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

Le moyen un peu plus difficile consiste à utiliser libelf et écrivez vous-même un programme C/C++ pour lister les symboles. Jetez un œil au elfutils package, qui est également construit à partir de la source libelf. Il existe un programme appelé eu-readelf (la version elfutils de readelf, à ne pas confondre avec la readelf binutils). eu-readelf -s $LIB répertorie les symboles exportés à l'aide de libelf, vous devriez donc pouvoir l'utiliser comme point de départ.


Linux
  1. Comment obtenir des informations sur le système et le matériel sous Linux

  2. Renommer tous les fichiers et noms de répertoires en minuscules sous Linux

  3. Comment définir et utiliser des fonctions dans Linux Shell Script

  4. J'ai utilisé Ctrl-Alt-F6 sous Linux et je ne peux pas récupérer mon écran

  5. Comment obtenir le nom et la version de la distribution Linux ?

Sortie de Linux Mint 19.3 "Tricia":voici les nouveautés et comment l'obtenir

Lancement de Linux Mint 19.2 "Tina":voici les nouveautés et comment l'obtenir

Le noyau Linux et ses fonctions

Processus Linux - Disposition de la mémoire, sortie et fonctions C _exit

Qu'est-ce que les appels système Linux et les fonctions de bibliothèque ?

10 meilleurs clients et services VPN Linux pour vous protéger