【问题标题】:Get the start and end address of text section in an executable获取可执行文件中文本部分的开始和结束地址
【发布时间】:2011-11-14 06:51:24
【问题描述】:

我需要获取可执行文件文本部分的开始和结束地址。我怎样才能得到它?

我可以从_init 符号或_start 符号中获取起始地址,但是结束地址呢?我是否应该将text 部分的结束地址视为.rodata 部分开始之前的最后一个地址?

或者我应该编辑默认的ld脚本并添加我自己的符号来指示文本部分的开始和结束,并在编译时将其传递给GCC?在这种情况下,我应该在哪里放置新符号,我应该考虑 init 和 fini 部分吗?

有什么好方法可以获取文本部分的开始和结束地址?

【问题讨论】:

标签: c gcc ld


【解决方案1】:

用于基于 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 是否为 all ELF -based 平台,或者提供的符号集是否在不同的 binutils 版本中发生了变化),尽管我猜如果 a)您正在做一些需要此信息的事情,并且 b)您正在考虑将黑客链接器脚本作为一个选项,然后是可移植性不用太担心!

要查看在特定平台上构建特定事物时获得的确切符号集,请将 --verbose 标志赋予 ld(或 -Wl,--verbosegcc)以打印它选择的链接器脚本使用(实际上有几种不同的默认链接器脚本,它们根据链接器选项和您正在构建的对象类型而有所不同)。

【讨论】:

  • 那会是更好的选择吗?破解链接器脚本并插入我自己的符号?
  • 不,如果这些符号在您的平台上有效,您不妨使用它们。上面的示例代码至少可以在 Linux x86、Linux ppc 和 NetBSD x86 上运行——我只是不知道是否还有其他平台无法运行。 (被黑的链接器脚本较少可移植:例如,被黑的 Linux x86 链接器脚本几乎肯定无法在 Linux ppc 上运行。)
【解决方案2】:

说“the”文本段是不正确的,因为可能有多个(在您拥有共享库时保证通常情况下,但单个 ELF 二进制文件仍然可能有多个 PT_LOAD 部分无论如何都是相同的标志)。

以下示例程序转储出dl_iterate_phr 返回的所有信息。您对带有PF_X 标志的PT_LOAD 类型的任何段感兴趣(请注意,如果-z execstack 被传递给链接器,则PT_GNU_STACK 将包含该标志,因此您确实必须同时检查两者)。

#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);
}

【讨论】:

    【解决方案3】:

    对于 Linux,考虑使用nm(1) 工具来检查目标文件提供的符号。您可以从这组符号中挑选,从中可以了解 Matthew Slattery 在他的回答中提供的两个符号。

    【讨论】:

      【解决方案4】:

      .rodata 不能保证总是紧跟在.text 之后。您可以使用objdump -h filereadelf --sections file 获取更多信息。使用 objdump,您可以获得文件的大小和偏移量。

      【讨论】:

        猜你喜欢
        • 2013-05-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-09
        • 2018-12-22
        相关资源
        最近更新 更多