GNU/Linux >> Linux Esercitazione >  >> Linux

Ottieni nomi e indirizzi delle funzioni esportate in Linux

Mi arrabbio piuttosto quando vedo domande che chiedono come fare qualcosa nel sistema operativo X che fai in Y.

Nella maggior parte dei casi, non è un approccio utile, perché ogni sistema operativo (famiglia) tende ad avere il proprio approccio ai problemi, quindi provare ad applicare qualcosa che funziona in X in Y è come infilare un cubo in un buco rotondo.

Nota:il testo qui è inteso come duro, non condiscendente; la mia padronanza della lingua inglese non è buona come vorrei. La severità combinata con un aiuto effettivo e indicazioni a soluzioni funzionanti conosciute sembra funzionare meglio per superare i limiti non tecnici, secondo la mia esperienza.

In Linux, un ambiente di test dovrebbe usa qualcosa come

LC_ALL=C LANG=C readelf -s FILE

per elencare tutti i simboli in FILE . readelf fa parte del pacchetto binutils e viene installato se si intende creare nuovi binari sul sistema. Questo porta a un codice portatile e robusto. Non dimenticare che Linux comprende più architetture hardware che presentano reali differenze.

Per creare binari in Linux, normalmente si utilizzano alcuni degli strumenti forniti in binutils. Se binutils fornisse una libreria, o ci fosse una libreria ELF basata sul codice usato in binutils, sarebbe molto meglio usarla, piuttosto che analizzare l'output delle utility umane. Tuttavia, non esiste una tale libreria (la libreria libbfd che binutils usa internamente non è specifica per ELF). La libreria [URL=http://www.mr511.de/software/english.html]libelf[/URL] è buona, ma è un'opera completamente separata principalmente di un singolo autore. I bug in esso contenuti sono stati segnalati a binutils, il che è improduttivo, poiché i due non sono correlati. In poche parole, non ci sono garanzie che gestisca i file ELF su una data architettura allo stesso modo di binutils. Pertanto, per robustezza e affidabilità, vorrai sicuramente usare binutils.

Se hai un'applicazione di test, dovrebbe usare uno script, ad esempio /usr/lib/yourapp/list-test-functions , per elencare le funzioni relative ai 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

In questo modo, se c'è un'architettura che ha stranezze (nel readelf delle binutils formato di output in particolare), è sufficiente modificare lo script. Modificare uno script così semplice non è difficile, ed è facile verificare che lo script funzioni correttamente -- basta confrontare il readelf grezzo output all'output dello script; chiunque può farlo.

