Указатель void *
, возвращаемый dlopen(0, RTLD_LAZY)
, дает struct link_map *
, который соответствует основному исполняемому файлу.
Вызов dl_iterate_phdr
также возвращает запись для основного исполняемого файла на самое первое выполнение обратного вызова.
Вероятно, вас смущает тот факт, что .l_addr == 0
в карте ссылок и dlpi_addr == 0
при использовании dl_iterate_phdr
.
Этопроисходит, потому что l_addr
(и dlpi_addr
) фактически не записывают адрес загрузки изображения ELF.Вместо этого они записывают перемещение , которое было применено к этому образу.
Обычно основной исполняемый файл создается для загрузки в 0x400000
(для x86_64 Linux) или в 0x08048000
(дляix86 Linux) и загружаются по тому же адресу (т. е. они не перемещаются).
Но если вы свяжете свой исполняемый файл с флагом -pie
, то он будет связан с 0x0
, и он будет перемещен на какой-то другой адрес.
Так как же вы попадете в заголовок ELF?Легко:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <link.h>
#include <stdio.h>
#include <stdlib.h>
static int
callback(struct dl_phdr_info *info, size_t size, void *data)
{
int j;
static int once = 0;
if (once) return 0;
once = 1;
printf("relocation: 0x%lx\n", (long)info->dlpi_addr);
for (j = 0; j < info->dlpi_phnum; j++) {
if (info->dlpi_phdr[j].p_type == PT_LOAD) {
printf("a.out loaded at %p\n",
(void *) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr));
break;
}
}
return 0;
}
int
main(int argc, char *argv[])
{
dl_iterate_phdr(callback, NULL);
exit(EXIT_SUCCESS);
}
$ gcc -m32 t.c && ./a.out
relocation: 0x0
a.out loaded at 0x8048000
$ gcc -m64 t.c && ./a.out
relocation: 0x0
a.out loaded at 0x400000
$ gcc -m32 -pie -fPIC t.c && ./a.out
relocation: 0xf7789000
a.out loaded at 0xf7789000
$ gcc -m64 -pie -fPIC t.c && ./a.out
relocation: 0x7f3824964000
a.out loaded at 0x7f3824964000
Обновление:
Почему на странице руководства написано "базовый адрес", а не перемещение?
Это ошибка; -)
Я предполагаю, что справочная страница была написана задолго до того, как prelink
и pie
и ASLR
существовали.Без предварительной ссылки разделяемые библиотеки всегда связываются с загрузкой по адресу 0x0
, а затем relocation
и base address
становятся одним и тем же.
, почему dlpi_name указывает на пустую строку, когда информацияотносится к основному исполняемому файлу?
Это случайность реализации.
То, как это работает, заключается в том, что ядро open(2)
выполняет исполняемый файл и передает дескриптор открытого файла взагрузчик (в векторе auxv[]
, как AT_EXECFD
). Все загрузчик знает об исполняемом файле, который он получает, читая этот дескриптор файла.
В UNIX нет простого способа сопоставить дескриптор файла с именем, с которым он был открыт.Во-первых, UNIX поддерживает жесткие ссылки, и может быть несколько имен файлов, которые ссылаются на один и тот же файл.
Более новые ядра Linux также передают имя, которое использовалось для execve(2)
исполняемого файла (также в auxv[]
, а AT_EXECFN
).Но это необязательно, и даже когда оно передается, glibc не помещает его в .l_name
/ dlpi_name
, чтобы не разбивать существующие программы, которые стали зависимыми от пустого имени.
Вместо этогоglibc сохраняет это имя в __progname
и __progname_full
.
Загрузчик coud readlink(2)
имя из /proc/self/exe
в системах, которые не используют AT_EXECFN
, нофайловая система /proc
также не гарантируется для монтирования, поэтому иногда ее можно оставить с пустым именем.