Получение заголовка ELF основного исполняемого файла - PullRequest
11 голосов
/ 16 января 2012

Для различных целей я пытаюсь получить адрес заголовка ELF основного исполняемого файла без разбора /proc/self/maps.Я попытался проанализировать цепочку link_list, заданную функциями dlopen / dlinfo, но они не содержат записи, где l_addr указывает на базовый адрес основного исполняемого файла.Есть ли способ сделать это (стандартный или нет) без разбора /proc/self/maps?

Пример того, что я пытаюсь сделать:

#include <stdio.h>
#include <elf.h>
int main()
{
    Elf32_Ehdr* header = /* Somehow obtain the address of the ELF header of this program */;
    printf("%p\n", header);
    /* Read the header and do stuff, etc */
    return 0;
}

Ответы [ 2 ]

18 голосов
/ 16 января 2012

Указатель void *, возвращаемый dlopen(0, RTLD_LAZY), дает struct link_map *, который соответствует основному исполняемому файлу.

Вызов dl_iterate_phdr также возвращает запись для основного исполняемого файла на самое первое выполнение обратного вызова.

Вероятно, вас смущает тот факт, что .l_addr == 0 в карте ссылок и dlpi_addr == 0 при использовании dl_iterate_phdr.

Этопроисходит, потому что l_addrdlpi_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 также не гарантируется для монтирования, поэтому иногда ее можно оставить с пустым именем.

1 голос
/ 16 января 2012

Есть функция glibc dl_iterate_phdr (). Я не уверен, что это дает вам именно то, что вы хотите, но это настолько близко, насколько я знаю:

"Функция dl_iterate_phdr () позволяет приложению запрашивать во время выполнения, чтобы выяснить, какие общие объекты оно загрузило." http://linux.die.net/man/3/dl_iterate_phdr

...