mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
954107e29c | ||
|
|
21e20e01da | ||
|
|
161cb6c99c | ||
|
|
21cfee556a | ||
|
|
06203c4a72 | ||
|
|
232067634f | ||
|
|
3f9aab7066 | ||
|
|
8b88595f26 | ||
|
|
fa09c562c9 | ||
|
|
9457d993c4 | ||
|
|
43810dfecd | ||
|
|
2d6061a233 | ||
|
|
3d6aad65b0 | ||
|
|
f7b1920f84 | ||
|
|
b42f978212 |
No files matched your search
+6
-2
@@ -44,7 +44,7 @@
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.37.1)
|
||||
project(mold VERSION 2.38.1)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(CheckSymbolExists)
|
||||
@@ -62,7 +62,11 @@ endif()
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold -Wl,--gdb-index)
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold)
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Deb")
|
||||
target_link_options(mold PRIVATE -Wl,--gdb-index)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
|
||||
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
|
||||
|
||||
@@ -126,10 +126,10 @@ RUN mkdir /build && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build LLVM 19
|
||||
# Build LLVM 20
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-19.1.7.tar.gz | tar xzf - --strip-components=1 && \
|
||||
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-20.1.3.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir b && \
|
||||
cd b && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
|
||||
@@ -195,14 +195,22 @@ dest=mold-$version-$arch-linux
|
||||
|
||||
# We use the timestamp of the last Git commit as the file timestamp
|
||||
# for build artifacts.
|
||||
timestamp="$(git log -1 --format=%ci)"
|
||||
timestamp=$(git log -1 --format=%ct)
|
||||
|
||||
# Build mold in a container.
|
||||
mkdir -p dist
|
||||
|
||||
# If the host is ARM64, `uname -m` in an ARM32 container running on it
|
||||
# reports it not as ARM32 but as ARM64, which confuses BLAKE3's cmake script
|
||||
# and erroneously enables NEON SIMD instructions. `setarch` can be used
|
||||
# to change the output of `uname -m`.
|
||||
[ $arch = arm ] && setarch='setarch linux32'
|
||||
|
||||
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image bash -c "
|
||||
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
|
||||
$setarch bash -c "
|
||||
set -e
|
||||
export SOURCE_DATE_EPOCH=$timestamp
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
|
||||
@@ -212,7 +220,7 @@ cmake -DMOLD_USE_MOLD=1 .
|
||||
cmake --build . -j\$(nproc)
|
||||
ctest --output-on-failure -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
find $dest -print | xargs touch --no-dereference --date='$timestamp'
|
||||
find $dest -print | xargs touch --no-dereference --date=@$timestamp
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
|
||||
cp mold /dist
|
||||
sha256sum /dist/$dest.tar.gz
|
||||
|
||||
+8
-2
@@ -587,6 +587,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
bool warn_shared_textrel = false;
|
||||
bool error_unresolved_symbols = true;
|
||||
std::optional<SeparateCodeKind> z_separate_code;
|
||||
std::optional<bool> allow_shlib_undefined;
|
||||
std::optional<bool> report_undefined;
|
||||
std::optional<bool> z_relro;
|
||||
std::optional<bool> z_dynamic_undefined_weak;
|
||||
@@ -1303,9 +1304,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_arg("filter") || read_arg("F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_flag("allow-shlib-undefined")) {
|
||||
ctx.arg.allow_shlib_undefined = true;
|
||||
allow_shlib_undefined = true;
|
||||
} else if (read_flag("no-allow-shlib-undefined")) {
|
||||
ctx.arg.allow_shlib_undefined = false;
|
||||
allow_shlib_undefined = false;
|
||||
} else if (read_arg("O")) {
|
||||
} else if (read_flag("EB")) {
|
||||
} else if (read_flag("EL")) {
|
||||
@@ -1412,6 +1413,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
|
||||
if (allow_shlib_undefined)
|
||||
ctx.arg.allow_shlib_undefined = *allow_shlib_undefined;
|
||||
else
|
||||
ctx.arg.allow_shlib_undefined = ctx.arg.shared;
|
||||
|
||||
if (!report_undefined)
|
||||
report_undefined = !ctx.arg.shared;
|
||||
|
||||
|
||||
+52
-9
@@ -999,6 +999,7 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, esym, !this->is_reachable) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.set_input_section(isec);
|
||||
@@ -1006,6 +1007,7 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = esym.is_weak();
|
||||
sym.is_versioned_default = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1273,23 +1275,48 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
if (vers.empty() || esyms[i].is_undef())
|
||||
ver = VER_NDX_GLOBAL;
|
||||
else
|
||||
ver = (vers[i] & ~VERSYM_HIDDEN);
|
||||
ver = vers[i] & ~VERSYM_HIDDEN;
|
||||
|
||||
if (ver == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
bool is_default = vers.empty() || !(vers[i] & VERSYM_HIDDEN);
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
this->versyms.push_back(ver);
|
||||
|
||||
if (is_default) {
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
std::string_view mangled_name = save_string(
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
|
||||
auto get_versioned_sym = [&] {
|
||||
std::string_view key = save_string(
|
||||
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
|
||||
this->symbols.push_back(get_symbol(ctx, mangled_name, name));
|
||||
return get_symbol(ctx, key, name);
|
||||
};
|
||||
|
||||
// Symbol resolution involving symbol versioning is tricky because one
|
||||
// symbol can be resolved with two different identifiers. Among
|
||||
// symbols with the same name but different versions, one of them is
|
||||
// always marked as the "default" one. This symbol is often denoted
|
||||
// with two atsigns as `foo@@VERSION` and can be referred to either
|
||||
// as `foo` or `foo@VERSION`. No other symbols have two names like that.
|
||||
//
|
||||
// On contrary, a versioned non-default symbol can be referred only
|
||||
// with an explicit version suffix, e.g., `foo@VERSION`.
|
||||
//
|
||||
// Here is how we resolve versioned default symbols. We resolve `foo`
|
||||
// and `foo@VERSION` as usual, but with information to forward
|
||||
// references to `foo@VERSION` to `foo`. After name resolution, we
|
||||
// visit all symbol references to redirect `foo@VERSION` to `foo`.
|
||||
if (vers.empty() || ver == VER_NDX_GLOBAL) {
|
||||
// Unversioned symbol
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
this->symbols2.push_back(nullptr);
|
||||
} else if (vers[i] & VERSYM_HIDDEN) {
|
||||
// Versioned non-default symbol
|
||||
this->symbols.push_back(get_versioned_sym());
|
||||
this->symbols2.push_back(nullptr);
|
||||
} else {
|
||||
// Versioned default symbol
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
this->symbols2.push_back(get_versioned_sym());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1396,6 +1423,22 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = versyms[i];
|
||||
sym.is_weak = true;
|
||||
sym.is_versioned_default = false;
|
||||
}
|
||||
|
||||
// A symbol with the default version is a special case because, unlike
|
||||
// other symbols, the symbol can be referred by two names, `foo` and
|
||||
// `foo@VERSION`. Here, we resolve `foo@VERSOIN` as a proxy of `foo`.
|
||||
Symbol<E> *sym2 = this->symbols2[i];
|
||||
if (sym2 && sym2 != &sym) {
|
||||
std::scoped_lock lock2(sym2->mu);
|
||||
|
||||
if (get_rank(this, esym, false) < get_rank(*sym2)) {
|
||||
sym2->file = this;
|
||||
sym2->origin = (uintptr_t)&sym;
|
||||
sym2->sym_idx = i;
|
||||
sym2->is_versioned_default = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -44,8 +44,8 @@ static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
|
||||
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, archive_name);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->is_reachable =
|
||||
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
@@ -65,8 +65,8 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, ReaderContext &rctx,
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->archive_name = archive_name;
|
||||
file->is_reachable =
|
||||
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
if (ctx.arg.trace)
|
||||
Out(ctx) << "trace: " << *file;
|
||||
@@ -81,7 +81,7 @@ new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
SharedFile<E> *file = new SharedFile<E>(ctx, mf);
|
||||
ctx.dso_pool.emplace_back(file);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->is_reachable = !rctx.as_needed;
|
||||
file->as_needed = rctx.as_needed;
|
||||
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
@@ -496,7 +496,7 @@ int mold_main(int argc, char **argv) {
|
||||
// create as few segments as possible.
|
||||
sort_output_sections(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gnu_debuglink)
|
||||
separate_debug_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
@@ -612,7 +612,7 @@ int mold_main(int argc, char **argv) {
|
||||
// .gdb_index's contents cannot be constructed before applying
|
||||
// relocations to other debug sections. We have relocated debug
|
||||
// sections now, so write the .gdb_index section.
|
||||
if (ctx.gdb_index && ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gdb_index && !ctx.gnu_debuglink)
|
||||
write_gdb_index(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
@@ -621,7 +621,7 @@ int mold_main(int argc, char **argv) {
|
||||
if (ctx.buildid)
|
||||
write_build_id(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gnu_debuglink)
|
||||
write_gnu_debuglink(ctx);
|
||||
|
||||
t_copy.stop();
|
||||
@@ -642,7 +642,7 @@ int mold_main(int argc, char **argv) {
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
if (!ctx.arg.separate_debug_file.empty())
|
||||
if (ctx.gnu_debuglink)
|
||||
write_separate_debug_file(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
|
||||
@@ -1353,6 +1353,7 @@ public:
|
||||
std::string_view shstrtab;
|
||||
std::string_view symbol_strtab;
|
||||
|
||||
bool as_needed = false;
|
||||
bool has_init_array = false;
|
||||
bool has_ctors = false;
|
||||
|
||||
@@ -1487,6 +1488,7 @@ public:
|
||||
|
||||
std::string soname;
|
||||
std::vector<std::string_view> version_strings;
|
||||
std::vector<Symbol<E> *> symbols2;
|
||||
std::vector<ElfSym<E>> elf_syms2;
|
||||
|
||||
private:
|
||||
@@ -2386,6 +2388,9 @@ public:
|
||||
bool is_traced : 1 = false; // for --trace-symbol
|
||||
bool is_wrapped : 1 = false; // for --wrap
|
||||
|
||||
// For symbols with default symbol version, e.g. foo@@VERSION.
|
||||
bool is_versioned_default : 1 = false;
|
||||
|
||||
// If a symbol can be resolved to a symbol in a different ELF file at
|
||||
// runtime, `is_imported` is true. If a symbol is a dynamic symbol and
|
||||
// can be used by other ELF file at runtime, `is_exported` is true.
|
||||
|
||||
+112
-98
@@ -215,14 +215,14 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
std::vector<InputFile<E> *> roots;
|
||||
append(roots, ctx.objs);
|
||||
append(roots, ctx.dsos);
|
||||
|
||||
for (InputFile<E> *file : ctx.objs)
|
||||
if (file->is_reachable)
|
||||
roots.push_back(file);
|
||||
for (InputFile<E> *file : roots)
|
||||
if (!file->as_needed)
|
||||
file->is_reachable = true;
|
||||
|
||||
for (InputFile<E> *file : ctx.dsos)
|
||||
if (file->is_reachable)
|
||||
roots.push_back(file);
|
||||
std::erase_if(roots, [](InputFile<E> *file) { return !file->is_reachable; });
|
||||
|
||||
tbb::parallel_for_each(roots, [&](InputFile<E> *file,
|
||||
tbb::feeder<InputFile<E> *> &feeder) {
|
||||
@@ -247,25 +247,14 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
// `VER1`. If it does, that's the symbol we are looking for.
|
||||
template <typename E>
|
||||
static void resolve_default_symver(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
std::vector<InputFile<E> *> files;
|
||||
append(files, ctx.objs);
|
||||
append(files, ctx.dsos);
|
||||
|
||||
if (!sym.file && esym.is_undef() &&
|
||||
file->has_symver[i - file->first_global]) {
|
||||
std::string_view str = file->symbol_strtab.data() + esym.st_name;
|
||||
i64 pos = str.find('@');
|
||||
assert(pos != str.npos);
|
||||
|
||||
std::string_view name = str.substr(0, pos);
|
||||
std::string_view ver = str.substr(pos + 1);
|
||||
|
||||
Symbol<E> *sym2 = get_symbol(ctx, name);
|
||||
if (sym2->file && sym2->file->is_dso && sym2->get_version() == ver)
|
||||
file->symbols[i] = sym2;
|
||||
}
|
||||
}
|
||||
tbb::parallel_for_each(files, [](InputFile<E> *file) {
|
||||
for (Symbol<E> *&sym : file->get_global_syms())
|
||||
if (sym->is_versioned_default)
|
||||
sym = (Symbol<E> *)sym->origin;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -310,69 +299,68 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
resolve_default_symver(ctx);
|
||||
mark_live_objects(ctx);
|
||||
|
||||
// Now that we know the exact set of input files that are to be
|
||||
// included in the output file, we want to redo symbol resolution.
|
||||
// This is because symbols defined by object files in archive files
|
||||
// may have risen as a result of mark_live_objects().
|
||||
//
|
||||
// To redo symbol resolution, we want to clear the state first.
|
||||
clear_symbols(ctx);
|
||||
|
||||
// COMDAT elimination needs to happen exactly here.
|
||||
//
|
||||
// It needs to be after archive extraction, otherwise we might
|
||||
// assign COMDAT leader to an archive member that is not supposed to
|
||||
// be extracted.
|
||||
//
|
||||
// It needs to happen before the final symbol resolution, otherwise
|
||||
// we could eliminate a symbol that is already resolved to and cause
|
||||
// dangling references.
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
update_minimum(ref.group->owner, file->priority);
|
||||
for (ComdatGroup *g : file->lto_comdat_groups)
|
||||
if (g)
|
||||
update_minimum(g->owner, file->priority);
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
if (ref.group->owner != file->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (InputSection<E> *isec = file->sections[i].get())
|
||||
isec->is_alive = false;
|
||||
});
|
||||
|
||||
// Redo symbol resolution
|
||||
tbb::parallel_for_each(files, [&](InputFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
file->resolve_symbols(ctx);
|
||||
});
|
||||
|
||||
// Symbols with hidden visibility need to be resolved within the
|
||||
// output file. If a hidden symbol was resolved to a DSO, we'll redo
|
||||
// symbol resolution from scratch with the flag to skip that symbol
|
||||
// next time. This should be rare.
|
||||
std::atomic_bool flag = false;
|
||||
|
||||
std::atomic_bool redo = false;
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file && sym->visibility == STV_HIDDEN) {
|
||||
sym->skip_dso = true;
|
||||
flag = true;
|
||||
redo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!flag)
|
||||
return;
|
||||
if (!redo)
|
||||
break;
|
||||
clear_symbols(ctx);
|
||||
}
|
||||
|
||||
// Now that we know the exact set of input files that are to be
|
||||
// included in the output file, we want to redo symbol resolution.
|
||||
// This is because symbols defined by object files in archive files
|
||||
// may have risen as a result of mark_live_objects().
|
||||
//
|
||||
// To redo symbol resolution, we want to clear the state first.
|
||||
clear_symbols(ctx);
|
||||
|
||||
// COMDAT elimination needs to happen exactly here.
|
||||
//
|
||||
// It needs to be after archive extraction, otherwise we might
|
||||
// assign COMDAT leader to an archive member that is not supposed to
|
||||
// be extracted.
|
||||
//
|
||||
// It needs to happen before the final symbol resolution, otherwise
|
||||
// we could eliminate a symbol that is already resolved to and cause
|
||||
// dangling references.
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable) {
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
update_minimum(ref.group->owner, file->priority);
|
||||
for (ComdatGroup *g : file->lto_comdat_groups)
|
||||
if (g)
|
||||
update_minimum(g->owner, file->priority);
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
for (ComdatGroupRef<E> &ref : file->comdat_groups)
|
||||
if (ref.group->owner != file->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (InputSection<E> *isec = file->sections[i].get())
|
||||
isec->is_alive = false;
|
||||
});
|
||||
|
||||
// Redo symbol resolution
|
||||
tbb::parallel_for_each(files, [&](InputFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
file->resolve_symbols(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// Do link-time optimization. We pass all IR object files to the compiler
|
||||
@@ -1125,17 +1113,35 @@ void check_shlib_undefined(Context<E> &ctx) {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Obtain a list of known shared library names.
|
||||
std::unordered_set<std::string_view> sonames;
|
||||
// We want to skip a file that (either directly or indirectly) depends
|
||||
// on another .so file we know nothing about. This is because missing
|
||||
// symbols might be provided by that unknown file.
|
||||
//
|
||||
// We need recursion for indirect dependencies.
|
||||
std::unordered_map<std::string_view, std::vector<std::string_view>> deps;
|
||||
for (std::unique_ptr<SharedFile<E>> &file : ctx.dso_pool)
|
||||
sonames.insert(file->soname);
|
||||
deps[file->soname] = file->get_dt_needed(ctx);
|
||||
|
||||
std::function<bool(std::string_view, i64)> can_check =
|
||||
[&](std::string_view soname, i64 depth) -> bool {
|
||||
if (depth == deps.size() + 1) {
|
||||
// A library with a circular dependency is rare but does exist and
|
||||
// can be loaded without issue, so we need to handle such a case.
|
||||
return true;
|
||||
}
|
||||
auto it = deps.find(soname);
|
||||
if (it == deps.end())
|
||||
return false;
|
||||
for (std::string_view needed : it->second)
|
||||
if (!can_check(needed, depth + 1))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
// Skip the file if it depends on a file that we know nothing about.
|
||||
// This is because missing symbols may be provided by that unknown file.
|
||||
for (std::string_view needed : file->get_dt_needed(ctx))
|
||||
if (!sonames.contains(needed))
|
||||
return;
|
||||
if (!can_check(file->soname, 0))
|
||||
return;
|
||||
|
||||
// Check if all undefined symbols have been resolved.
|
||||
for (i64 i = 0; i < file->elf_syms.size(); i++) {
|
||||
@@ -1153,7 +1159,7 @@ void check_shlib_undefined(Context<E> &ctx) {
|
||||
// SharedFile<E>::mark_live_objects just for this pass. Therefore,
|
||||
// remove unneeded DSOs from the list now.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
file->is_reachable = false;
|
||||
file->is_reachable = !file->as_needed;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms())
|
||||
@@ -1169,10 +1175,6 @@ template <typename E>
|
||||
void check_symbol_types(Context<E> &ctx) {
|
||||
Timer t(ctx, "check_symbol_types");
|
||||
|
||||
std::vector<InputFile<E> *> files;
|
||||
append(files, ctx.objs);
|
||||
append(files, ctx.dsos);
|
||||
|
||||
auto canonicalize = [](u32 ty) -> u32 {
|
||||
if (ty == STT_GNU_IFUNC)
|
||||
return STT_FUNC;
|
||||
@@ -1181,23 +1183,35 @@ void check_symbol_types(Context<E> &ctx) {
|
||||
return ty;
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(files.begin(), files.end(), [&](InputFile<E> *file) {
|
||||
auto check = [&](InputFile<E> &file, Symbol<E> &sym,
|
||||
const ElfSym<E> &esym1, const ElfSym<E> &esym2) {
|
||||
if (sym.file && sym.file != &file &&
|
||||
esym1.st_type != STT_NOTYPE && esym2.st_type != STT_NOTYPE &&
|
||||
canonicalize(esym1.st_type) != canonicalize(esym2.st_type)) {
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
<< ">>> defined in " << *sym.file << " as "
|
||||
<< stt_to_string<E>(esym1.st_type) << '\n'
|
||||
<< ">>> defined in " << file << " as "
|
||||
<< stt_to_string<E>(esym2.st_type);
|
||||
}
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++)
|
||||
if (Symbol<E> *sym = file->symbols[i];
|
||||
sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
if (!sym.file || sym.file == file)
|
||||
continue;
|
||||
if (Symbol<E> *sym = file->symbols[i];
|
||||
sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
|
||||
const ElfSym<E> &esym1 = sym.esym();
|
||||
const ElfSym<E> &esym2 = file->elf_syms[i];
|
||||
|
||||
if (esym1.st_type != STT_NOTYPE && esym2.st_type != STT_NOTYPE &&
|
||||
canonicalize(esym1.st_type) != canonicalize(esym2.st_type)) {
|
||||
Warn(ctx) << "symbol type mismatch: " << sym << '\n'
|
||||
<< ">>> defined in " << *sym.file << " as "
|
||||
<< stt_to_string<E>(esym1.st_type) << '\n'
|
||||
<< ">>> defined in " << *file << " as "
|
||||
<< stt_to_string<E>(esym2.st_type);
|
||||
}
|
||||
if (Symbol<E> *sym = file->symbols2[i])
|
||||
if (sym->file && sym->file != file)
|
||||
check(*file, *sym, sym->esym(), file->elf_syms[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,8 +22,8 @@ void bar();
|
||||
int main() { bar(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/c.o $t/b.so
|
||||
$CC -B. -o $t/exe1 $t/c.o $t/b.so -Wl,--allow-shlib-undefined
|
||||
readelf --dynamic $t/exe1 | grep 'Dependency audit library:..*/a.so'
|
||||
|
||||
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo
|
||||
$CC -B. -o $t/exe2 $t/c.o $t/b.so -Wl,--depaudit=foo -Wl,--allow-shlib-undefined
|
||||
readelf --dynamic $t/exe2 | grep 'Dependency audit library:..*foo:.*/a.so'
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
|
||||
void foo();
|
||||
void bar() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc - -L$t -lbar
|
||||
void baz();
|
||||
void foo() { baz(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -c -xc -
|
||||
int bar();
|
||||
int main() { bar(); }
|
||||
EOF
|
||||
|
||||
not $CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lfoo -lbar |&
|
||||
grep 'libfoo.so: --no-allow-shlib-undefined: undefined symbol: baz'
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
|
||||
void foo();
|
||||
void bar() { foo(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbaz.so -xc - -L$t -lbar
|
||||
void bar();
|
||||
void baz() { bar(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -c -xc -
|
||||
int baz();
|
||||
int main() { baz(); }
|
||||
EOF
|
||||
|
||||
mv $t/libfoo.so $t/libfoo.so.bak
|
||||
echo | $CC -B. -shared -fPIC -o $t/libfoo.so -xc -
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lbar -lbaz
|
||||
|
||||
not $CC -B. -o $t/exe2 $t/a.o -Wl,--no-allow-shlib-undefined \
|
||||
-L$t -lfoo -lbar -lbaz |& grep 'no-allow-shlib-undefined: undefined symbol: foo'
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC
|
||||
__asm__(".symver foo_,foo@@VERSION");
|
||||
void foo_() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/c.version
|
||||
VERSION { global: foo; local: *; };
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/libfoo.so $t/a.o
|
||||
$CC -B. -shared -o $t/libbar.so $t/b.o -Wl,--version-script=$t/c.version
|
||||
|
||||
cat <<EOF | $CC -o $t/d.o -c -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
__asm__(".symver foo,foo@VERSION");
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/d.o -L$t -lfoo -lbar
|
||||
@@ -9,5 +9,4 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/a.so $t/a.o |&
|
||||
grep 'warning: symbol type mismatch: times'
|
||||
$CC -B. -shared -o $t/b.so $t/a.o |& grep 'warning: symbol type mismatch: times'
|
||||
Reference in new issue
Block a user