GNU/Linux >> Tutoriales Linux >  >> Linux

Obtener nombres y direcciones de funciones exportadas en Linux

Me molesta bastante cuando veo preguntas sobre cómo hacer algo en el sistema operativo X que tú haces en Y.

En la mayoría de los casos, no es un enfoque útil, porque cada sistema operativo (familia) tiende a tener su propio enfoque de los problemas, por lo que tratar de aplicar algo que funciona en X en Y es como meter un cubo en un agujero redondo.

Tenga en cuenta:el texto aquí tiene la intención de ser duro, no condescendiente; mi dominio del idioma inglés no es tan bueno como me gustaría. Según mi experiencia, la dureza combinada con la ayuda real y los indicadores de soluciones de trabajo conocidas parecen funcionar mejor para superar las limitaciones no técnicas.

En Linux, un entorno de prueba debería usa algo como

LC_ALL=C LANG=C readelf -s FILE

para listar todos los símbolos en FILE . readelf es parte del paquete binutils y se instala si tiene la intención de construir nuevos binarios en el sistema. Esto conduce a un código portátil y robusto. No olvide que Linux abarca múltiples arquitecturas de hardware que tienen diferencias reales.

Para construir binarios en Linux, normalmente usa algunas de las herramientas provistas en binutils. Si binutils proporcionara una biblioteca, o hubiera una biblioteca ELF basada en el código utilizado en binutils, sería mucho mejor usar eso, en lugar de analizar la salida de las utilidades humanas. Sin embargo, no existe tal biblioteca (la biblioteca libbfd que binutils usa internamente no es específica de ELF). La biblioteca [URL=http://www.mr511.de/software/english.html]libelf[/URL] es buena, pero es un trabajo completamente separado de un solo autor. Se han informado errores en él a binutils, lo cual es improductivo, ya que los dos no están relacionados. En pocas palabras, no hay garantías de que maneje los archivos ELF en una arquitectura dada de la misma manera que lo hace binutils. Por lo tanto, para robustez y confiabilidad, definitivamente querrá usar binutils.

Si tiene una aplicación de prueba, debe usar un script, digamos /usr/lib/yourapp/list-test-functions , para enumerar las funciones relacionadas con la prueba:

#!/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 esta manera, si hay una arquitectura que tiene peculiaridades (en el readelf de binutils formato de salida en particular), solo necesita modificar el script. Modificar una secuencia de comandos tan simple no es difícil, y es fácil verificar que la secuencia de comandos funcione correctamente; simplemente compare el readelf sin procesar salida a la salida del script; cualquiera puede hacer eso.

Una subrutina que construye una tubería, fork() s un proceso secundario, ejecuta el script en el proceso secundario y utiliza, p. getline() en el proceso principal para leer la lista de nombres, es bastante simple y extremadamente robusto. Dado que este también es el único punto frágil, hemos hecho que sea muy fácil solucionar cualquier peculiaridad o problema aquí mediante el uso de ese script externo (que es personalizable/extensible para cubrir esas peculiaridades y fácil de depurar). Recuerde, si binutils se tiene errores (aparte de los errores de formato de salida), es casi seguro que cualquier archivo binario creado también exhibirá esos mismos errores.

Al ser una persona orientada a Microsoft, probablemente tendrá problemas para comprender los beneficios de un enfoque modular de este tipo. (No es específico de Microsoft, sino específico de un ecosistema controlado por un solo proveedor donde el enfoque impulsado por el proveedor es a través de marcos generales y cajas negras con interfaces limpias pero muy limitadas. Lo pienso como la limitación del marco, o el jardín amurallado impuesto por el vendedor, o el jardín de la prisión. Se ve bien, pero salir es difícil. Para obtener una descripción e historia del enfoque modular que estoy tratando de describir, consulte, por ejemplo, el artículo sobre la filosofía de Unix en Wikipedia.)

Lo siguiente muestra que su enfoque también es posible en Linux, aunque torpe y frágil; este material está destinado a hacerse utilizando las herramientas estándar en su lugar. Simplemente no es el enfoque correcto en general.

La interfaz, symbols.h , es más fácil de implementar usando una función de devolución de llamada que se llama para cada símbolo encontrado:

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

El enlace de símbolos ELF y las macros de tipo son específicas del tamaño de palabra, por lo que para evitar molestias, declaré los tipos de enumeración anteriores. Omití algunos tipos poco interesantes (STT_NOTYPE , STT_SECTION , STT_FILE ), sin embargo.

La implementación, 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);
}

