ELF重定位的应用顺序在哪里指定?

问题描述 投票:0回答:1

考虑 Linux 系统上的以下两个文件:

use_message.cpp

#include <iostream>

extern const char* message;
void print_message();

int main() {
    std::cout << message << '\n';
    print_message();
}

libmessage.cpp

#include <iostream>
const char* message = "Meow!";

void print_message() {
    std::cout << message << '\n';
}

我们可以将 use_message.cpp 编译为目标文件,将 libmessage.cpp 编译为共享库,并将它们链接在一起,如下所示:

$ g++ use_message.cpp -c -pie -o use_message.o
$ g++ libmessage.cpp -fPIC -shared -o libmessage.so
$ g++ use_message.o libmessage.so -o use_message

message
的定义最初位于libmessage.so中。当执行
use_message
时,动态链接器执行重定位:

  1. 用字符串数据的加载地址更新
    libmessage.so
    中的message定义
  2. message
    的定义从 libmessage.so 复制到 use_message
    .bss
    部分
  3. 更新libmessage.so中的全局偏移表以指向
    use_message
    中新版本的message

readelf
转储的相关搬迁是:

使用消息

  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000004150  000c00000005 R_X86_64_COPY     0000000000004150 message + 0

这是我之前写的列表中的第 2 号搬迁。

libmessage.so

  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000004040  000000000008 R_X86_64_RELATIVE                    2000
000000003fd8  000b00000006 R_X86_64_GLOB_DAT 0000000000004040 message + 0

这些分别是搬迁编号 1 和 3。

重定位编号 1 和 2 之间存在依赖关系:对 libmessage.so

message
定义的更新必须在该值复制到 use_message 之前进行,否则 use_message 将不会指向正确的位置。

我的问题是:申请搬迁的顺序是如何规定的? ELF 文件中是否有编码指定这一点?或者在 ABI 中?或者动态链接器只是期望计算出重定位本身之间的依赖关系,并确保写入给定内存地址的任何重定位都在从同一位置读取的任何重定位之前运行?静态链接器是否只输出重定位,以便可执行文件中的重定位始终可以在共享库重定位之后处理?

c++ elf abi relocation
1个回答
0
投票

我的问题是:申请搬迁的顺序是如何规定的? ELF 文件中是否有编码指定这一点?或者在 ABI 中?或者动态链接器只是期望计算出重定位本身之间的依赖关系,并确保写入给定内存地址的任何重定位都在从同一位置读取的任何重定位之前运行?静态链接器是否只输出重定位,以便可执行文件中的重定位始终可以在共享库重定位之后处理?

我认为重定位解决顺序没有由标准指定。 动态加载器定义顺序。为了支持复制重定位,主可执行文件最后被重定位。 链接器仅生成可执行链接的复制重定位(-no-pie/-pie),并且了解动态加载器语义。


有两部分:模块内的顺序和两个模块之间的顺序。

glibc rtld 以反向搜索顺序(反向 l_initfini)处理重定位,其中 rtld 本身有一个特殊情况。主可执行文件需要最后处理才能处理 R_*_COPY。如果A有一个ifunc引用B,一般B需要在A之前重定位。没有ifunc,共享对象的解析顺序可以是任意的。

假设我们有以下依赖树。

main
  dep1.so
    dep2.so
      dep3.so
        libc.so.6
      dep4.so
        dep3.so
        libc.so.6
    libc.so.6
  libc.so.6

l_initfini 包含 main、dep1.so、dep2.so、dep4.so、dep3.so、libc.so.6、ld.so。重定位解析顺序为 ld.so (bootstrap)、libc.so.6、dep3.so、dep4.so、dep2.so、dep1.so、main、ld.so。

在模块内,glibc rtld 按顺序解析重定位。假设DT_RELA(.rela.dyn)和DT_PLTREL(.rela.plt)都存在,glibc逻辑如下:

// Simplified from elf/dynamic-link.h
ranges[0] = {DT_RELA, DT_RELASZ, 0};
ranges[1] = {DT_JMPREL, DT_PLTRELSZ, do_lazy};
if (!do_lazy && ranges[0].start + ranges[0].size == ranges[1].start) { // the equality operator is always satisfied in practice
  ranges[0].size += size;
  ranges[1] = {};
}
for (int ranges_index = 0; ranges_index < 2; ++ranges_index)
  elf_dynamic_do_Rela (... ranges[ranges_index]);

FreeBSD rtld 使用更复杂的顺序,这使得某些 ifunc 代码更加健壮。

顺便说一句,

use_message
(带有-fPIE可重定位文件)由于
HAVE_LD_PIE_COPYRELOC
而需要复制重定位。 对于 Clang 和 GCC 的其他架构,PIE 模式不会导致复制重定位。

$ g++ use_message.cpp -c -pie -o use_message.o
$ g++ libmessage.cpp -fPIC -shared -o libmessage.so
$ g++ use_message.o libmessage.so -o use_message
© www.soinside.com 2019 - 2024. All rights reserved.