Получить начальный и конечный адрес текстового раздела в исполняемом файле

Мне нужно получить начальный и конечный адрес исполняемого текстового раздела. Как я могу получить его?

Я могу получить начальный адрес из символа _init или _start, но как насчет конечного адреса? Должен ли я считать конечный адрес раздела text последним адресом перед началом раздела .rodata?

Или мне нужно изменить значение по умолчанию ld script и добавить свои собственные символы, чтобы указать начало и конец текстового раздела, и передать его в GCC при компиляции? В этом случае, где я должен поместить новые символы, я должен рассмотреть секцию init и fini?

Каков хороший способ получить начальный и конечный адрес текстового раздела?

Ответ 1

Стандартные скрипты компоновщика GNU binutils для платформ на базе ELF обычно определяют довольно много разных символов, которые можно использовать для поиска начала и конца различных разделов.

Конец текстового раздела обычно ссылается на выбор из трех разных символов: 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), хотя я предполагаю, что если вы делаете что-то, что нуждается в этой информации, и б) вы рассматриваете взломанные скрипты компоновщика в качестве опции, тогда переносимость не слишком большая беспокойство!

Чтобы увидеть точный набор символов, которые вы получаете при создании определенной вещи на конкретной платформе, дайте значку --verbose ld (или -Wl,--verbose to gcc) для печати компоновщика script it (существует действительно несколько различных сценариев компоновщика по умолчанию, которые различаются в зависимости от параметров компоновщика и типа объекта, который вы создаете).

Ответ 2

Неправильно говорить о "текстовом сегменте", поскольку может быть несколько (гарантировано для обычного случая, когда у вас есть общие библиотеки, но все же возможно, что для одного ELF-бинара есть несколько разделов PT_LOAD с все равно).

Следующая примерная программа выгружает всю информацию, возвращаемую dl_iterate_phr. Вас интересует любой сегмент типа PT_LOAD с флагом PF_X (обратите внимание, что 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

.rodata не гарантируется, что он всегда появляется сразу после .text. Вы можете использовать objdump -h file и readelf --sections file для получения дополнительной информации. С objdump вы получаете размер и смещение в файл.

Ответ 4

Для Linux рассмотрите возможность использования инструмента nm(1) для проверки того, какие символы предоставляет объектный файл. Вы можете выбрать этот набор символов, где вы могли бы узнать оба символа, которые предоставил Матвей Слейтер в своем ответе.