From 9cc309af757a2ac774e68c0f3513e5afd3e32ddc Mon Sep 17 00:00:00 2001 From: Rui Ueyama Date: Tue, 8 Jun 2021 15:39:23 +0900 Subject: [PATCH] Implement --relocatable Fixes https://github.com/rui314/mold/issues/46 --- Makefile | 2 +- arch_i386.cc | 9 +- arch_x86_64.cc | 9 +- cmdline.cc | 3 + docs/mold.1 | 8 + main.cc | 5 + mold.h | 23 +++ object_file.cc | 30 ++- output_chunks.cc | 4 + relocatable.cc | 494 ++++++++++++++++++++++++++++++++++++++++++++ test/relocatable.sh | 55 +++++ 11 files changed, 625 insertions(+), 17 deletions(-) create mode 100644 relocatable.cc create mode 100755 test/relocatable.sh diff --git a/Makefile b/Makefile index 2b2acc49..118c33b2 100644 --- a/Makefile +++ b/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 diff --git a/arch_i386.cc b/arch_i386.cc index 93ecd609..1c6af916 100644 --- a/arch_i386.cc +++ b/arch_i386.cc @@ -310,6 +310,12 @@ void InputSection::scan_relocations(Context &ctx) { // Scan relocations for (i64 i = 0; i < rels.size(); i++) { const ElfRel &rel = rels[i]; + + if (rel.r_type == R_386_NONE) { + rel_types[i] = R_NONE; + break; + } + Symbol &sym = *file.symbols[rel.r_sym]; u8 *loc = (u8 *)(contents.data() + rel.r_offset); @@ -324,9 +330,6 @@ void InputSection::scan_relocations(Context &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] = { diff --git a/arch_x86_64.cc b/arch_x86_64.cc index 4ba21cec..f15161e9 100644 --- a/arch_x86_64.cc +++ b/arch_x86_64.cc @@ -481,6 +481,12 @@ void InputSection::scan_relocations(Context &ctx) { // Scan relocations for (i64 i = 0; i < rels.size(); i++) { const ElfRel &rel = rels[i]; + + if (rel.r_type == R_X86_64_NONE) { + rel_types[i] = R_NONE; + continue; + } + Symbol &sym = *file.symbols[rel.r_sym]; u8 *loc = (u8 *)(contents.data() + rel.r_offset); @@ -495,9 +501,6 @@ void InputSection::scan_relocations(Context &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: diff --git a/cmdline.cc b/cmdline.cc index 9f53826e..29de0e44 100644 --- a/cmdline.cc +++ b/cmdline.cc @@ -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 &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")) { diff --git a/docs/mold.1 b/docs/mold.1 index fa232e43..7307a49a 100644 --- a/docs/mold.1 +++ b/docs/mold.1 @@ -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" diff --git a/main.cc b/main.cc index efa15d24..43e3a8cc 100644 --- a/main.cc +++ b/main.cc @@ -312,6 +312,11 @@ int do_main(int argc, char **argv) { std::vector 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); diff --git a/mold.h b/mold.h index 03f45171..433af702 100644 --- a/mold.h +++ b/mold.h @@ -54,6 +54,13 @@ template class Symbol; template struct CieRecord; template struct Context; template struct FdeRecord; + +template class ROutputChunk; +template class ROutputEhdr; +template class ROutputShdr; +template class RStrtabSection; +template class RSymtabSection; + class Compressor; class TarFile; @@ -1223,6 +1230,13 @@ void gc_sections(Context &ctx); template void icf_sections(Context &ctx); +// +// relocatable.cc +// + +template +void combine_objects(Context &ctx, std::span 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> note_property; std::unique_ptr> repro; + // For --relocatable + std::vector *> r_chunks; + ROutputEhdr *r_ehdr = nullptr; + ROutputShdr *r_shdr = nullptr; + RStrtabSection *r_shstrtab = nullptr; + RStrtabSection *r_strtab = nullptr; + RSymtabSection *r_symtab = nullptr; + u64 tls_begin = -1; u64 tls_end = -1; diff --git a/object_file.cc b/object_file.cc index 98a58e2c..a3bfaec8 100644 --- a/object_file.cc +++ b/object_file.cc @@ -298,6 +298,8 @@ void ObjectFile::initialize_ehframe_sections(Context &ctx) { template void ObjectFile::read_ehframe(Context &ctx, InputSection &isec) { std::span> 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::read_ehframe(Context &ctx, InputSection &isec) { assert(begin_offset <= rels[rel_begin].r_offset); if (id == 0) { + // This is CIE. cies.push_back(CieRecord(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::read_ehframe(Context &ctx, InputSection &isec) { // Associate CIEs to FDEs. auto find_cie = [&](i64 offset) -> CieRecord * { - for (CieRecord &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 &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 &a, const FdeRecord &b) { + std::stable_sort(fdes.begin() + fdes_begin, fdes.end(), + [&](const FdeRecord &a, const FdeRecord &b) { InputSection *x = this->symbols[rels[a.rel_idx].r_sym]->input_section; InputSection *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 *isec = this->symbols[rels[fdes[i].rel_idx].r_sym]->input_section; assert(isec->fde_begin == -1); diff --git a/output_chunks.cc b/output_chunks.cc index e60987ca..122e5777 100644 --- a/output_chunks.cc +++ b/output_chunks.cc @@ -1270,6 +1270,8 @@ void EhFrameSection::copy_buf(Context &ctx) { memcpy(base + cie.output_offset, contents.data(), contents.size()); for (ElfRel &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::copy_buf(Context &ctx) { *(u32 *)(base + offset + 4) = offset + 4 - fde.cie->output_offset; for (ElfRel &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); diff --git a/relocatable.cc b/relocatable.cc new file mode 100644 index 00000000..436407fb --- /dev/null +++ b/relocatable.cc @@ -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 +#include + +template class RObjectFile; + +template +class ROutputChunk { +public: + ROutputChunk() { + out_shdr.sh_addralign = 1; + } + + virtual ~ROutputChunk() = default; + virtual void update_shdr(Context &ctx) {} + virtual void write_to(Context &ctx) = 0; + + std::string_view name; + i64 shndx = 0; + ElfShdr in_shdr = {}; + ElfShdr out_shdr = {}; +}; + +template +class RInputSection : public ROutputChunk { +public: + RInputSection(Context &ctx, RObjectFile &file, const ElfShdr &shdr); + void update_shdr(Context &ctx) override; + void write_to(Context &ctx) override; + + RObjectFile &file; +}; + +template +class RSymtabSection : public ROutputChunk { +public: + RSymtabSection() { + this->name = ".symtab"; + this->out_shdr.sh_type = SHT_SYMTAB; + this->out_shdr.sh_entsize = sizeof(ElfSym); + this->out_shdr.sh_addralign = E::wordsize; + } + + void add_local_symbol(Context &ctx, RObjectFile &file, i64 idx); + void add_global_symbol(Context &ctx, RObjectFile &file, i64 idx); + void update_shdr(Context &ctx) override; + void write_to(Context &ctx) override; + + std::unordered_map sym_map; + std::vector> syms{1}; +}; + +template +class RStrtabSection : public ROutputChunk { +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 &ctx) override; + + std::unordered_map strings; +}; + +template +class ROutputEhdr : public ROutputChunk { +public: + ROutputEhdr() { + this->out_shdr.sh_size = sizeof(ElfEhdr); + } + + void write_to(Context &ctx) override; +}; + +template +class ROutputShdr : public ROutputChunk { +public: + ROutputShdr() { + this->out_shdr.sh_size = sizeof(ElfShdr); + } + + void update_shdr(Context &ctx) override; + void write_to(Context &ctx) override; +}; + +template +class RObjectFile { +public: + RObjectFile(Context &ctx, MemoryMappedFile &mb); + + void remove_comdats(Context &ctx, + std::unordered_set &groups); + + template + std::span get_data(Context &ctx, const ElfShdr &shdr); + + MemoryMappedFile &mb; + std::span> elf_sections; + std::vector>> sections; + std::span> syms; + std::vector symidx; + i64 symtab_shndx = 0; + i64 first_global = 0; + const char *strtab = nullptr; + const char *shstrtab = nullptr; +}; + +template +void RSymtabSection::add_local_symbol(Context &ctx, RObjectFile &file, + i64 idx) { + ElfSym 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 +void RSymtabSection::add_global_symbol(Context &ctx, RObjectFile &file, + i64 idx) { + ElfSym 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 +void RSymtabSection::update_shdr(Context &ctx) { + this->out_shdr.sh_size = syms.size() * sizeof(ElfSym); + this->out_shdr.sh_link = ctx.r_strtab->shndx; +} + +template +void RSymtabSection::write_to(Context &ctx) { + ElfSym *buf = (ElfSym *)(ctx.buf + this->out_shdr.sh_offset); + for (i64 i = 1; i < syms.size(); i++) + buf[i] = syms[i]; +} + +template +RInputSection::RInputSection(Context &ctx, RObjectFile &file, + const ElfShdr &shdr) + : file(file) { + this->name = file.shstrtab + shdr.sh_name; + this->in_shdr = shdr; + this->out_shdr = shdr; +} + +template +void RInputSection::update_shdr(Context &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> &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 +void RInputSection::write_to(Context &ctx) { + if (this->in_shdr.sh_type == SHT_NOBITS) + return; + + std::span contents = file.template get_data(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 *rel = (ElfRel *)(ctx.buf + this->out_shdr.sh_offset); + i64 size = this->out_shdr.sh_size / sizeof(ElfRel); + + for (i64 i = 0; i < size; i++) { + const ElfSym &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)); + } + + 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)); + break; + } + } +} + +template +i64 RStrtabSection::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 +void RStrtabSection::write_to(Context &ctx) { + for (auto [str, offset] : strings) + memcpy(ctx.buf + this->out_shdr.sh_offset + offset, str.data(), str.size()); +} + +template +void ROutputEhdr::write_to(Context &ctx) { + ElfEhdr &hdr = *(ElfEhdr *)(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); + hdr.e_shentsize = sizeof(ElfShdr); + hdr.e_shstrndx = ctx.r_shstrtab->shndx; + hdr.e_shnum = ctx.r_shdr->out_shdr.sh_size / sizeof(ElfShdr); + hdr.e_shstrndx = ctx.r_shstrtab->shndx; +} + +template +void ROutputShdr::update_shdr(Context &ctx) { + for (ROutputChunk *chunk : ctx.r_chunks) + if (chunk->shndx) + this->out_shdr.sh_size += sizeof(ElfShdr); +} + +template +void ROutputShdr::write_to(Context &ctx) { + ElfShdr *hdr = (ElfShdr *)(ctx.buf + this->out_shdr.sh_offset); + for (ROutputChunk *chunk : ctx.r_chunks) + if (chunk->shndx) + hdr[chunk->shndx] = chunk->out_shdr; +} + +template +RObjectFile::RObjectFile(Context &ctx, MemoryMappedFile &mb) + : mb(mb) { + // Read ELF header and section header + ElfEhdr &ehdr = *(ElfEhdr *)mb.data(ctx); + ElfShdr *sh_begin = (ElfShdr *)(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 &shdr = elf_sections[i]; + if (shdr.sh_type == SHT_SYMTAB) { + syms = get_data>(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 &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 +void RObjectFile::remove_comdats(Context &ctx, + std::unordered_set &groups) { + for (i64 i = 1; i < sections.size(); i++) { + if (!sections[i]) + continue; + ElfShdr &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 &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(ctx, shdr).subspan(1)) + sections[j] = nullptr; + } +} + +template +template +std::span RObjectFile::get_data(Context &ctx, const ElfShdr &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 +static std::vector>> +open_files(Context &ctx, std::span file_args) { + std::vector>> files; + for (std::string_view arg : file_args) { + if (arg.starts_with('-')) + continue; + + MemoryMappedFile *mb = + MemoryMappedFile::must_open(ctx, std::string(arg)); + if (get_file_type(ctx, mb) == FileType::OBJ) + files.emplace_back(new RObjectFile(ctx, *mb)); + } + return files; +} + +template +static i64 assign_offsets(Context &ctx) { + i64 offset = 0; + for (ROutputChunk *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 +void combine_objects(Context &ctx, std::span file_args) { + // Read object files + std::vector>> files = open_files(ctx, file_args); + + // Remove duplicate comdat groups + std::unordered_set comdat_groups; + for (std::unique_ptr> &file : files) + file->remove_comdats(ctx, comdat_groups); + + // Create headers and linker-synthesized sections + ROutputEhdr ehdr; + ROutputShdr shdr; + RSymtabSection symtab; + RStrtabSection shstrtab(".shstrtab"); + RStrtabSection 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> &file : files) + for (std::unique_ptr> &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 *chunk : ctx.r_chunks) + if (chunk != &ehdr && chunk != &shdr) + chunk->shndx = shndx++; + + // Add section names to .shstrtab + for (ROutputChunk *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> &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> &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 *chunk : ctx.r_chunks) + chunk->update_shdr(ctx); + + // Open an output file + i64 filesize = assign_offsets(ctx); + std::unique_ptr> out = + OutputFile::open(ctx, ctx.arg.output, filesize); + memset(out->buf, 0, filesize); + ctx.buf = out->buf; + + // Write to the output file + for (ROutputChunk *chunk : ctx.r_chunks) + chunk->write_to(ctx); + out->close(ctx); +} + +template void combine_objects(Context &, std::span); +template void combine_objects(Context &, std::span); diff --git a/test/relocatable.sh b/test/relocatable.sh new file mode 100755 index 00000000..a4b9e8be --- /dev/null +++ b/test/relocatable.sh @@ -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 < + +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