1
1
mirror of https://github.com/rui314/mold.git synced 2024-12-26 01:44:29 +03:00

Implement --relocatable

Fixes https://github.com/rui314/mold/issues/46
This commit is contained in:
Rui Ueyama 2021-06-08 15:39:23 +09:00
parent 3102ab867c
commit 9cc309af75
11 changed files with 625 additions and 17 deletions

View File

@ -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

View File

@ -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] = {

View File

@ -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:

View File

@ -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")) {

View File

@ -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"

View File

@ -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
View File

@ -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;

View File

@ -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);

View File

@ -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
View 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
View 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