如果在同一翻译单元中调用函数,为什么需要重定位

cst*_*fel 6 c++ linker g++ elf linkage

因此,我有两个文件,一个是我的库,另一个是主要的prog可执行文件。图书馆:

static int internal1(int a, int b){
  return a + b;
}

namespace {
  int internal2(int a, int b){
    return a + b;
  }
}

void external2(int qq, int zz){

}

void external(int a, int b){
  external2(a, b);
  internal1(a, b);
  internal2(a, b);
}
Run Code Online (Sandbox Code Playgroud)

g++ -c -O0 -fPIC -o libtest.o libtest.cpp和 编译 g++ -shared -o libtest.so libtest.o

主要编:

extern void external(int a, int b);

int main(){
  external(1, 2);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译与 g++ -O0 -L. -ltest -o tester tester.cpp

现在,如果我为我转储了重定位信息,则会tester得到我期望的结果:

Relocation section '.rela.dyn' at offset 0x4d0 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000600a48  000100000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

Relocation section '.rela.plt' at offset 0x4e8 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000600a68  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
000000600a70  000400000007 R_X86_64_JUMP_SLO 0000000000000000 _Z8externalii + 0
000000600a78  000a00000007 R_X86_64_JUMP_SLO 0000000000400578 __gxx_personality_v0 + 0
Run Code Online (Sandbox Code Playgroud)

外部地址在重定位列表中,因为它必须找到地址并将其放入。

但是我不明白的是,当我转储共享对象的重定位列表时,为什么在共享对象的重定位列表上看到external2。为什么它不像内部链接功能那样自动输入地址。

Relocation section '.rela.dyn' at offset 0x460 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
0000002007d8  000000000008 R_X86_64_RELATIVE                    00000000002007d8
000000200990  000200000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000200998  000300000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0
0000002009a0  000400000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize + 0
0000002009d0  000500000001 R_X86_64_64       0000000000000000 __gxx_personality_v0 + 0

Relocation section '.rela.plt' at offset 0x4d8 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
0000002009c0  000400000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize + 0
0000002009c8  000600000007 R_X86_64_JUMP_SLO 0000000000000646 _Z9external2ii + 0
Run Code Online (Sandbox Code Playgroud)

调用internal1并且internal2不需要重定位,为什么external2作为一个外部符号意味着即使该符号位于同一翻译单元中,也必须通过GOT进行查找?为什么它不能像internals 一样执行普通的偏移调用

Fra*_*ank 5

出于好奇,让我们看看当您尝试仅启用(而不是链接)启用了大多数优化功能的相同代码时,编译器会做什么。这通常会暴露出编译器被束缚的情况。另外,编译器在方面可以很随意/懒散O0,因此避免过多地阅读它。

libtest.cpp-O3 -fPIC -c收益编译:

external2(int, int):
        ret
external(int, int):
        jmp     external2(int, int)@PLT
Run Code Online (Sandbox Code Playgroud)

在Godbolt上查看:https ://gcc.godbolt.org/z/CnRVEX

这是非常有趣:GCC可以明显地告诉大家,external2()是一个空操作,但它仍然称之为O3

我们可以从中得出什么结论?调用external2()不一定会执行TU的TU版本中的代码external2()。但这怎么可能呢?ODR应该允许我们假设external2()同一二进制文件中的任何一个都比该TU中的二进制文件更糟。

在C ++级别上确实如此,但是Linux不会加载C ++代码。它加载精灵,它们按照不同的规则进行游戏。这些规则之一是,您可以用来LD_PRELOAD在可执行文件之前加载符号以拦截它们。并且通过在PIC代码中将符号置于外部,链接器将其解释为可重载的符号,从而防止了内联(在我的示例中)以及局部跳转(在您的示例中)。