Получить начальный и конечный адрес текстового раздела в исполняемом файле - PullRequest
15 голосов
/ 10 сентября 2011

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

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

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

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

Ответы [ 4 ]

20 голосов
/ 10 сентября 2011

Скрипты компоновщика 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 до gcc), чтобы напечатать сценарий компоновщика, который он выберет для использования (на самом деле существует несколько различных сценариев компоновщика по умолчанию, которые различаются в зависимости от параметров компоновщика и типа создаваемого объекта).

7 голосов
/ 23 июня 2016

Неправильно говорить о «текстовом» сегменте, поскольку их может быть больше одного (гарантировано для обычного случая, когда у вас есть общие библиотеки, но все еще возможно, чтобы один двоичный файл 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 голосов
/ 19 января 2012

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

2 голосов
/ 10 сентября 2011
Не гарантируется, что

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

...