Docela mě štve, když vidím otázky, jak udělat něco v operačním systému X, co děláte v Y.
Ve většině případů to není užitečný přístup, protože každý operační systém (rodina) má tendenci mít svůj vlastní přístup k problémům, takže pokusit se aplikovat něco, co funguje v X v Y, je jako nacpat krychli do kulaté díry.
Vezměte prosím na vědomí:text zde je zamýšlen jako drsný, nikoli blahosklonný; moje znalost angličtiny není tak dobrá, jak bych si přál. Podle mých zkušeností se zdá, že drsnost v kombinaci se skutečnou nápovědou a odkazy na známá fungující řešení nejlépe fungují při překonávání netechnických omezení.
V systému Linux by mělo testovací prostředí použijte něco jako
LC_ALL=C LANG=C readelf -s FILE
vypíše všechny symboly v FILE
. readelf
je součástí balíčku binutils a instaluje se, pokud máte v úmyslu v systému vytvářet nové binární soubory. To vede k přenosnému robustnímu kódu. Nezapomeňte, že Linux zahrnuje několik hardwarových architektur, které se skutečně liší.
K sestavení binárních souborů v Linuxu obvykle používáte některé nástroje poskytované v binutils. Pokud by binutils poskytoval knihovnu nebo existovala knihovna ELF založená na kódu používaném v binutilech, bylo by mnohem lepší použít to, než analyzovat výstup lidských utilit. Žádná taková knihovna však neexistuje (knihovna libbfd, kterou binutils používá interně, není specifická pro ELF). Knihovna [URL=http://www.mr511.de/software/english.html]libelf[/URL] je dobrá, ale je to zcela samostatné dílo převážně jednoho autora. Chyby v něm byly hlášeny binutilům, což je neproduktivní, protože tyto dva spolu nesouvisí. Jednoduše řečeno, neexistují žádné záruky, že zachází se soubory ELF na dané architektuře stejně jako binutils. Z důvodu robustnosti a spolehlivosti budete určitě chtít používat binutils.
Pokud máte testovací aplikaci, měla by používat skript, řekněme /usr/lib/yourapp/list-test-functions
, zobrazí se seznam funkcí souvisejících s testem:
#!/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
Tímto způsobem, pokud existuje architektura, která má zvláštnosti (v readelf
binutils výstupní formát), stačí pouze upravit skript. Úprava takto jednoduchého skriptu není obtížná a je snadné ověřit, zda skript funguje správně – stačí porovnat nezpracovaný readelf
výstup na výstup skriptu; to může udělat každý.
Podprogram, který vytváří potrubí, fork()
je podřízený proces, provádí skript v podřízeném procesu a používá např. getline()
v nadřazeném procesu číst seznam jmen, je poměrně jednoduchý a extrémně robustní. Vzhledem k tomu, že toto je také jediné křehké místo, velmi snadno jsme zde opravili jakékoli výstřednosti nebo problémy pomocí externího skriptu (který je přizpůsobitelný/rozšiřitelný tak, aby pokryl tyto výstřednosti, a lze jej snadno ladit). Pamatujte, že pokud se binutils sám obsahuje chyby (jiné než chyby formátování výstupu), všechny vytvořené binární soubory téměř jistě také vykazují stejné chyby.
Jako člověk orientovaný na Microsoft budete mít pravděpodobně problémy s pochopením výhod takového modulárního přístupu. (Není to specifické pro Microsoft, ale specifické pro ekosystém kontrolovaný jedním dodavatelem, kde je přístup prosazovaný dodavatelem prostřednictvím překlenujících rámců a černé skříňky s čistým, ale velmi omezeným rozhraním. Myslím, že je to rámcové omezení, nebo prodejcem vynucená zděná zahrada nebo vězeňská zahrada. Vypadá to dobře, ale dostat se ven je těžké. Popis a historii modulárního přístupu, který se snažím popsat, naleznete například v článku o filozofii Unixu na Wikipedii.)
Následující ukazuje, že váš přístup je skutečně možný i v Linuxu -- i když neohrabaný a křehký; tyto věci jsou určeny k provádění pomocí standardních nástrojů. Obecně to prostě není správný přístup.
Rozhraní, symbols.h
, je nejjednodušší implementovat pomocí funkce zpětného volání, která se volá pro každý nalezený symbol:
#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 */
Vazba symbolu ELF a typová makra jsou specifická pro velikost slova, takže abych se vyhnul potížím, uvedl jsem výše uvedené typy výčtu. Vynechal jsem některé nezajímavé typy (STT_NOTYPE
, STT_SECTION
, STT_FILE
), nicméně.
Implementace, 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);
}
Při kompilaci výše uvedeného nezapomeňte odkazovat na dl
knihovna.
Můžete najít gnu_hashtab_symbol_count()
funkce výše zajímavé; formát tabulky není nikde dobře zdokumentován. Toto je testováno, aby fungovalo na architekturách i386 i x86-64, ale než se na to spolehnete v produkčním kódu, mělo by to být prověřeno podle zdrojů GNU. Opět platí, že lepší možností je použít tyto nástroje přímo prostřednictvím pomocného skriptu, protože se nainstalují na jakýkoli vývojový stroj.
Technicky vzato, DT_GNU_HASH
tabulka nám říká první dynamický symbol a nejvyšší index v libovolném segmentu hash nám říká poslední dynamický symbol, ale protože položky v DT_SYMTAB
tabulka symbolů vždy začíná na 0 (ve skutečnosti je položka 0 "žádná"), beru v úvahu pouze horní hranici.
Pro shodu názvů knihoven a funkcí doporučuji použít strncmp()
pro shodu předpony pro knihovny (shoda na začátku názvu knihovny až do prvních .
). Samozřejmě můžete použít fnmatch()
pokud dáváte přednost vzorům glob nebo regcomp()+regexec()
pokud dáváte přednost regulárním výrazům (jsou vestavěny do knihovny GNU C, nejsou potřeba žádné externí knihovny).
Zde je příklad programu example.c
, který pouze vytiskne všechny symboly:
#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;
}
Pro kompilaci a spuštění výše uvedeného použijte například
gcc -Wall -O2 -c symbols.c
gcc -Wall -O2 -c example.c
gcc -Wall -O2 example.o symbols.o -ldl -o example
./example | less
Chcete-li zobrazit symboly v samotném programu, použijte -rdynamic
flag v čase odkazu pro přidání všech symbolů do tabulky dynamických symbolů:
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
V mém systému se poslední vytiskne
(): 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
Použil jsem ...
pro označení místa, kde jsem odstranil spoustu řádků.
Otázky?
Chcete-li získat seznam exportovaných symbolů ze sdílené knihovny (a .so
) pod Linuxem existují dva způsoby:snadný a o něco těžší.
Nejjednodušší je použít již dostupné nástroje konzoly:objdump
(zahrnuto v 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
...
O něco těžší způsob je použít libelf
a napište si program v C/C++, abyste si vypsali symboly sami. Podívejte se na elfutils
balíček, který je také vytvořen ze zdroje urážky na cti. Existuje program nazvaný eu-readelf
(elfutils verze readelf, nezaměňovat s binutils readelf). eu-readelf -s $LIB
uvádí exportované symboly pomocí libelf, takže byste to měli být schopni použít jako výchozí bod.