Una subroutine che costruisce una pipe, fork() è un processo figlio, esegue lo script nel processo figlio e utilizza ad es. getline() nel processo genitore per leggere l'elenco dei nomi, è abbastanza semplice ed estremamente robusto. Dato che questo è anche l'unico punto fragile, abbiamo reso molto facile risolvere eventuali stranezze o problemi qui usando quello script esterno (che è personalizzabile/estensibile per coprire quelle stranezze e facile da eseguire il debug). Ricorda, se binutils stesso ha dei bug (diversi dai bug di formattazione dell'output), qualsiasi binario compilato presenterà quasi certamente anche gli stessi bug.

Essendo una persona orientata a Microsoft, probabilmente avrai difficoltà a cogliere i vantaggi di un simile approccio modulare. (Non è specifico di Microsoft, ma specifico di un ecosistema controllato da un singolo fornitore in cui l'approccio spinto dal fornitore avviene tramite framework globali e scatole nere con interfacce pulite ma molto limitate. Penso che sia la limitazione della struttura, o il giardino recintato imposto dai venditori, o il giardino della prigione. Sembra buono, ma uscire è difficile. Per la descrizione e la storia dell'approccio modulare che sto cercando di descrivere, vedi ad esempio l'articolo sulla filosofia Unix su Wikipedia.)

Quanto segue mostra che il tuo approccio è effettivamente possibile anche in Linux, sebbene goffo e fragile; questa roba è pensata per essere fatta usando invece gli strumenti standard. Non è solo l'approccio giusto in generale.

L'interfaccia, symbols.h , è più semplice da implementare utilizzando una funzione di callback che viene chiamata per ogni simbolo trovato:

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

L'associazione di simboli ELF e le macro di tipo sono specifiche per la dimensione della parola, quindi per evitare problemi, ho dichiarato i tipi enum sopra. Ho omesso alcuni tipi poco interessanti (STT_NOTYPE , STT_SECTION , STT_FILE ), tuttavia.

L'implementazione, 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);
}

Quando compili quanto sopra, ricordati di collegarti al dl libreria.

Potresti trovare il gnu_hashtab_symbol_count() funzione sopra interessante; il formato della tabella non è ben documentato da nessuna parte che io possa trovare. Questo è testato per funzionare su entrambe le architetture i386 e x86-64, ma dovrebbe essere verificato rispetto ai sorgenti GNU prima di fare affidamento su di esso nel codice di produzione. Ancora una volta, l'opzione migliore è utilizzare questi strumenti direttamente tramite uno script di supporto, poiché verranno installati su qualsiasi computer di sviluppo.

Tecnicamente, un DT_GNU_HASH table ci dice il primo simbolo dinamico e l'indice più alto in qualsiasi hash bucket ci dice l'ultimo simbolo dinamico, ma poiché le voci nel DT_SYMTAB la tabella dei simboli inizia sempre da 0 (in realtà, la voce 0 è "none"), considero solo il limite superiore.

Per abbinare i nomi delle librerie e delle funzioni, consiglio di utilizzare strncmp() per una corrispondenza di prefisso per le librerie (corrisponde all'inizio del nome della libreria, fino al primo . ). Ovviamente puoi usare fnmatch() se preferisci i modelli glob, o regcomp()+regexec() se preferisci le espressioni regolari (sono integrate nella libreria GNU C, non sono necessarie librerie esterne).

Ecco un programma di esempio, example.c , che stampa solo tutti i simboli:

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

Per compilare ed eseguire quanto sopra, usa per esempio

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

Per vedere i simboli nel programma stesso, usa il -rdynamic flag al momento del collegamento per aggiungere tutti i simboli alla tabella dei simboli dinamici:

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

Sul mio sistema, quest'ultimo stampa

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

Ho usato ... per contrassegnare dove ho rimosso molte righe.

Domande?


Per ottenere un elenco di simboli esportati da una libreria condivisa (un file .so ) sotto Linux, ci sono due modi:quello facile e uno leggermente più difficile.

Quello facile è utilizzare gli strumenti della console già disponibili:objdump (incluso nelle 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
...

Il modo leggermente più difficile è usare libelf e scrivi un programma C/C++ per elencare tu stesso i simboli. Dai un'occhiata al elfutils package, anch'esso compilato dal sorgente libelf. C'è un programma chiamato eu-readelf (la versione elfutils di readelf, da non confondere con binutils readelf). eu-readelf -s $LIB elenca i simboli esportati usando libelf, quindi dovresti essere in grado di usarlo come punto di partenza.


Linux
  1. Quali caratteri sono vietati nei nomi delle directory di Windows e Linux?

  2. Ottieni un elenco di nomi di funzioni in uno script di shell

  3. Ottieni indirizzi IPv6 in Linux usando ioctl

  4. Ottenere i nomi delle funzioni in una libreria condivisa a livello di codice

  5. Come posso fare in modo che Windows veda i nomi host di Linux e viceversa?

Rilasciato Linux Mint 19.3 "Tricia":ecco cosa c'è di nuovo e come ottenerlo

Rilasciato Linux Mint 19.2 "Tina":ecco cosa c'è di nuovo e come ottenerlo

Kernel Linux e le sue funzioni

Processi Linux:layout di memoria, uscita e funzioni C _exit

Che cosa sono le chiamate di sistema Linux e le funzioni di libreria?

10 migliori client e servizi VPN Linux per proteggerti