正在获取主可执行文件的ELF标头
Getting the ELF header of the main executable
出于各种目的,我试图在不解析/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("%pn", header);
/* Read the header and do stuff, etc */
return 0;
}
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_64Linux)或0x08048000
(对于ix86Linux)加载,并在同一地址加载(即,它们不重新定位)。
但是,如果您将可执行文件与-pie
标志链接,那么它将在0x0
,处链接,并且它将被重新定位到其他地址。
那么,如何到达ELF标题?
2023更新:
一个更简单的方法(如果依赖于未记录的详细信息),只是在
struct link_map
中的l_ld
地址上调用dladdr
,然后使用其中的dli_fbase
吗Simon Kissane
的确如此。这里有一个简单得多的解决方案:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
#include <stdio.h>
int main()
{
void *dyn = _DYNAMIC;
Dl_info info;
if (dladdr(dyn, &info) != 0) {
printf("a.out loaded at %pn", info.dli_fbase);
}
return 0;
}
gcc -g -Wall -Wextra x.c -ldl && ./a.out
a.out loaded at 0x556433ea0000 # high address here because my GCC defaults to PIE.
gcc -g -Wall -Wextra x.c -ldl -no-pie && ./a.out
a.out loaded at 0x400000
gcc -g -Wall -Wextra x.c -ldl -no-pie -m32 && ./a.out
a.out loaded at 0x8048000
2012年的原始解决方案:
#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%lxn", (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 %pn",
(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
更新:
为什么手册页上写着";基地址";而不是搬迁?
这是一个bug;-)
我猜测手册页早在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
中。
在未使用AT_EXECFN
的系统上,加载程序coudreadlink(2)
的名称来自/proc/self/exe
,但也不能保证安装/proc
文件系统,因此有时仍会留下一个空名称。
有一个glibc dl_iterate_phdr()函数。我不确定它是否能满足你的需求,但据我所知:
"dl_iterate_phdr()函数允许应用程序在运行时查询,以找出它加载了哪些共享对象。"http://linux.die.net/man/3/dl_iterate_phdr
- g++ 说函数不存在,即使包含正确的标头
- spdlog标头仅与外部fmt一起使用.spdlog错误:'内部':不是'fmt'
- 在CMake中使用find_package时,是否会显式包含标头
- 错误"Could not find Boost"(缺少:上下文标头)
- 如何在标头中声明(或定义)函数的问题
- 如何避免在仅标头库中C++类/变量重定义
- C++算法标头中,为什么要使用 "!(val < *first)" ?
- C++标头错误 C2238 意外标记";"
- std::initializer_list,大括号初始化和标头
- 在 c++ 中拆分类和标头中的继承,错误
- 如何确定哪个标头调用 c++ 中的另一个标头
- 如何在生成文件中添加多个标头 (HDR) 和对象?
- 我可以在运行时重新定义在 OpenCascade/OCCT 标头中定义的 c++ 静态常量吗?
- GCC,CMake,预编译标头和维护依赖项
- VisualStudio:使用 Suse Enterprise Server 12 SP5 时,不会下载远程库标头
- 一个标头库中的错误
- 可视化 Bazel C++预编译标头实现
- 如何在 CMake 中添加预编译标头用于 Visual Studio 生成器
- 使用 GN 构建预编译标头
- 正在获取主可执行文件的ELF标头