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.