Доста се дразня, когато видя въпроси, които питат как да направите нещо в операционна система X, което правите в Y.
В повечето случаи това не е полезен подход, тъй като всяка операционна система (семейство) има свой собствен подход към проблемите, така че опитът да се приложи нещо, което работи в X в Y, е като да напъхате куб в кръгла дупка.
Моля, обърнете внимание: текстът тук е замислен като груб, а не снизходителен; владеенето на английски език не е толкова добро, колкото ми се иска. Суровостта, съчетана с действителна помощ и насоки към известни работещи решения, според моя опит изглежда работи най-добре за преодоляване на нетехнически ограничения.
В Linux тестова среда трябва да използва нещо подобно
LC_ALL=C LANG=C readelf -s FILE
за да изброите всички символи в FILE
. readelf
е част от пакета binutils и се инсталира, ако възнамерявате да създавате нови двоични файлове в системата. Това води до преносим, стабилен код. Не забравяйте, че Linux обхваща множество хардуерни архитектури, които наистина имат разлики.
За да създадете двоични файлове в Linux, обикновено използвате някои от инструментите, предоставени в binutils. Ако binutils предоставяше библиотека или имаше ELF библиотека, базирана на кода, използван в binutils, би било много по-добре да се използва това, вместо да се анализира изхода на човешките помощни програми. Въпреки това няма такава библиотека (библиотеката libbfd, която binutils използва вътрешно, не е специфична за ELF). [URL=http://www.mr511.de/software/english.html]libelf[/URL] библиотеката е добра, но е напълно отделна работа от предимно един автор. Грешките в него са докладвани на binutils, което е непродуктивно, тъй като двете не са свързани. Просто казано, няма гаранции, че той обработва ELF файловете на дадена архитектура по същия начин, по който го прави binutils. Ето защо, за здравина и надеждност, определено ще искате да използвате binutils.
Ако имате тестово приложение, то трябва да използва скрипт, например /usr/lib/yourapp/list-test-functions
, за да изброи функциите, свързани с теста:
#!/bin/bash
export LC_ALL=C LANG=C
for file in "$@" ; 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
По този начин, ако има архитектура, която има странности (по-специално в изходния формат readelf
на binutils), трябва само да модифицирате скрипта. Модифицирането на такъв прост скрипт не е трудно и е лесно да се провери дали скриптът работи правилно -- просто сравнете необработения readelf
изход с изхода на скрипта; всеки може да направи това.
Подпрограма, която конструира канал, fork()
s дъщерен процес, изпълнява скрипта в дъщерния процес и използва напр. getline()
в родителския процес за четене на списъка с имена е доста проста и изключително стабилна. Тъй като това също е единственото уязвимо място, направихме много лесно коригирането на всякакви странности или проблеми тук, като използваме този външен скрипт (който може да се персонализира/разширява, за да покрие тези странности и лесен за отстраняване на грешки). Не забравяйте, че ако самият binutils има грешки (различни от грешки във форматирането на изхода), всички построени двоични файлове почти сигурно ще показват същите тези грешки.
Като човек, ориентиран към Microsoft, вероятно ще имате проблеми с разбирането на предимствата на такъв модулен подход. (Това не е специфично за Microsoft, а специфично за контролирана от един доставчик екосистема, където подходът, насочен от доставчика, е чрез всеобхватни рамки и черни кутии с изчистени, но много ограничени интерфейси. Мисля, че като рамково ограничение или наложена от продавача оградена градина или затворническа градина. Изглежда добре, но излизането е трудно. За описание и история на модулния подход, който се опитвам да опиша, вижте например статията за философията на Unix в Уикипедия.)
Следното показва, че вашият подход наистина е възможен и в Linux – макар и тромав и крехък; тези неща са предназначени да се правят с помощта на стандартните инструменти вместо това. Просто не е правилният подход като цяло.
Интерфейсът, symbols.h
, е най-лесен за изпълнение с помощта на функция за обратно извикване, която се извиква за всеки намерен символ:
#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 */
Свързването на символа ELF и макросите за тип са специфични за размера на думата, така че, за да избегна караницата, декларирах типовете enum по-горе. Пропуснах обаче някои безинтересни типове (STT_NOTYPE
, STT_SECTION
, STT_FILE
).
Изпълнението, 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);
}
Когато компилирате горното, не забравяйте да се свържете с библиотеката dl
.
Може да намерите функцията gnu_hashtab_symbol_count()
по-горе за интересна; форматът на таблицата не е добре документиран навсякъде, където мога да намеря. Това е тествано да работи както на i386, така и на x86-64 архитектури, но трябва да бъде проверено спрямо източниците на GNU, преди да разчитате на него в производствения код. Отново, по-добрият вариант е просто да използвате тези инструменти директно чрез помощен скрипт, тъй като те ще бъдат инсталирани на всяка машина за разработка.
Технически, таблица DT_GNU_HASH
ни казва първия динамичен символ, а най-високият индекс във всяка хеш група ни казва последния динамичен символ, но тъй като записите в таблицата със символи DT_SYMTAB
винаги започват с 0 (всъщност записът 0 е "няма" ), вземам предвид само горната граница.
За съпоставяне на имена на библиотеки и функции препоръчвам да използвате strncmp()
за съвпадение на префикс за библиотеки (съвпадение в началото на името на библиотеката, до първото .
). Разбира се, можете да използвате fnmatch()
, ако предпочитате глобални модели, или regcomp()+regexec()
ако предпочитате регулярни изрази (те са вградени в библиотеката GNU C, няма външни библиотеки необходими).
Ето примерна програма, example.c
, която просто отпечатва всички символи:
#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;
}
За да компилирате и стартирате горното, използвайте например
gcc -Wall -O2 -c symbols.c
gcc -Wall -O2 -c example.c
gcc -Wall -O2 example.o symbols.o -ldl -o example
./example | less
За да видите символите в самата програма, използвайте флага -rdynamic
по време на връзката, за да добавите всички символи към таблицата с динамични символи:
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
В моята система последният се отпечатва
(): 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
Използвах ...
, за да отбележа къде съм премахнал много редове.
въпроси?
person
Nominal Animal
schedule
28.04.2015
.so
под Linux, нали? Не искате да анализирате.dll
? Или използвате mono и това извежда DLL файлове дори на Linux? - person Christian Aichinger   schedule 27.04.2015ELF
-- може би не сте знаели това. - person Jongware   schedule 27.04.2015dlsym
, за да получа указателя към функцията. - person justin.m.chase   schedule 28.04.2015