mirror of
https://github.com/rui314/mold.git
synced 2024-12-26 01:44:29 +03:00
parent
3102ab867c
commit
9cc309af75
2
Makefile
2
Makefile
@ -14,7 +14,7 @@ LIBS = -Wl,-as-needed -lcrypto -pthread -ltbb -lz -lxxhash -ldl
|
||||
OBJS = main.o object_file.o input_sections.o output_chunks.o \
|
||||
mapfile.o perf.o linker_script.o archive_file.o output_file.o \
|
||||
subprocess.o gc_sections.o icf.o symbols.o cmdline.o filepath.o \
|
||||
glob.o passes.o tar.o compress.o memory_mapped_file.o \
|
||||
glob.o passes.o tar.o compress.o memory_mapped_file.o relocatable.o \
|
||||
arch_x86_64.o arch_i386.o
|
||||
|
||||
PREFIX ?= /usr
|
||||
|
@ -310,6 +310,12 @@ void InputSection<I386>::scan_relocations(Context<I386> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_386_NONE) {
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
@ -324,9 +330,6 @@ void InputSection<I386>::scan_relocations(Context<I386> &ctx) {
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
case R_386_8:
|
||||
case R_386_16: {
|
||||
Action table[][4] = {
|
||||
|
@ -481,6 +481,12 @@ void InputSection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_X86_64_NONE) {
|
||||
rel_types[i] = R_NONE;
|
||||
continue;
|
||||
}
|
||||
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
@ -495,9 +501,6 @@ void InputSection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_NONE:
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
|
@ -37,6 +37,7 @@ Options:
|
||||
-l LIBNAME Search for a given library
|
||||
-m EMULATION Ignored
|
||||
-o FILE, --output FILE Set output filename
|
||||
-r, --relocatable Generate relocatable output
|
||||
-s, --strip-all Strip .symtab section
|
||||
-u SYMBOL, --undefined SYMBOL
|
||||
Force to resolve SYMBOL
|
||||
@ -525,6 +526,8 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.relax = true;
|
||||
} else if (read_flag(args, "no-relax")) {
|
||||
ctx.arg.relax = false;
|
||||
} else if (read_flag(args, "r") || read_flag(args, "relocatable")) {
|
||||
ctx.arg.relocatable = true;
|
||||
} else if (read_flag(args, "perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_flag(args, "stats")) {
|
||||
|
@ -310,6 +310,14 @@ Choose a target.
|
||||
Use \fIfile\fR as the output file name instead of the default name
|
||||
\fBa.out\fR.
|
||||
|
||||
.IP "\fB\-r\fR"
|
||||
.PD 0
|
||||
.IP "\fB\-\-relocatable\fR"
|
||||
.PD
|
||||
Instead of generating an executable or a shared object file, combine
|
||||
input object files to generate another object file that can be used as
|
||||
an input to a linker.
|
||||
|
||||
.IP "\fB\-s\fR"
|
||||
.PD 0
|
||||
.IP "\fB\-\-strip\-all\fR"
|
||||
|
5
main.cc
5
main.cc
@ -312,6 +312,11 @@ int do_main(int argc, char **argv) {
|
||||
std::vector<std::string_view> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ctx.arg.preload)
|
||||
try_resume_daemon(ctx);
|
||||
|
||||
|
23
mold.h
23
mold.h
@ -54,6 +54,13 @@ template <typename E> class Symbol;
|
||||
template <typename E> struct CieRecord;
|
||||
template <typename E> struct Context;
|
||||
template <typename E> struct FdeRecord;
|
||||
|
||||
template <typename E> class ROutputChunk;
|
||||
template <typename E> class ROutputEhdr;
|
||||
template <typename E> class ROutputShdr;
|
||||
template <typename E> class RStrtabSection;
|
||||
template <typename E> class RSymtabSection;
|
||||
|
||||
class Compressor;
|
||||
class TarFile;
|
||||
|
||||
@ -1223,6 +1230,13 @@ void gc_sections(Context<E> &ctx);
|
||||
template <typename E>
|
||||
void icf_sections(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// relocatable.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args);
|
||||
|
||||
//
|
||||
// mapfile.cc
|
||||
//
|
||||
@ -1424,6 +1438,7 @@ struct Context {
|
||||
bool print_map = false;
|
||||
bool quick_exit = true;
|
||||
bool relax = true;
|
||||
bool relocatable = false;
|
||||
bool repro = false;
|
||||
bool shared = false;
|
||||
bool stats = false;
|
||||
@ -1555,6 +1570,14 @@ struct Context {
|
||||
std::unique_ptr<NotePropertySection<E>> note_property;
|
||||
std::unique_ptr<ReproSection<E>> repro;
|
||||
|
||||
// For --relocatable
|
||||
std::vector<ROutputChunk<E> *> r_chunks;
|
||||
ROutputEhdr<E> *r_ehdr = nullptr;
|
||||
ROutputShdr<E> *r_shdr = nullptr;
|
||||
RStrtabSection<E> *r_shstrtab = nullptr;
|
||||
RStrtabSection<E> *r_strtab = nullptr;
|
||||
RSymtabSection<E> *r_symtab = nullptr;
|
||||
|
||||
u64 tls_begin = -1;
|
||||
u64 tls_end = -1;
|
||||
|
||||
|
@ -298,6 +298,8 @@ void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
|
||||
// Verify relocations.
|
||||
for (i64 i = 1; i < rels.size(); i++)
|
||||
@ -328,10 +330,17 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
assert(begin_offset <= rels[rel_begin].r_offset);
|
||||
|
||||
if (id == 0) {
|
||||
// This is CIE.
|
||||
cies.push_back(CieRecord<E>(ctx, *this, isec, begin_offset, rel_begin));
|
||||
} else {
|
||||
if (rel_begin == rel_idx)
|
||||
Fatal(ctx) << isec << ": FDE has no relocations";
|
||||
// This is FDE.
|
||||
if (rel_begin == rel_idx) {
|
||||
// FDE has no valid relocation, which means FDE is dead from
|
||||
// the beginning. Compilers usually don't create such FDE, but
|
||||
// `ld -r` tend to generate such dead FDEs.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rels[rel_begin].r_offset - begin_offset != 8)
|
||||
Fatal(ctx) << isec << ": FDE's first relocation should have offset 8";
|
||||
|
||||
@ -341,27 +350,28 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
|
||||
// Associate CIEs to FDEs.
|
||||
auto find_cie = [&](i64 offset) -> CieRecord<E> * {
|
||||
for (CieRecord<E> &cie : cies)
|
||||
if (cie.input_offset == offset)
|
||||
return &cie;
|
||||
for (i64 i = cies_begin; i < cies.size(); i++)
|
||||
if (cies[i].input_offset == offset)
|
||||
return &cies[i];
|
||||
Fatal(ctx) << isec << ": bad FDE pointer";
|
||||
};
|
||||
|
||||
for (FdeRecord<E> &fde : fdes) {
|
||||
i64 cie_offset = *(i32 *)(contents.data() + fde.input_offset + 4);
|
||||
fde.cie = find_cie(fde.input_offset + 4 - cie_offset);
|
||||
for (i64 i = fdes_begin; i < fdes.size(); i++) {
|
||||
i64 cie_offset = *(i32 *)(contents.data() + fdes[i].input_offset + 4);
|
||||
fdes[i].cie = find_cie(fdes[i].input_offset + 4 - cie_offset);
|
||||
}
|
||||
|
||||
// We assume that FDEs for the same input sections are contiguous
|
||||
// in `fdes` vector.
|
||||
sort(fdes, [&](const FdeRecord<E> &a, const FdeRecord<E> &b) {
|
||||
std::stable_sort(fdes.begin() + fdes_begin, fdes.end(),
|
||||
[&](const FdeRecord<E> &a, const FdeRecord<E> &b) {
|
||||
InputSection<E> *x = this->symbols[rels[a.rel_idx].r_sym]->input_section;
|
||||
InputSection<E> *y = this->symbols[rels[b.rel_idx].r_sym]->input_section;
|
||||
return x->get_priority() < y->get_priority();
|
||||
});
|
||||
|
||||
// Associate FDEs to input sections.
|
||||
for (i64 i = 0; i < fdes.size();) {
|
||||
for (i64 i = fdes_begin; i < fdes.size();) {
|
||||
InputSection<E> *isec =
|
||||
this->symbols[rels[fdes[i].rel_idx].r_sym]->input_section;
|
||||
assert(isec->fde_begin == -1);
|
||||
|
@ -1270,6 +1270,8 @@ void EhFrameSection<E>::copy_buf(Context<E> &ctx) {
|
||||
memcpy(base + cie.output_offset, contents.data(), contents.size());
|
||||
|
||||
for (ElfRel<E> &rel : cie.get_rels()) {
|
||||
if (rel.r_type == E::R_NONE)
|
||||
continue;
|
||||
assert(rel.r_offset - cie.input_offset < contents.size());
|
||||
u64 loc = cie.output_offset + rel.r_offset - cie.input_offset;
|
||||
u64 val = file->symbols[rel.r_sym]->get_addr(ctx);
|
||||
@ -1288,6 +1290,8 @@ void EhFrameSection<E>::copy_buf(Context<E> &ctx) {
|
||||
*(u32 *)(base + offset + 4) = offset + 4 - fde.cie->output_offset;
|
||||
|
||||
for (ElfRel<E> &rel : fde.get_rels()) {
|
||||
if (rel.r_type == E::R_NONE)
|
||||
continue;
|
||||
assert(rel.r_offset - fde.input_offset < contents.size());
|
||||
u64 loc = offset + rel.r_offset - fde.input_offset;
|
||||
u64 val = file->symbols[rel.r_sym]->get_addr(ctx);
|
||||
|
494
relocatable.cc
Normal file
494
relocatable.cc
Normal file
@ -0,0 +1,494 @@
|
||||
// This file implements the -r or ---relocatable option. If the option
|
||||
// is given to a linker, the linker creates not an executable nor a
|
||||
// shared library file but instead create another object file by
|
||||
// combining input object files.
|
||||
//
|
||||
// The behavior of a linker with -r is pretty different from the one
|
||||
// without -r, and adding code for -r to the regular code path could
|
||||
// severely complicate it. Therefore, we implement -r as an
|
||||
// independent feature from others. That's why this file share only a
|
||||
// small amount of code with other files.
|
||||
//
|
||||
// Since the -r option is a minor feature, we don't aim for speed in
|
||||
// this file. This is a "better than nothing" implementation of the -r
|
||||
// option.
|
||||
//
|
||||
// Here is the strategy as to how to combine multiple object files
|
||||
// into one:
|
||||
//
|
||||
// - Regular sections containing opaque data (e.g. ".text" or
|
||||
// ".data") are just copied as-is without merging.
|
||||
//
|
||||
// - .symtab, .strtab and .shstrtab are merged.
|
||||
//
|
||||
// - COMDAT groups are uniquified.
|
||||
//
|
||||
// - Relocation sections are copied one by one, but we need to fix
|
||||
// symbol indices.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
template <typename E> class RObjectFile;
|
||||
|
||||
template <typename E>
|
||||
class ROutputChunk {
|
||||
public:
|
||||
ROutputChunk() {
|
||||
out_shdr.sh_addralign = 1;
|
||||
}
|
||||
|
||||
virtual ~ROutputChunk() = default;
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
virtual void write_to(Context<E> &ctx) = 0;
|
||||
|
||||
std::string_view name;
|
||||
i64 shndx = 0;
|
||||
ElfShdr<E> in_shdr = {};
|
||||
ElfShdr<E> out_shdr = {};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RInputSection : public ROutputChunk<E> {
|
||||
public:
|
||||
RInputSection(Context<E> &ctx, RObjectFile<E> &file, const ElfShdr<E> &shdr);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
RObjectFile<E> &file;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RSymtabSection : public ROutputChunk<E> {
|
||||
public:
|
||||
RSymtabSection() {
|
||||
this->name = ".symtab";
|
||||
this->out_shdr.sh_type = SHT_SYMTAB;
|
||||
this->out_shdr.sh_entsize = sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_addralign = E::wordsize;
|
||||
}
|
||||
|
||||
void add_local_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
void add_global_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
std::unordered_map<std::string_view, i64> sym_map;
|
||||
std::vector<ElfSym<E>> syms{1};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RStrtabSection : public ROutputChunk<E> {
|
||||
public:
|
||||
RStrtabSection(std::string_view name) {
|
||||
this->name = name;
|
||||
this->out_shdr.sh_type = SHT_STRTAB;
|
||||
this->out_shdr.sh_size = 1;
|
||||
}
|
||||
|
||||
i64 add_string(std::string_view str);
|
||||
void write_to(Context<E> &ctx) override;
|
||||
|
||||
std::unordered_map<std::string_view, i64> strings;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputEhdr : public ROutputChunk<E> {
|
||||
public:
|
||||
ROutputEhdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfEhdr<E>);
|
||||
}
|
||||
|
||||
void write_to(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ROutputShdr : public ROutputChunk<E> {
|
||||
public:
|
||||
ROutputShdr() {
|
||||
this->out_shdr.sh_size = sizeof(ElfShdr<E>);
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RObjectFile {
|
||||
public:
|
||||
RObjectFile(Context<E> &ctx, MemoryMappedFile<E> &mb);
|
||||
|
||||
void remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups);
|
||||
|
||||
template <typename T>
|
||||
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
|
||||
|
||||
MemoryMappedFile<E> &mb;
|
||||
std::span<ElfShdr<E>> elf_sections;
|
||||
std::vector<std::unique_ptr<RInputSection<E>>> sections;
|
||||
std::span<const ElfSym<E>> syms;
|
||||
std::vector<i64> symidx;
|
||||
i64 symtab_shndx = 0;
|
||||
i64 first_global = 0;
|
||||
const char *strtab = nullptr;
|
||||
const char *shstrtab = nullptr;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::add_local_symbol(Context<E> &ctx, RObjectFile<E> &file,
|
||||
i64 idx) {
|
||||
ElfSym<E> sym = file.syms[idx];
|
||||
assert(sym.st_bind == STB_LOCAL);
|
||||
|
||||
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common()) {
|
||||
if (!file.sections[sym.st_shndx])
|
||||
return;
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
}
|
||||
|
||||
std::string_view name = file.strtab + sym.st_name;
|
||||
sym.st_name = ctx.r_strtab->add_string(name);
|
||||
|
||||
file.symidx[idx] = syms.size();
|
||||
syms.push_back(sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::add_global_symbol(Context<E> &ctx, RObjectFile<E> &file,
|
||||
i64 idx) {
|
||||
ElfSym<E> sym = file.syms[idx];
|
||||
assert(sym.st_bind != STB_LOCAL);
|
||||
|
||||
std::string_view name = file.strtab + sym.st_name;
|
||||
auto [it, inserted] = sym_map.insert({name, syms.size()});
|
||||
|
||||
if (inserted) {
|
||||
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common())
|
||||
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
|
||||
sym.st_name = ctx.r_strtab->add_string(name);
|
||||
file.symidx[idx] = syms.size();
|
||||
syms.push_back(sym);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
file.symidx[idx] = it->second;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::update_shdr(Context<E> &ctx) {
|
||||
this->out_shdr.sh_size = syms.size() * sizeof(ElfSym<E>);
|
||||
this->out_shdr.sh_link = ctx.r_strtab->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RSymtabSection<E>::write_to(Context<E> &ctx) {
|
||||
ElfSym<E> *buf = (ElfSym<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (i64 i = 1; i < syms.size(); i++)
|
||||
buf[i] = syms[i];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RInputSection<E>::RInputSection(Context<E> &ctx, RObjectFile<E> &file,
|
||||
const ElfShdr<E> &shdr)
|
||||
: file(file) {
|
||||
this->name = file.shstrtab + shdr.sh_name;
|
||||
this->in_shdr = shdr;
|
||||
this->out_shdr = shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RInputSection<E>::update_shdr(Context<E> &ctx) {
|
||||
switch (this->in_shdr.sh_type) {
|
||||
case SHT_GROUP:
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
this->out_shdr.sh_info = file.symidx[this->in_shdr.sh_info];
|
||||
break;
|
||||
case SHT_REL:
|
||||
case SHT_RELA:
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
this->out_shdr.sh_info = file.sections[this->in_shdr.sh_info]->shndx;
|
||||
break;
|
||||
default:
|
||||
if (this->in_shdr.sh_link) {
|
||||
std::unique_ptr<RInputSection<E>> &sec =
|
||||
file.sections[this->in_shdr.sh_info];
|
||||
|
||||
if (sec)
|
||||
this->out_shdr.sh_link = sec->shndx;
|
||||
else if (this->in_shdr.sh_link == file.symtab_shndx)
|
||||
this->out_shdr.sh_link = ctx.r_symtab->shndx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RInputSection<E>::write_to(Context<E> &ctx) {
|
||||
if (this->in_shdr.sh_type == SHT_NOBITS)
|
||||
return;
|
||||
|
||||
std::span<u8> contents = file.template get_data<u8>(ctx, this->in_shdr);
|
||||
memcpy(ctx.buf + this->out_shdr.sh_offset, contents.data(), contents.size());
|
||||
|
||||
switch (this->in_shdr.sh_type) {
|
||||
case SHT_GROUP: {
|
||||
u32 *mem = (u32 *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (i64 i = 1; i < this->out_shdr.sh_size / sizeof(u32); i++)
|
||||
mem[i] = file.sections[mem[i]]->shndx;
|
||||
break;
|
||||
}
|
||||
case SHT_REL:
|
||||
case SHT_RELA: {
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
i64 size = this->out_shdr.sh_size / sizeof(ElfRel<E>);
|
||||
|
||||
for (i64 i = 0; i < size; i++) {
|
||||
const ElfSym<E> &sym = file.syms[rel[i].r_sym];
|
||||
if (sym.is_undef() || sym.is_abs() || sym.is_common() ||
|
||||
file.sections[sym.st_shndx])
|
||||
rel[i].r_sym = file.symidx[rel[i].r_sym];
|
||||
else
|
||||
memset(rel + i, 0, sizeof(ElfRel<E>));
|
||||
}
|
||||
|
||||
i64 i = 0;
|
||||
i64 j = 0;
|
||||
for (; j < size; j++)
|
||||
if (rel[j].r_type)
|
||||
rel[i++] = rel[j];
|
||||
for (; i < size; i++)
|
||||
memset(rel + i, 0, sizeof(ElfRel<E>));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 RStrtabSection<E>::add_string(std::string_view str) {
|
||||
auto [it, inserted] = strings.insert({str, this->out_shdr.sh_size});
|
||||
if (inserted)
|
||||
this->out_shdr.sh_size += str.size() + 1;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void RStrtabSection<E>::write_to(Context<E> &ctx) {
|
||||
for (auto [str, offset] : strings)
|
||||
memcpy(ctx.buf + this->out_shdr.sh_offset + offset, str.data(), str.size());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputEhdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfEhdr<E> &hdr = *(ElfEhdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
memcpy(&hdr.e_ident, "\177ELF", 4);
|
||||
hdr.e_ident[EI_CLASS] = (E::wordsize == 8) ? ELFCLASS64 : ELFCLASS32;
|
||||
hdr.e_ident[EI_DATA] = E::is_le ? ELFDATA2LSB : ELFDATA2MSB;
|
||||
hdr.e_ident[EI_VERSION] = EV_CURRENT;
|
||||
hdr.e_type = ET_REL;
|
||||
hdr.e_machine = E::e_machine;
|
||||
hdr.e_version = EV_CURRENT;
|
||||
hdr.e_shoff = ctx.r_shdr->out_shdr.sh_offset;
|
||||
hdr.e_ehsize = sizeof(ElfEhdr<E>);
|
||||
hdr.e_shentsize = sizeof(ElfShdr<E>);
|
||||
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
|
||||
hdr.e_shnum = ctx.r_shdr->out_shdr.sh_size / sizeof(ElfShdr<E>);
|
||||
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::update_shdr(Context<E> &ctx) {
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
this->out_shdr.sh_size += sizeof(ElfShdr<E>);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ROutputShdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfShdr<E> *hdr = (ElfShdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
hdr[chunk->shndx] = chunk->out_shdr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
RObjectFile<E>::RObjectFile(Context<E> &ctx, MemoryMappedFile<E> &mb)
|
||||
: mb(mb) {
|
||||
// Read ELF header and section header
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)mb.data(ctx);
|
||||
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(mb.data(ctx) + ehdr.e_shoff);
|
||||
i64 num_sections = (ehdr.e_shnum == 0) ? sh_begin->sh_size : ehdr.e_shnum;
|
||||
elf_sections = {sh_begin, sh_begin + num_sections};
|
||||
sections.resize(num_sections);
|
||||
|
||||
// Read .shstrtab
|
||||
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
|
||||
? sh_begin->sh_link : ehdr.e_shstrndx;
|
||||
shstrtab = (char *)(mb.data(ctx) + elf_sections[shstrtab_idx].sh_offset);
|
||||
|
||||
// Read .symtab
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
if (shdr.sh_type == SHT_SYMTAB) {
|
||||
syms = get_data<const ElfSym<E>>(ctx, shdr);
|
||||
strtab = (char *)(mb.data(ctx) + elf_sections[shdr.sh_link].sh_offset);
|
||||
symtab_shndx = i;
|
||||
first_global = shdr.sh_info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
symidx.resize(syms.size());
|
||||
|
||||
// Read sections
|
||||
for (i64 i = 1; i < elf_sections.size(); i++) {
|
||||
ElfShdr<E> &shdr = elf_sections[i];
|
||||
switch (shdr.sh_type) {
|
||||
case SHT_NULL:
|
||||
case SHT_SYMTAB:
|
||||
case SHT_STRTAB:
|
||||
break;
|
||||
default:
|
||||
sections[i].reset(new RInputSection(ctx, *this, shdr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
template <typename E>
|
||||
void RObjectFile<E>::remove_comdats(Context<E> &ctx,
|
||||
std::unordered_set<std::string_view> &groups) {
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
if (!sections[i])
|
||||
continue;
|
||||
ElfShdr<E> &shdr = sections[i]->in_shdr;
|
||||
if (shdr.sh_type != SHT_GROUP)
|
||||
continue;
|
||||
|
||||
// Get a comdat group signature and insert it into a set.
|
||||
const ElfSym<E> &sym = syms[shdr.sh_info];
|
||||
std::string_view signature = strtab + sym.st_name;
|
||||
if (groups.insert(signature).second)
|
||||
continue;
|
||||
|
||||
// If it is a duplicate, remove it and its members.
|
||||
sections[i] = nullptr;
|
||||
for (i64 j : this->template get_data<u32>(ctx, shdr).subspan(1))
|
||||
sections[j] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
template <typename T>
|
||||
std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
T *begin = (T *)(mb.data(ctx) + shdr.sh_offset);
|
||||
T *end = (T *)(mb.data(ctx) + shdr.sh_offset + shdr.sh_size);
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::unique_ptr<RObjectFile<E>>>
|
||||
open_files(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files;
|
||||
for (std::string_view arg : file_args) {
|
||||
if (arg.starts_with('-'))
|
||||
continue;
|
||||
|
||||
MemoryMappedFile<E> *mb =
|
||||
MemoryMappedFile<E>::must_open(ctx, std::string(arg));
|
||||
if (get_file_type(ctx, mb) == FileType::OBJ)
|
||||
files.emplace_back(new RObjectFile<E>(ctx, *mb));
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 assign_offsets(Context<E> &ctx) {
|
||||
i64 offset = 0;
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks) {
|
||||
offset = align_to(offset, chunk->out_shdr.sh_addralign);
|
||||
chunk->out_shdr.sh_offset = offset;
|
||||
offset += chunk->out_shdr.sh_size;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
// Read object files
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files = open_files(ctx, file_args);
|
||||
|
||||
// Remove duplicate comdat groups
|
||||
std::unordered_set<std::string_view> comdat_groups;
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
file->remove_comdats(ctx, comdat_groups);
|
||||
|
||||
// Create headers and linker-synthesized sections
|
||||
ROutputEhdr<E> ehdr;
|
||||
ROutputShdr<E> shdr;
|
||||
RSymtabSection<E> symtab;
|
||||
RStrtabSection<E> shstrtab(".shstrtab");
|
||||
RStrtabSection<E> strtab(".strtab");
|
||||
|
||||
ctx.r_chunks.push_back(&ehdr);
|
||||
ctx.r_chunks.push_back(&shstrtab);
|
||||
ctx.r_chunks.push_back(&strtab);
|
||||
|
||||
ctx.r_ehdr = &ehdr;
|
||||
ctx.r_shdr = &shdr;
|
||||
ctx.r_shstrtab = &shstrtab;
|
||||
ctx.r_strtab = &strtab;
|
||||
ctx.r_symtab = &symtab;
|
||||
|
||||
// Add input sections to output sections
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (std::unique_ptr<RInputSection<E>> &sec : file->sections)
|
||||
if (sec)
|
||||
ctx.r_chunks.push_back(sec.get());
|
||||
|
||||
ctx.r_chunks.push_back(&symtab);
|
||||
ctx.r_chunks.push_back(&shdr);
|
||||
|
||||
// Assign output section indices
|
||||
i64 shndx = 1;
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk != &ehdr && chunk != &shdr)
|
||||
chunk->shndx = shndx++;
|
||||
|
||||
// Add section names to .shstrtab
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
if (chunk->shndx)
|
||||
chunk->out_shdr.sh_name = shstrtab.add_string(chunk->name);
|
||||
|
||||
// Copy symbols from input objects to an output object
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (i64 i = 1; i < file->first_global; i++)
|
||||
symtab.add_local_symbol(ctx, *file, i);
|
||||
|
||||
symtab.out_shdr.sh_info = symtab.syms.size() + 1;
|
||||
|
||||
for (std::unique_ptr<RObjectFile<E>> &file : files)
|
||||
for (i64 i = file->first_global; i < file->syms.size(); i++)
|
||||
symtab.add_global_symbol(ctx, *file, i);
|
||||
|
||||
// Finalize section header
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
// Open an output file
|
||||
i64 filesize = assign_offsets(ctx);
|
||||
std::unique_ptr<OutputFile<E>> out =
|
||||
OutputFile<E>::open(ctx, ctx.arg.output, filesize);
|
||||
memset(out->buf, 0, filesize);
|
||||
ctx.buf = out->buf;
|
||||
|
||||
// Write to the output file
|
||||
for (ROutputChunk<E> *chunk : ctx.r_chunks)
|
||||
chunk->write_to(ctx);
|
||||
out->close(ctx);
|
||||
}
|
||||
|
||||
template void combine_objects(Context<X86_64> &, std::span<std::string_view>);
|
||||
template void combine_objects(Context<I386> &, std::span<std::string_view>);
|
55
test/relocatable.sh
Executable file
55
test/relocatable.sh
Executable file
@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd $(dirname $0)
|
||||
echo -n "Testing $(basename -s .sh $0) ... "
|
||||
t=$(pwd)/tmp/$(basename -s .sh $0)
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF | clang++ -c -o $t/a.o -xc++ -
|
||||
int one() { return 1; }
|
||||
|
||||
struct Foo {
|
||||
int three() { static int x = 3; return x++; }
|
||||
};
|
||||
|
||||
int a() {
|
||||
Foo x;
|
||||
return x.three();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | clang++ -c -o $t/b.o -xc++ -
|
||||
int two() { return 2; }
|
||||
|
||||
struct Foo {
|
||||
int three() { static int x = 3; return x++; }
|
||||
};
|
||||
|
||||
int b() {
|
||||
Foo x;
|
||||
return x.three();
|
||||
}
|
||||
EOF
|
||||
|
||||
../mold --relocatable -o $t/c.o $t/a.o $t/b.o
|
||||
|
||||
cat <<EOF | clang++ -c -o $t/d.o -xc++ -
|
||||
#include <iostream>
|
||||
|
||||
int one();
|
||||
int two();
|
||||
|
||||
struct Foo {
|
||||
int three();
|
||||
};
|
||||
|
||||
int main() {
|
||||
Foo x;
|
||||
std::cout << one() << " " << two() << " " << x.three() << "\n";
|
||||
}
|
||||
EOF
|
||||
|
||||
clang++ -fuse-ld=`pwd`/../mold -o $t/exe $t/c.o $t/d.o
|
||||
$t/exe | grep -q '^1 2 3$'
|
||||
|
||||
echo OK
|
Loading…
Reference in New Issue
Block a user