Меня очень раздражает, когда я вижу вопросы о том, как сделать что-то в операционной системе X, что вы делаете в Y.
В большинстве случаев это бесполезный подход, потому что каждая операционная система (семейство) имеет свой собственный подход к проблемам, поэтому пытаться применить что-то, что работает в X, в Y — все равно, что засовывать куб в круглую дыру.
Обратите внимание: текст здесь задуман как резкий, а не снисходительный; мое владение английским языком не так хорошо, как хотелось бы. Судя по моему опыту, жесткость в сочетании с реальной помощью и указанием на известные рабочие решения лучше всего помогает преодолеть нетехнические ограничения.
В Linux тестовая среда должна использовать что-то вроде
LC_ALL=C LANG=C readelf -s FILE
чтобы перечислить все символы в FILE
. readelf
является частью пакета binutils и устанавливается, если вы собираетесь создавать в системе новые двоичные файлы. Это приводит к переносимому, надежному коду. Не забывайте, что Linux включает в себя несколько аппаратных архитектур, которые действительно отличаются друг от друга.
Для создания бинарных файлов в Linux вы обычно используете некоторые инструменты, предоставляемые в binutils. Если бы binutils предоставляла библиотеку или существовала библиотека ELF, основанная на коде, используемом в binutils, было бы гораздо лучше использовать ее, а не анализировать вывод утилит, созданных человеком. Однако такой библиотеки нет (внутренне используемая binutils библиотека libbfd не является специфичной для 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 и макросы типов зависят от размера слова, поэтому, чтобы избежать хлопот, я объявил типы перечисления выше. Однако я пропустил некоторые неинтересные типы (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
? Или вы используете моно, и это выводит файлы DLL даже в Linux? - person Christian Aichinger   schedule 27.04.2015ELF
— возможно, вы этого не знали. - person Jongware   schedule 27.04.2015dlsym
, чтобы получить указатель на функцию. - person justin.m.chase   schedule 28.04.2015