出于各种目的,我试图在不解析/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;
}
答案 0 :(得分:17)
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
会变为同一个。
当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