Привет. Так вышло, что пришлось основательно разобраться в теме и пока память свежа изложил всё в виде небольшой памятки. Удобно по прошествии некоторого времени освежить память прочитав небольшой конспект. Вообще, по-хорошему, блог что ли какой завести )). Просьба - не флудить, ссылки/комментарии/дополнения по теме приветствуются. ЗЫ: подразумевается, что либы -fpic
1. Утилиты readelf, objdump. Читать man elf, man ld.so. N в именах структор
подразумевает 32 или 64.
2. Структура ELF файла:
1. заголовок (смещение 0, struct ElfN_Ehdr). Readelf::ELF Header
2. program header table (массив struct ElfN_Phdr). Содержит информацию о том
как отображать секции в память процесса. Readelf::Program Headers
3. section header table (массив struct ElfN_Shdr). Readelf::Section Headers
3. link_map
3.1. Загруженные в память модули попадают в список (массив) из struct link_map.
Списков может быть много, каждый список - "пространство имён". Для
загрузки модулей в неглобальный список (создание нового) используется
dlmopen().
3.1. Получать link_map модуля через dlinfo() или dladdr1():
[--code--]
#define _GNU_SOURCE
#include <link.h>
#include <dlfcn.h>
#include <stdio.h>
int main()
{
static char addr_in_mod;
Dl_info __info;
struct link_map *lm;
if(dladdr1(&addr_in_mod, &__info, (void*)&lm, RTLD_DL_LINKMAP) != 0) {
printf("link_map:\n");
struct link_map *i = lm;
for(; i->l_prev != NULL; i = i->l_prev);
for (; i != NULL; i = i->l_next)
printf("addr diff=%p name=%s%s",(void*)i->l_addr, i->l_name, i==lm?" <--cur\n":"\n");
}
}
//output:
//link_map:
//addr diff=0x41f000 name= <--current module
//addr diff=0xb7fc4000 name=linux-gate.so.1
//addr diff=0xb7fa3000 name=/lib/libdl.so.2
//addr diff=0xb7dc5000 name=/lib/libc.so.6
//addr diff=0xb7fc6000 name=/lib/ld-linux.so.2
[/--code--]
3.2. Во время переразмещений символ ищется в модулях указанных в link_map
списке начиная от начала списка т.е. порядок важен, "gcc -ls1 -ls2"
libs1.so находится в списке раньше, чем libs2.so.
3.3. При добавлении библиотеки через LD_PRELOAD, она попадает перед остальными
разделяемыми библиотеками в глобальном link_map списке.
3.4. Опция RTLD_DEEPBIND для dlopen - собственные символы модуля приоритетнее
символов из вышестоящих в link_map списке модулей.
Собственные символы загружаемой библиотеки содержат:
1. символы из самой загружаемой библиотеке
2. символы из библиотек, которые были слинкованы с загружаемой из
командной строки (у первых приоритет выше).
3.5. При загрузки через dlopen, библиотеки добавленные с флагом RTLD_GLOBAL
имеют приоритет над RTLD_LOCAL, не смотря на то, что находятся в link_map
списке позже (не относится к получению void f() через dlsym()). Например:
[--code--]
// предоставляет void f(), ссылается на void f().
dlopen("lib1.so", RTLD_LOCAL);
// предоставляет void f().
dlopen("lib2.so", RTLD_GLOBAL);
// при ленивом переразмещении, lib1.so будет ссылаться на lib2.so::f().
[/--code--]
4. RTLD_GLOBAL - символы из загруженного модуля будут участвовать в
переразмещениях для заргуженных в дальнейшем библиотек. RTLD_LOCAL - не будут.
Если lib2.so линкуется с lib1.so через командную строку
"gcc -fpic -shared -l2 s.c -o lib1.so", то видимость символов из lib2.so
наследуется от видимости символов из lib1.so:
[--code--]
dlopen("./lib1.so", RTLD_LAZY|RTLD_GLOBAL); // символы из lib2.so глобальные
dlopen("./lib1.so", RTLD_LAZY|RTLD_LOCAL); // символы из lib2.so локальные
[/--code--]
Если lib2.so подгружается из lib1.so через dlopen(), то видимость символов
из lib2.so контролируется флагом dlopen() при загрузке lib2.so. Способ
загрузки (через командную строку или dlopen) и флаг для dlopen при
загрузки lib1.so значения не имеет.
5. Переразмещение (relocation).
5.1. Переразмещение - процесс соединения символьной ссылки с символьным
определением.
Переразмещение: ленивое - загрузчик вызывается при ссылке на символ, и
ненеленивое - переразмещение при загрузке. Переразмещение переменных всегда
неленивое.
5.2. Символы, требующие переразмещения, содержатся в .rel... секциях. В них
находятся ElfN_Rel структуры.
[--code--]
typedef struct {
Elf32_Addr r_offset; \\ адрес внесения правки (адрес в GOT, например. readelf::Offset).
uint32_t r_info; \\ содержит тип переразмещения и индекс в таблице символов (массив Elf32_Sym[]).
} Elf32_Rel;
typedef struct {
uint32_t st_name; \\ индекс в таблице строк. Т.е. сопостовляет символ с Си строкой.
Elf32_Addr st_value; \\ адрес символа в текущем модуле (readelf::Sym.Value).
uint32_t st_size;
unsigned char st_info;
unsigned char st_other;
uint16_t st_shndx;
} Elf32_Sym;
[/--code--]
5.3. Механизм обращения к переменным (требующим переразмещений):
1. линкер на старте правит .got секцию, она начинает указывать на нужные
данные.
2. ссылка на переменную в коде (в .text секции):
[--code--]
call 44c <__x86.get_pc_thunk.ax> # получаем в eax адрес следующей инструкции
add $0x1bcb,%eax # в eax адрес .got секции
mov 0x14(%eax),%edx # отступ от края .got на адрес переменной,
# разыменовываем в edx
[/--code--]
5.4. Механизм обращения к функциям, для пример - exfn():
1. ссылка на exfn() в коде (в .text секции)
2. переход на "трамплин" в .plt секции - plt@exfn()
3. переход на разыменованный указатель из .got.plt, если переразмещение
уже было произведено, то попадаем на exfn(), иначе:
3.1. возврат в plt@exfn(), в стек кладётся смещение в .rel.plt
секции Elf32_Rel структуры и указатель на link_map список
3.2. вызов ld.so, правится указатель в .got.plt
3.3. переход на exfn().
6. .dynamic секция может быть прочитана из программы через массив _DYNAMIC[],
который содержит struct ElfN_Dyn, автоматически заполняется линкером.
7. Экспортируемые символы из elf модуля указываются в .dynsym секции.
8. -rdynamic опция линкера (для исполняемого ELF) - символы из exe, которые не
были востребованы библиотеками, указанными в командной строке, не
экспортируются (не указываются в .dynsym секции) и не участвуют в
переразмещениях в библиотеках, которые подргружаются через dlopen. Данная
опция заставляет линкер помещать в таблицу все функции.
9. Управление экспортом из модуля
* Управление экспортом по умолчанию:
gcc -fvisibility=default
-fvisibility=hidden
-fvisibility=internal
-fvisibility=protected
* Управление экспортом посимвольно:
__attribute__ ((visibility ("hidden")));
__attribute__ ((visibility ("hidden")))
* Для группы:
#pragma GCC visibility push(hidden)
...
#pragma GCC visibility pop
* static и анонимные namespace
* Управление эспортом через export map, через опцию --version-script