Al compilar lo anterior, recuerde enlazar contra el dl biblioteca.

Puede encontrar el gnu_hashtab_symbol_count() función por encima de interesante; el formato de la tabla no está bien documentado en ninguna parte que pueda encontrar. Esto está probado para funcionar en arquitecturas i386 y x86-64, pero debe examinarse con las fuentes de GNU antes de confiar en él en el código de producción. Nuevamente, la mejor opción es usar esas herramientas directamente a través de un script de ayuda, ya que se instalarán en cualquier máquina de desarrollo.

Técnicamente, un DT_GNU_HASH table nos dice el primer símbolo dinámico, y el índice más alto en cualquier cubo hash nos dice el último símbolo dinámico, pero dado que las entradas en el DT_SYMTAB la tabla de símbolos siempre comienza en 0 (en realidad, la entrada 0 es "ninguna"), solo considero el límite superior.

Para hacer coincidir los nombres de biblioteca y función, recomiendo usar strncmp() para una coincidencia de prefijo para bibliotecas (coincidencia al comienzo del nombre de la biblioteca, hasta el primer . ). Por supuesto, puedes usar fnmatch() si prefiere patrones globales, o regcomp()+regexec() si prefiere expresiones regulares (están integradas en la biblioteca GNU C, no se necesitan bibliotecas externas).

Aquí hay un programa de ejemplo, example.c , que solo imprime todos los símbolos:

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

Para compilar y ejecutar lo anterior, use por ejemplo

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

Para ver los símbolos en el propio programa, utilice el -rdynamic marca en el momento del enlace para agregar todos los símbolos a la tabla de símbolos dinámicos:

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

En mi sistema, este último 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

Usé ... para marcar dónde eliminé muchas líneas.

¿Preguntas?


Para obtener una lista de símbolos exportados de una biblioteca compartida (un .so ) en Linux, hay dos formas:la fácil y una un poco más difícil.

La más fácil es usar las herramientas de la consola ya disponibles:objdump (incluido en 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
...

La forma un poco más difícil es usar libelf y escriba un programa C/C++ para enumerar los símbolos usted mismo. Eche un vistazo al elfutils paquete, que también se construye a partir de la fuente libelf. Hay un programa llamado eu-readelf (la versión elfutils de readelf, que no debe confundirse con binutils readelf). eu-readelf -s $LIB enumera los símbolos exportados usando libelf, por lo que debería poder usarlo como punto de partida.


Linux
  1. Cómo obtener información sobre el sistema y el hardware en Linux

  2. Cambiar el nombre de todos los archivos y nombres de directorios a minúsculas en Linux

  3. Cómo definir y usar funciones en Linux Shell Script

  4. ¿Qué caracteres están prohibidos en los nombres de directorio de Windows y Linux?

  5. Obtener una lista de nombres de funciones en un script de shell

Lanzamiento de Linux Mint 19.3 "Tricia":esto es lo nuevo y cómo obtenerlo

Lanzamiento de Linux Mint 19.2 "Tina":esto es lo nuevo y cómo obtenerlo

Kernel de Linux y sus funciones

Procesos de Linux:diseño de memoria, salida y funciones C _exit

¿Qué son las llamadas al sistema Linux y las funciones de biblioteca?

Los 10 mejores clientes y servicios VPN de Linux para que esté protegido