【问题标题】:Base Address of ELFELF的基地址
【发布时间】:2013-08-20 05:10:15
【问题描述】:

我正在尝试查找 ELF 文件的基地址。我知道您可以使用 readelf 来查找程序入口点和不同的部分详细信息(基地址、大小、标志等)。

例如,x86 架构的程序由链接器基于 0x8048000。使用 readelf 我可以看到程序入口点,但输出中没有特定字段告诉基地址。

$ readelf -e test
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048390
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4436 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         30

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000024 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481d0 0001d0 000070 10   A  6   1  4

在 section details 中,我可以看到 Offset 是相对于 ELF 的基地址计算的。

所以,.dynsym 部分从地址 0x080481d0 开始,偏移量为 0x1d0。这意味着基地址是 0x08048000。这是正确的吗?

类似地,对于在 PPC、ARM、MIPS 等不同架构上编译的程序,我看不到它们的基地址,而只能看到 OEP、Section Headers。

【问题讨论】:

标签: linux elf


【解决方案1】:

.text部分的ELF映射基地址由binutils项目中的ld(1)加载器脚本在脚本模板elf.sc上定义Linux。

脚本定义了加载器使用的以下变量ld(1)

#       TEXT_START_ADDR - the first byte of the text segment, after any
#               headers.
#       TEXT_BASE_ADDRESS - the first byte of the text segment.
#       TEXT_START_SYMBOLS - symbols that appear at the start of the
#               .text section.

您可以使用以下命令检查当前值:

~$ ld --verbose |grep SEGMENT_START
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
  . = SEGMENT_START("ldata-segment", .);

文本段映射值为:

  • 0x08048000 32 位
  • 0x400000 64 位

ELF 程序的解释器基地址也定义在索引 AT_BASE 处的辅助向量数组中。辅助向量数组是 Elf_auxv_t 结构的数组,位于进程堆栈中的 envp 之后。它是在 Linux 内核 fs/binfmt_elf.c 的函数 create_elf_tables() 中加载 ELF 二进制文件时配置的。以下代码 sn -p 显示如何读取该值:

$ cat at_base.c
#include <stdio.h>                                                              
#include <elf.h>                                                                

int                                                                             
main(int argc, char* argv[], char* envp[])                                      
{                                                                               
        Elf64_auxv_t *auxp;                                                      
        while(*envp++ != NULL);                                                 

        for (auxp = (Elf64_auxv_t *)envp; auxp->a_type != 0; auxp++) {
            if (auxp->a_type == 7) {
                printf("AT_BASE: 0x%lx\n", auxp->a_un.a_val);
            }
        }

}
$ clang -o at_base at_base.c
$ ./at_base
AT_BASE: 0x7fcfd4025000

Linux Auxiliary Vector definition Auxiliary Vector Reference

它曾经是 x86 32 位架构上的固定地址,但现在使用 ASLR,它是随机的。如果需要,您可以使用setarch i386 -R 来禁用随机化。

【讨论】:

    【解决方案2】:

    它在链接描述文件中定义。您可以使用ld --verbose 转储默认链接描述文件。示例输出:

    GNU ld (GNU Binutils) 2.23.1
      Supported emulations:
       elf_x86_64
       elf32_x86_64
       elf_i386
       i386linux
       elf_l1om
       elf_k1om
    using internal linker script:
    ==================================================
    /* Script for -z combreloc: combine and sort reloc sections */
    OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
              "elf64-x86-64")
    OUTPUT_ARCH(i386:x86-64)
    ENTRY(_start)
    SEARCH_DIR("/nix/store/kxf1p7l7lgm6j5mjzkiwcwzc98s9f1az-binutils-2.23.1/x86_64-unknown-linux-gnu/lib64"); SEARCH_DIR("/nix/store/kxf1p7l7lgm6j5mjzkiwcwzc98s9f1az-binutils-2.23.1/lib64"); SEARCH_DIR("/nix/store/kxf1p7l7lgm6j5mjzkiwcwzc98s9f1az-binutils-2.23.1/x86_64-unknown-linux-gnu/lib"); SEARCH_DIR("/nix/store/kxf1p7l7lgm6j5mjzkiwcwzc98s9f1az-binutils-2.23.1/lib");
    SECTIONS
    {
      /* Read-only sections, merged into text segment: */
      PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
      .interp         : { *(.interp) }
      .note.gnu.build-id : { *(.note.gnu.build-id) }
      .hash           : { *(.hash) }
      .gnu.hash       : { *(.gnu.hash) }
      .dynsym         : { *(.dynsym) }
      .dynstr         : { *(.dynstr) }
      .gnu.version    : { *(.gnu.version) }
      .gnu.version_d  : { *(.gnu.version_d) }
      .gnu.version_r  : { *(.gnu.version_r) }
    

    (截图)

    如果你错过了:__executable_start = SEGMENT_START("text-segment", 0x400000))

    对我来说,果然,当我将一个简单的 .o 文件链接到二进制文件时,入口点地址非常接近 0x400000。

    ELF 元数据中的入口点地址就是这个值,加上从.text 部分开头到_start 符号的偏移量。另请注意,可以配置 _start 符号。同样来自我的默认链接器脚本示例:ENTRY(_start)

    【讨论】:

    • 错误答案! In case you missed it: __executable_start = SEGMENT_START("text-segment", 0x400000)) 我有PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x20000)); . = SEGMENT_START("text-segment", 0x20000); 虽然有一个从 0x20000 开始的操作码,但没有什么看起来像 ᴇʟꜰ 文件。当然,我的可执行文件不是用 fpic 编译的,而是静态链接的。它在 Linux ᴀᴍᴅ64 上运行。
    【解决方案3】:

    您需要检查段表,即程序头 (readelf -l)。

    Elf file type is EXEC (Executable file)
    Entry point 0x804a7a0
    There are 9 program headers, starting at offset 52
    
    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      PHDR           0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
      INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
          [Requesting program interpreter: /lib/ld-linux.so.2]
      LOAD           0x000000 0x08048000 0x08048000 0x10fc8 0x10fc8 R E 0x1000
      LOAD           0x011000 0x08059000 0x08059000 0x0038c 0x01700 RW  0x1000
      DYNAMIC        0x01102c 0x0805902c 0x0805902c 0x000f8 0x000f8 RW  0x4
      NOTE           0x000168 0x08048168 0x08048168 0x00020 0x00020 R   0x4
      TLS            0x011000 0x08059000 0x08059000 0x00000 0x0005c R   0x4
      GNU_EH_FRAME   0x00d3c0 0x080553c0 0x080553c0 0x00c5c 0x00c5c R   0x4
      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
    

    第一个(最低的)LOAD 段的虚拟地址是文件的默认加载基础。你可以看到这个文件是 0x08048000。

    【讨论】:

      猜你喜欢
      • 2017-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-24
      • 1970-01-01
      • 2011-09-07
      • 2020-11-28
      相关资源
      最近更新 更多