获取主可执行文件的 ELF 标头
出于各种目的,我试图在不解析 /proc/self/maps
的情况下获取主可执行文件的 ELF 标头的地址。我尝试解析由 dlopen
/dlinfo
函数给出的 link_list
链,但它们不包含 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;
}
For various purposes, I am trying to obtain the address of the ELF header of the main executable without parsing /proc/self/maps
. I have tried parsing the link_list
chain given by dlopen
/dlinfo
functions but they do not contain an entry where l_addr
points to the base address of the main executable. Is there any way to do this (Standard or not) without parsing /proc/self/maps
?
An example of what I'm trying to do:
#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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
dlopen(0, RTLD_LAZY)
返回的void *
指针为您提供一个struct link_map *
,它对应于主可执行文件。调用
dl_iterate_phdr
还会在第一次执行回调时返回主可执行文件的条目。您可能会对链接映射中的
.l_addr == 0
以及使用dl_iterate_phdr
时的dlpi_addr == 0
感到困惑。发生这种情况是因为
l_addr
(和dlpi_addr
)实际上并不记录 ELF 图像的加载地址。相反,它们记录已应用于该图像的重定位。通常,主可执行文件被构建为在
0x400000
(对于 x86_64 Linux)或0x08048000
(对于 ix86 Linux)加载,并且加载在同一地址(即它们不是搬迁)。但是,如果您使用
-pie
标志链接可执行文件,那么它将被链接到0x0
,并且它将被重新定位到其他地址。那么如何到达 ELF 头呢?
2023 年更新:
确实如此。这是更简单的解决方案:
2012 年原始解决方案:
更新:
这是一个错误;-)
我猜测手册页是在
prelink
和pie
以及ASLR
存在之前编写的。如果没有预链接,共享库总是链接到地址0x0
加载,然后重定位
和基地址
变得一模一样。这是执行过程中的一个意外。
其工作方式是,内核
open(2)
执行可执行文件并将打开的文件描述符传递给加载器(在auxv[]
向量中,如AT_EXECFD
)。加载程序通过读取该文件描述符所获取的可执行文件的所有信息。在 UNIX 上没有简单的方法可以将文件描述符映射回其打开时的名称。一方面,UNIX 支持硬链接,并且可能有多个文件名引用同一文件。
较新的 Linux 内核还传入用于
execve(2)
可执行文件的名称(也在auxv[]
中,如AT_EXECFN
)。但这是可选的,即使它被传入,glibc 也不会将其放入.l_name
/dlpi_name
中,以免破坏依赖于名称为空。相反,glibc 将该名称保存在
__progname
和__progname_full
中。加载程序可以
readlink(2)
未使用AT_EXECFN
的系统上的/proc/self/exe
名称code>,但/proc
文件系统也不能保证被挂载,因此有时仍然会留下一个空名称。The
void *
pointer returned bydlopen(0, RTLD_LAZY)
gives you astruct link_map *
, that corresponds to the main executable.Calling
dl_iterate_phdr
also returns the entry for the main executable on the very first execution of callback.You are likely confused by the fact that
.l_addr == 0
in the link map, and thatdlpi_addr == 0
when usingdl_iterate_phdr
.This is happening, because
l_addr
(anddlpi_addr
) don't actually record the load address of an ELF image. Rather, they record the relocation that has been applied to that image.Usually the main executable is built to load at
0x400000
(for x86_64 Linux) or at0x08048000
(for ix86 Linux), and are loaded at that same address (i.e. they are not relocated).But if you link your executable with
-pie
flag, then it will be linked-at0x0
, and it will be relocated to some other address.So how do you get to the ELF header?
2023 Update:
Indeed it is. Here is much simpler solution:
Original 2012 solution:
Update:
It's a bug ;-)
I am guessing that the man page was written long before
prelink
andpie
, andASLR
existed. Without prelink, shared libraries are always linked to load at address0x0
, and thenrelocation
andbase address
become one and the same.It's an accident of implementation.
The way this works, is that the kernel
open(2)
s the executable and passes the open file descriptor to the loader (in theauxv[]
vector, asAT_EXECFD
). Everything the loader knows about the executable it gets by reading that file descriptor.There is no easy way on UNIX to map a file descriptor back to the name it was opened as. For one thing, UNIX supports hard-links, and there could be multiple filenames that refer to the same file.
Newer Linux kernels also pass in the name that was used to
execve(2)
the executable (also inauxv[]
, asAT_EXECFN
). But that is optional, and even when it is passed in, glibc doesn't put it into.l_name
/dlpi_name
in order to not break existing programs which became dependent on the name being empty.Instead, glibc saves that name in
__progname
and__progname_full
.The loader coud
readlink(2)
the name from/proc/self/exe
on systems that didn't useAT_EXECFN
, but the/proc
file system is not guaranteed to be mounted either, so that would still leave it with an empty name sometimes.有 glibc dl_iterate_phdr() 函数。我不确定它是否完全满足您的需求,但据我所知,这很接近:
“dl_iterate_phdr() 函数允许应用程序在运行时查询以找出它已加载的共享对象。”
http://linux.die.net/man/3/dl_iterate_phdr
There is the glibc dl_iterate_phdr() function. I'm not sure it gives you exactly what you want, but that is as close as I know:
"The dl_iterate_phdr() function allows an application to inquire at run time to find out which shared objects it has loaded."
http://linux.die.net/man/3/dl_iterate_phdr