获取主可执行文件的ELF头

时间:2012-01-16 04:48:43

标签: c++ c linux elf

出于各种目的,我试图在不解析/proc/self/maps的情况下获取主可执行文件的ELF头的地址。我尝试解析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 个答案:

答案 0 :(得分:17)

void *返回的dlopen(0, RTLD_LAZY)指针为您提供struct link_map *,与主可执行文件对应。

调用dl_iterate_phdr也会在第一次执行回调时返回主可执行文件的条目。

您可能会对链接映射中.l_addr == 0和使用dlpi_addr == 0dl_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

更新

  

为什么手册页会说“基地址”而不是重定位?

这是一个错误; - )

我猜这个手册页是在prelinkpie以及ASLR存在之前写的。如果没有预链接,共享库始终会链接到地址0x0处的加载,然后relocationbase address会变为同一个。

  

当info引用主可执行文件时,dlpi_name如何指向空字符串?

实施意外。

这种方式的工作原理是内核open(2)是可执行文件,并将打开的文件描述符传递给加载器(在auxv[]向量中,作为AT_EXECFD)。 Everything 加载器通过读取该文件描述符来了解它所获得的可执行文件。

在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 :(得分:1)

有glibc dl_iterate_phdr()函数。我不确定它会给你你想要的东西,但这就像我所知道的那样接近:

“dl_iterate_phdr()函数允许应用程序在运行时查询以找出它已加载的共享对象。” http://linux.die.net/man/3/dl_iterate_phdr