Skip to content

Commit

Permalink
Allow multiple .eh_frame sections in a single object file
Browse files Browse the repository at this point in the history
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 #1157
  • Loading branch information
rui314 committed Nov 29, 2023
1 parent 0fdbace commit f4c5a8a
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 57 deletions.
105 changes: 51 additions & 54 deletions elf/input-files.cc
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &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<E>)
if (name == ".got2")
Expand Down Expand Up @@ -416,78 +416,75 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
// This function parses an input .eh_frame section.
template <typename E>
void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
if (!eh_frame_section)
return;

InputSection<E> &isec = *eh_frame_section;
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
i64 cies_begin = cies.size();
i64 fdes_begin = fdes.size();
for (InputSection<E> *isec : eh_frame_sections) {
std::span<ElfRel<E>> 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<E> *)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<E> *)(data.data() + 4);
data = data.substr(size + 4);

for (std::string_view data = contents; !data.empty();) {
i64 size = *(U32<E> *)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<E> *)(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<E> *)(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<E> *)(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<E> &fde) -> InputSection<E> * {
return get_section(this->elf_syms[rels[fde.rel_idx].r_sym]);
auto get_isec = [&](const FdeRecord<E> &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<E> &a, const FdeRecord<E> &b) {
sort(fdes, [&](const FdeRecord<E> &a, const FdeRecord<E> &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<E> *isec = get_isec(fdes[i]);
assert(isec->fde_begin == -1);
isec->fde_begin = i++;
Expand Down
2 changes: 1 addition & 1 deletion elf/mold.h
Original file line number Diff line number Diff line change
Expand Up @@ -1183,7 +1183,7 @@ class ObjectFile : public InputFile<E> {
std::vector<FdeRecord<E>> fdes;
BitVector has_symver;
std::vector<ComdatGroupRef<E>> comdat_groups;
InputSection<E> *eh_frame_section = nullptr;
std::vector<InputSection<E> *> eh_frame_sections;
bool exclude_libs = false;
std::map<u32, u32> gnu_properties;
bool is_lto_obj = false;
Expand Down
4 changes: 2 additions & 2 deletions elf/passes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,8 @@ void kill_eh_frame_sections(Context<E> &ctx) {
Timer t(ctx, "kill_eh_frame_sections");

for (ObjectFile<E> *file : ctx.objs)
if (file->eh_frame_section)
file->eh_frame_section->is_alive = false;
for (InputSection<E> *sec : file->eh_frame_sections)
sec->is_alive = false;
}

template <typename E>
Expand Down
46 changes: 46 additions & 0 deletions test/elf/exception-multiple-ehframe.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/bash
. $(dirname $0)/common.inc

[ $MACHINE = m68k ] && skip
[ $MACHINE = sh4 ] && skip

cat <<EOF | $CXX -o $t/a.o -c -xc++ -
int foo() {
try {
throw 1;
} catch (int x) {
return x;
}
return 2;
}
EOF

cat <<EOF | $CXX -o $t/b.o -c -xc++ -
int bar() {
try {
throw 3;
} catch (int x) {
return x;
}
return 4;
}
EOF

$OBJCOPY --rename-section .eh_frame=.eh_frame2 $t/a.o
./mold -r -o $t/c.o $t/a.o $t/b.o
$OBJCOPY --rename-section .eh_frame2=.eh_frame $t/c.o

cat <<EOF | $CXX -o $t/d.o -c -xc++ -
#include <stdio.h>
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$'

0 comments on commit f4c5a8a

Please sign in to comment.