我需要获取可执行文本部分的开始和结束地址。我怎么能得到它?
我可以从_init
符号或_start
符号获取起始地址,但结束地址呢?在开始text
部分之前,我应该将.rodata
部分的结束地址视为最后一个地址吗?
或者我应该编辑默认的ld脚本并添加我自己的符号来指示文本部分的开头和结尾,并在编译时将其传递给GCC?在这种情况下,我应该在哪里放置新符号,我应该考虑init和fini部分吗?
获取文本部分的开始和结束地址有什么好方法?
答案 0 :(得分:20)
基于ELF的平台的GNU binutils默认链接描述文件通常定义了许多不同的符号,可用于查找各个部分的开头和结尾。
文本部分的结尾通常由三个不同符号选择引用:etext
,_etext
或__etext
;开头可以找到__executable_start
。 (请注意,这些符号通常使用PROVIDE()机制导出,这意味着如果您的可执行文件中的其他内容定义它们,而不仅仅是引用,它们将被覆盖特别是这意味着_etext
或__etext
可能比etext
更安全。)
示例:
$ cat etext.c
#include <stdio.h>
extern char __executable_start;
extern char __etext;
int main(void)
{
printf("0x%lx\n", (unsigned long)&__executable_start);
printf("0x%lx\n", (unsigned long)&__etext);
return 0;
}
$ gcc -Wall -o etext etext.c
$ ./etext
0x8048000
0x80484a0
$
我不相信这些符号中的任何符号都是由任何标准指定的,因此不应该认为这是可移植的(我不知道甚至GNU binutils是否为所有 ELF提供它们基于平台,或者提供的符号集是否已经在不同的binutils版本上发生了变化),尽管我猜是否a)你正在做一些需要这些信息的事情,并且b)你正在考虑将被黑客的链接器脚本作为选项,然后可移植性不是太在意了!
要查看在特定平台上构建特定内容时获得的确切符号集,请将--verbose
标记提供给ld
(或-Wl,--verbose
至gcc
)打印它选择使用的链接描述文件(实际上有几个不同的默认链接描述文件,根据链接器选项和你正在构建的对象类型而不同)。
答案 1 :(得分:7)
说&#34;&#34;&#34;文本段,因为可能有多个(保证通常情况下,当您有共享库时,但单个ELF二进制文件仍然可能有多个PT_LOAD
部分具有相同的标记)
以下示例程序会转储dl_iterate_phr
返回的所有信息。您对PT_LOAD
类型的PF_X
标记段感兴趣(注意PT_GNU_STACK
将包含标记-z execstack
传递给链接器,所以你真的要检查两者。)
#define _GNU_SOURCE
#include <link.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
const char *type_str(ElfW(Word) type)
{
switch (type)
{
case PT_NULL:
return "PT_NULL"; // should not be seen at runtime, only in the file!
case PT_LOAD:
return "PT_LOAD";
case PT_DYNAMIC:
return "PT_DYNAMIC";
case PT_INTERP:
return "PT_INTERP";
case PT_NOTE:
return "PT_NOTE";
case PT_SHLIB:
return "PT_SHLIB";
case PT_PHDR:
return "PT_PHDR";
case PT_TLS:
return "PT_TLS";
case PT_GNU_EH_FRAME:
return "PT_GNU_EH_FRAME";
case PT_GNU_STACK:
return "PT_GNU_STACK";
case PT_GNU_RELRO:
return "PT_GNU_RELRO";
case PT_SUNWBSS:
return "PT_SUNWBSS";
case PT_SUNWSTACK:
return "PT_SUNWSTACK";
default:
if (PT_LOOS <= type && type <= PT_HIOS)
{
return "Unknown OS-specific";
}
if (PT_LOPROC <= type && type <= PT_HIPROC)
{
return "Unknown processor-specific";
}
return "Unknown";
}
}
const char *flags_str(ElfW(Word) flags)
{
switch (flags & (PF_R | PF_W | PF_X))
{
case 0 | 0 | 0:
return "none";
case 0 | 0 | PF_X:
return "x";
case 0 | PF_W | 0:
return "w";
case 0 | PF_W | PF_X:
return "wx";
case PF_R | 0 | 0:
return "r";
case PF_R | 0 | PF_X:
return "rx";
case PF_R | PF_W | 0:
return "rw";
case PF_R | PF_W | PF_X:
return "rwx";
}
__builtin_unreachable();
}
static int callback(struct dl_phdr_info *info, size_t size, void *data)
{
int j;
(void)data;
printf("object \"%s\"\n", info->dlpi_name);
printf(" base address: %p\n", (void *)info->dlpi_addr);
if (size > offsetof(struct dl_phdr_info, dlpi_adds))
{
printf(" adds: %lld\n", info->dlpi_adds);
}
if (size > offsetof(struct dl_phdr_info, dlpi_subs))
{
printf(" subs: %lld\n", info->dlpi_subs);
}
if (size > offsetof(struct dl_phdr_info, dlpi_tls_modid))
{
printf(" tls modid: %zu\n", info->dlpi_tls_modid);
}
if (size > offsetof(struct dl_phdr_info, dlpi_tls_data))
{
printf(" tls data: %p\n", info->dlpi_tls_data);
}
printf(" segments: %d\n", info->dlpi_phnum);
for (j = 0; j < info->dlpi_phnum; j++)
{
const ElfW(Phdr) *hdr = &info->dlpi_phdr[j];
printf(" segment %2d\n", j);
printf(" type: 0x%08X (%s)\n", hdr->p_type, type_str(hdr->p_type));
printf(" file offset: 0x%08zX\n", hdr->p_offset);
printf(" virtual addr: %p\n", (void *)hdr->p_vaddr);
printf(" physical addr: %p\n", (void *)hdr->p_paddr);
printf(" file size: 0x%08zX\n", hdr->p_filesz);
printf(" memory size: 0x%08zX\n", hdr->p_memsz);
printf(" flags: 0x%08X (%s)\n", hdr->p_flags, flags_str(hdr->p_flags));
printf(" align: %zd\n", hdr->p_align);
if (hdr->p_memsz)
{
printf(" derived address range: %p to %p\n",
(void *) (info->dlpi_addr + hdr->p_vaddr),
(void *) (info->dlpi_addr + hdr->p_vaddr + hdr->p_memsz));
}
}
return 0;
}
int main(void)
{
dl_iterate_phdr(callback, NULL);
exit(EXIT_SUCCESS);
}
答案 2 :(得分:2)
.rodata
并不保证始终直接在.text
之后。您可以使用objdump -h file
和readelf --sections file
获取更多信息。使用objdump,您可以将大小和偏移量都记录到文件中。
答案 3 :(得分:2)
对于Linux,请考虑使用nm(1)
工具检查目标文件提供的符号。您可以选择这组符号,在那里您可以学习Matthew Slattery在其答案中提供的两个符号。