From f4c5a8a42ecaf090a4aa14d52400bcaa341d53e2 Mon Sep 17 00:00:00 2001 From: Rui Ueyama Date: Wed, 29 Nov 2023 15:03:41 +0900 Subject: [PATCH] Allow multiple `.eh_frame` sections in a single object file A `.eh_frame` section contains data for exception handling. Usually, an object file contains only one `.eh_frame` section, which explains how to handle exceptions for all text sections in the same object file. However, it appears that, in rare cases, we need to handle object files containing multiple `.eh_frame` sections. An example of this is the `/usr/lib/clang/17/lib/x86_64-redhat-linux-gnu/clang_rt.crtbegin.o` file, which is provided by the `compiler-rt` package of Fedora 39. Specifically, I'm using the `quay.io/fedora/fedora:39` Docker image. The file contains two `.eh_frame` sections. One `.eh_frame` in the file is of type `STT_X86_64_UNWIND` and the other is of `STT_PROGBITS`. It's possible that the file was created with `ld -r`, and the linker failed to merge the two incoming `.eh_frame` sections into one output section due to the difference in section types. We did not expect such inputs and consequently produced corrupted output files. This commit improves our linker so that mold can handle multiple `.eh_frame` sections in a single object file. Fixes https://github.com/rui314/mold/issues/1157 --- elf/input-files.cc | 105 ++++++++++++------------- elf/mold.h | 2 +- elf/passes.cc | 4 +- test/elf/exception-multiple-ehframe.sh | 46 +++++++++++ 4 files changed, 100 insertions(+), 57 deletions(-) create mode 100755 test/elf/exception-multiple-ehframe.sh diff --git a/elf/input-files.cc b/elf/input-files.cc index 49580a097c..39a92892fa 100644 --- a/elf/input-files.cc +++ b/elf/input-files.cc @@ -324,7 +324,7 @@ void ObjectFile::initialize_sections(Context &ctx) { ctx.has_ctors = true; if (name == ".eh_frame") - eh_frame_section = this->sections[i].get(); + eh_frame_sections.push_back(this->sections[i].get()); if constexpr (is_ppc32) if (name == ".got2") @@ -416,78 +416,75 @@ void ObjectFile::initialize_sections(Context &ctx) { // This function parses an input .eh_frame section. template void ObjectFile::parse_ehframe(Context &ctx) { - if (!eh_frame_section) - return; - - InputSection &isec = *eh_frame_section; - std::span> rels = isec.get_rels(ctx); - i64 cies_begin = cies.size(); - i64 fdes_begin = fdes.size(); + for (InputSection *isec : eh_frame_sections) { + std::span> rels = isec->get_rels(ctx); + i64 cies_begin = cies.size(); + i64 fdes_begin = fdes.size(); + + // Read CIEs and FDEs until empty. + std::string_view contents = this->get_string(ctx, isec->shdr()); + i64 rel_idx = 0; + + for (std::string_view data = contents; !data.empty();) { + i64 size = *(U32 *)data.data(); + if (size == 0) + break; - // Read CIEs and FDEs until empty. - std::string_view contents = this->get_string(ctx, isec.shdr()); - i64 rel_idx = 0; + i64 begin_offset = data.data() - contents.data(); + i64 end_offset = begin_offset + size + 4; + i64 id = *(U32 *)(data.data() + 4); + data = data.substr(size + 4); - for (std::string_view data = contents; !data.empty();) { - i64 size = *(U32 *)data.data(); - if (size == 0) - break; + i64 rel_begin = rel_idx; + while (rel_idx < rels.size() && rels[rel_idx].r_offset < end_offset) + rel_idx++; + assert(rel_idx == rels.size() || begin_offset <= rels[rel_begin].r_offset); - i64 begin_offset = data.data() - contents.data(); - i64 end_offset = begin_offset + size + 4; - i64 id = *(U32 *)(data.data() + 4); - data = data.substr(size + 4); + if (id == 0) { + // This is CIE. + cies.emplace_back(ctx, *this, *isec, begin_offset, rels, rel_begin); + } else { + // This is FDE. + if (rel_begin == rel_idx || rels[rel_begin].r_sym == 0) { + // 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; + } - i64 rel_begin = rel_idx; - while (rel_idx < rels.size() && rels[rel_idx].r_offset < end_offset) - rel_idx++; - assert(rel_idx == rels.size() || begin_offset <= rels[rel_begin].r_offset); + if (rels[rel_begin].r_offset - begin_offset != 8) + Fatal(ctx) << *isec << ": FDE's first relocation should have offset 8"; - if (id == 0) { - // This is CIE. - cies.emplace_back(ctx, *this, isec, begin_offset, rels, rel_begin); - } else { - // This is FDE. - if (rel_begin == rel_idx || rels[rel_begin].r_sym == 0) { - // 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; + fdes.emplace_back(begin_offset, rel_begin); } - - if (rels[rel_begin].r_offset - begin_offset != 8) - Fatal(ctx) << isec << ": FDE's first relocation should have offset 8"; - - fdes.emplace_back(begin_offset, rel_begin); } - } - // Associate CIEs to FDEs. - auto find_cie = [&](i64 offset) { - for (i64 i = cies_begin; i < cies.size(); i++) - if (cies[i].input_offset == offset) - return i; - Fatal(ctx) << isec << ": bad FDE pointer"; - }; + // Associate CIEs to FDEs. + auto find_cie = [&](i64 offset) { + for (i64 i = cies_begin; i < cies.size(); i++) + if (cies[i].input_offset == offset) + return i; + Fatal(ctx) << *isec << ": bad FDE pointer"; + }; - for (i64 i = fdes_begin; i < fdes.size(); i++) { - i64 cie_offset = *(I32 *)(contents.data() + fdes[i].input_offset + 4); - fdes[i].cie_idx = find_cie(fdes[i].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_idx = find_cie(fdes[i].input_offset + 4 - cie_offset); + } } - auto get_isec = [&](const FdeRecord &fde) -> InputSection * { - return get_section(this->elf_syms[rels[fde.rel_idx].r_sym]); + auto get_isec = [&](const FdeRecord &fde) { + return get_section(this->elf_syms[fde.get_rels(*this)[0].r_sym]); }; // We assume that FDEs for the same input sections are contiguous // in `fdes` vector. - std::stable_sort(fdes.begin() + fdes_begin, fdes.end(), - [&](const FdeRecord &a, const FdeRecord &b) { + sort(fdes, [&](const FdeRecord &a, const FdeRecord &b) { return get_isec(a)->get_priority() < get_isec(b)->get_priority(); }); // Associate FDEs to input sections. - for (i64 i = fdes_begin; i < fdes.size();) { + for (i64 i = 0; i < fdes.size();) { InputSection *isec = get_isec(fdes[i]); assert(isec->fde_begin == -1); isec->fde_begin = i++; diff --git a/elf/mold.h b/elf/mold.h index 86e78dbd22..e4816c4a38 100644 --- a/elf/mold.h +++ b/elf/mold.h @@ -1183,7 +1183,7 @@ class ObjectFile : public InputFile { std::vector> fdes; BitVector has_symver; std::vector> comdat_groups; - InputSection *eh_frame_section = nullptr; + std::vector *> eh_frame_sections; bool exclude_libs = false; std::map gnu_properties; bool is_lto_obj = false; diff --git a/elf/passes.cc b/elf/passes.cc index 7d85a1e461..13fad126bb 100644 --- a/elf/passes.cc +++ b/elf/passes.cc @@ -356,8 +356,8 @@ void kill_eh_frame_sections(Context &ctx) { Timer t(ctx, "kill_eh_frame_sections"); for (ObjectFile *file : ctx.objs) - if (file->eh_frame_section) - file->eh_frame_section->is_alive = false; + for (InputSection *sec : file->eh_frame_sections) + sec->is_alive = false; } template diff --git a/test/elf/exception-multiple-ehframe.sh b/test/elf/exception-multiple-ehframe.sh new file mode 100755 index 0000000000..005538b9cc --- /dev/null +++ b/test/elf/exception-multiple-ehframe.sh @@ -0,0 +1,46 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +[ $MACHINE = m68k ] && skip +[ $MACHINE = sh4 ] && skip + +cat < + +int foo(); +int bar(); + +int main() { + printf("%d %d\n", foo(), bar()); +} +EOF + +$CXX -B. -o $t/exe1 $t/d.o $t/c.o +$QEMU $t/exe1 +$QEMU $t/exe1 | grep -q '^1 3$'