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.