// If --packed_dyn_relocs=relr was given, base relocations are stored // to a .relr.dyn section in a compressed form. Construct a compressed // relocations now so that we can fix section sizes and file layout. if (ctx.arg.pack_dyn_relocs_relr) construct_relr(ctx);
-z pack-relative-relocs Alias for –pack-dyn-relocs=relr -z nopack-relative-relocs
Relocation entries describe how to alter the following instruction and data fields (bit numbers appear in the lower box corners).
这里提前引用部分elf spec中提到的在i386中下面会用到的几种rel type的含义
R*_386_GLOB_DAT*
This relocation type is used to set a global offset table entry to the address of the specified symbol. The special relocation type allows one to determine the correspondence between symbols and global offset table entries.
R_386_RELATIVE
The link editor creates this relocation type for dynamic linking. Its offset member gives a location within a shared object that contains a value representing a relative address. The dynamic linker computes the corresponding virtual address by adding the virtual address at which the shared object was loaded to the relative address. Relocation entries for this type must specify 0 for the symbol table index.
// Get .got and .rel.dyn contents. // // .got is a linker-synthesized constant pool whose entry is of pointer // size. If we know a correct value for an entry, we'll just set that value // to the entry. Otherwise, we'll create a dynamic relocation and let the // dynamic linker to fill the entry at load-time. // // Most GOT entries contain addresses of global variable. If a global // variable is an imported symbol, we don't know its address until runtime. // GOT contains the addresses of such variables at runtime so that we can // access imported global variables via GOT. // // Thread-local variables (TLVs) also use GOT entries. We need them because // TLVs are accessed in a different way than the ordinary global variables. // Their addresses are not unique; each thread has its own copy of TLVs. template <typename E> static std::vector<GotEntry<E>> get_got_entries(Context<E> &ctx) { std::vector<GotEntry<E>> entries;
// Create GOT entries for ordinary symbols for (Symbol<E> *sym : ctx.got->got_syms) { i64 idx = sym->get_got_idx(ctx);
// If a symbol is imported, let the dynamic linker to resolve it. if (sym->is_imported) { entries.push_back({idx, 0, E::R_GLOB_DAT, sym}); continue; }
// IFUNC always needs to be fixed up by the dynamic linker. if (sym->is_ifunc()) { entries.push_back({idx, sym->get_addr(ctx, NO_PLT), E::R_IRELATIVE}); continue; }
// If we know an address at link-time, fill that GOT entry now. // It may need a base relocation, though. if (ctx.arg.pic && sym->is_relative()) entries.push_back({idx, sym->get_addr(ctx, NO_PLT), E::R_RELATIVE}); else entries.push_back({idx, sym->get_addr(ctx, NO_PLT)}); }
// Create GOT entries for TLVs. for (Symbol<E> *sym : ctx.got->tlsgd_syms) { i64 idx = sym->get_tlsgd_idx(ctx);
if (ctx.arg.is_static) { entries.push_back({idx, 1}); // One indicates the main executable file entries.push_back({idx + 1, sym->get_addr(ctx) - ctx.dtp_addr}); } else { entries.push_back({idx, 0, E::R_DTPMOD, sym}); entries.push_back({idx + 1, 0, E::R_DTPOFF, sym}); } }
ifconstexpr(supports_tlsdesc<E>){ for (Symbol<E> *sym : ctx.got->tlsdesc_syms) { // _TLS_MODULE_BASE_ is a linker-synthesized virtual symbol that // refers the begining of the TLS block. if (sym == ctx._TLS_MODULE_BASE_) entries.push_back({sym->get_tlsdesc_idx(ctx), 0, E::R_TLSDESC}); else entries.push_back({sym->get_tlsdesc_idx(ctx), 0, E::R_TLSDESC, sym}); } }
for (Symbol<E> *sym : ctx.got->gottp_syms) { i64 idx = sym->get_gottp_idx(ctx);
// If we know nothing about the symbol, let the dynamic linker // to fill the GOT entry. if (sym->is_imported) { entries.push_back({idx, 0, E::R_TPOFF, sym}); continue; }
// If we know the offset within the current thread vector, // let the dynamic linker to adjust it. if (ctx.arg.shared) { entries.push_back({idx, sym->get_addr(ctx) - ctx.tls_begin, E::R_TPOFF}); continue; }
// Otherwise, we know the offset from the thread pointer (TP) at // link-time, so we can fill the GOT entry directly. entries.push_back({idx, sym->get_addr(ctx) - ctx.tp_addr}); }
if (ctx.got->tlsld_idx != -1) { if (ctx.arg.is_static) entries.push_back({ctx.got->tlsld_idx, 1}); // 1 means the main executable else entries.push_back({ctx.got->tlsld_idx, 0, E::R_DTPMOD}); }
// `value` contains symbol value. If it's an absolute symbol, it is // equivalent to its address. If it belongs to an input section or a // section fragment, value is added to the base of the input section // to yield an address. // u64 value = 0;
// `value` contains symbol value. If it's an absolute symbol, it is // equivalent to its address. If it belongs to an input section or a // section fragment, value is added to the base of the input section // to yield an address. // u64 value = 0;
template <typename E> inline u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const { if (SectionFragment<E> *frag = get_frag()) { if (!frag->is_alive) { // This condition is met if a non-alloc section refers an // alloc section and if the referenced piece of data is // garbage-collected. Typically, this condition occurs if a // debug info section refers a string constant in .rodata. return0; }
return frag->get_addr(ctx) + value; }
if (has_copyrel) { return copyrel_readonly ? ctx.copyrel_relro->shdr.sh_addr + value : ctx.copyrel->shdr.sh_addr + value; }
InputSection<E> *isec = get_input_section(); if (!isec) return value; // absolute symbol
if (!isec->is_alive) { if (isec->is_killed_by_icf()) return isec->leader->get_addr() + value;
if (isec->name() == ".eh_frame") { // .eh_frame contents are parsed and reconstructed by the linker, // so pointing to a specific location in a source .eh_frame // section doesn't make much sense. However, CRT files contain // symbols pointing to the very beginning and ending of the section. if (name() == "__EH_FRAME_BEGIN__" || name() == "__EH_FRAME_LIST__" || name() == ".eh_frame_seg" || esym().st_type == STT_SECTION) return ctx.eh_frame->shdr.sh_addr;
// ARM object files contain "$d" local symbol at the beginning // of data sections. Their values are not significant for .eh_frame, // so we just treat them as offset 0. if (name() == "$d" || name().starts_with("$d.")) return ctx.eh_frame->shdr.sh_addr;
Fatal(ctx) << "symbol referring .eh_frame is not supported: " << *this << " " << *file; }
// The control can reach here if there's a relocation that refers // a local symbol belonging to a comdat group section. This is a // violation of the spec, as all relocations should use only global // symbols of comdat members. However, .eh_frame tends to have such // relocations. return0; }
return isec->get_addr() + value; }
dynsym finalize
1 2 3 4
// Reserve a space for dynamic symbol strings in .dynstr and sort // .dynsym contents if necessary. Beyond this point, no symbol will // be added to .dynsym. ctx.dynsym->finalize(ctx);
// Sort symbols. In any symtab, local symbols must precede global symbols. auto first_global = std::stable_partition(symbols.begin() + 1, symbols.end(), [&](Symbol<E> *sym) { return sym->is_local(ctx); });
// We also place undefined symbols before defined symbols for .gnu.hash. // Defined symbols are sorted by their hashes for .gnu.hash. if (ctx.gnu_hash) { // Count the number of exported symbols to compute the size of .gnu.hash. i64 num_exported = 0; for (i64 i = 1; i < symbols.size(); i++) if (symbols[i]->is_exported) num_exported++;