Skip to content

Commit

Permalink
Fix --dynamic-list for DSOs
Browse files Browse the repository at this point in the history
--dynamic-list, --export-dynamic-symbol and --export-dynamic-symbol-list
have different semantics for executables and DSOs. If the output is an
executable, they specify a list of symbols that are to be exported.
If the output is a shared object, they specify the list of symbols that
are to be interposable.

mold havne't implemented the latter semantics. This commit fixes that
issue.

Fixes #1071
  • Loading branch information
rui314 committed Nov 12, 2023
1 parent b59911b commit da3f5dd
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 77 deletions.
25 changes: 10 additions & 15 deletions elf/cmdline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1104,21 +1104,21 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("no-keep-memory")) {
} else if (read_arg("max-cache-size")) {
} else if (read_arg("version-script")) {
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
// are treated as positional arguments even though they are actually not
// positional. This is because linker scripts (a positional argument)
// can also specify a version script, and it's better to consolidate
// parsing in read_input_files. In particular, version scripts can
// modify ctx.default_version which we initialize *after* parsing
// non-positional args, so the parsing cannot be done right here.
// --version-script is treated as positional arguments even though
// they are actually not positional. This is because linker scripts
// (a positional argument) can also specify a version script, and
// it's better to consolidate parsing in read_input_files. In
// particular, version scripts can modify ctx.default_version which
// we initialize *after* parsing non-positional args, so the parsing
// cannot be done right here.
remaining.push_back("--version-script=" + std::string(arg));
} else if (read_arg("dynamic-list")) {
ctx.arg.Bsymbolic = true;
remaining.push_back("--dynamic-list=" + std::string(arg));
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
} else if (read_arg("export-dynamic-symbol")) {
remaining.push_back("--export-dynamic-symbol=" + std::string(arg));
ctx.dynamic_list_patterns.push_back({arg, "<command line>"});
} else if (read_arg("export-dynamic-symbol-list")) {
remaining.push_back("--export-dynamic-symbol-list=" + std::string(arg));
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
} else if (read_flag("as-needed")) {
remaining.push_back("--as-needed");
} else if (read_flag("no-as-needed")) {
Expand Down Expand Up @@ -1228,11 +1228,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (char *env = getenv("MOLD_REPRO"); env && env[0])
ctx.arg.repro = true;

if (ctx.arg.shared || ctx.arg.export_dynamic)
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.default_version = VER_NDX_LOCAL;

if (ctx.arg.default_symver) {
std::string ver = ctx.arg.soname.empty() ?
filepath(ctx.arg.output).filename().string() : std::string(ctx.arg.soname);
Expand Down
35 changes: 20 additions & 15 deletions elf/linker-script.cc
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,6 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,

if (tok[0] == "*") {
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
ctx.default_version_from_version_script = true;
} else if (is_global) {
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
ver_str, ver_idx, is_cpp});
Expand Down Expand Up @@ -367,7 +366,9 @@ void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
}

template <typename E>
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
void read_dynamic_list_commands(Context<E> &ctx,
std::vector<DynamicPattern> &result,
std::span<std::string_view> &tok,
bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (tok[0] == "extern") {
Expand All @@ -376,48 +377,52 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, false);
read_dynamic_list_commands(ctx, result, tok, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, true);
read_dynamic_list_commands(ctx, result, tok, true);
}

tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
continue;
}

if (tok[0] == "*")
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
"global", VER_NDX_GLOBAL, is_cpp});

result.push_back({unquote(tok[0]), "", is_cpp});
tok = skip(ctx, tok.subspan(1), ";");
}
}

template <typename E>
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::vector<DynamicPattern>
parse_dynamic_list(Context<E> &ctx, std::string_view path) {
std::string_view contents =
MappedFile<Context<E>>::must_open(ctx, std::string(path))->get_contents();
std::vector<std::string_view> vec = tokenize(ctx, contents);
std::span<std::string_view> tok = vec;
std::vector<DynamicPattern> result;

tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, false);
read_dynamic_list_commands(ctx, result, tok, false);
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");

if (!tok.empty())
SyntaxError(ctx, tok[0]) << "trailing garbage token";

for (DynamicPattern &p : result)
p.source = path;

return result;
}

using E = MOLD_TARGET;

template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
template std::string_view get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
template void parse_version_script(Context<E> &, MappedFile<Context<E>> *);
template void parse_dynamic_list(Context<E> &, MappedFile<Context<E>> *);
template std::vector<DynamicPattern> parse_dynamic_list(Context<E> &, std::string_view);


} // namespace mold::elf
16 changes: 0 additions & 16 deletions elf/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -299,22 +299,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
if (!mf)
Fatal(ctx) << "--version-script: file not found: " << arg;
parse_version_script(ctx, mf);
} else if (remove_prefix(arg, "--dynamic-list=")) {
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
parse_dynamic_list(ctx, mf);
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
if (arg == "*")
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
"global", VER_NDX_GLOBAL, false});
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
parse_dynamic_list(ctx, mf);
} else if (arg == "--push-state") {
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
ctx.in_lib});
Expand Down
17 changes: 11 additions & 6 deletions elf/mold.h
Original file line number Diff line number Diff line change
Expand Up @@ -1281,8 +1281,15 @@ get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf);
template <typename E>
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf);

struct DynamicPattern {
std::string_view pattern;
std::string_view source;
bool is_cpp = false;
};

template <typename E>
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf);
std::vector<DynamicPattern>
parse_dynamic_list(Context<E> &ctx, std::string_view path);

//
// lto.cc
Expand Down Expand Up @@ -1733,13 +1740,11 @@ struct Context {
} arg;

std::vector<VersionPattern> version_patterns;
u16 default_version = VER_NDX_GLOBAL;
std::vector<DynamicPattern> dynamic_list_patterns;
i64 default_version = -1;
i64 page_size = E::page_size;
std::optional<int> global_lock_fd;

// true if default_version is set by a wildcard in version script.
bool default_version_from_version_script = false;

// Reader context
bool as_needed = false;
bool whole_archive = false;
Expand Down Expand Up @@ -2034,7 +2039,7 @@ class Symbol {
i32 sym_idx = -1;

i32 aux_idx = -1;
u16 ver_idx = 0;
i32 ver_idx = -1;

// `flags` has NEEDS_ flags.
Atomic<u8> flags = 0;
Expand Down
8 changes: 6 additions & 2 deletions elf/output-chunks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2550,8 +2550,12 @@ void VerdefSection<E>::construct(Context<E> &ctx) {
for (std::string_view verstr : ctx.arg.version_definitions)
write(verstr, idx++, 0);

for (Symbol<E> *sym : std::span<Symbol<E> *>(ctx.dynsym->symbols).subspan(1))
ctx.versym->contents[sym->get_dynsym_idx(ctx)] = sym->ver_idx;
for (Symbol<E> *sym : std::span<Symbol<E> *>(ctx.dynsym->symbols).subspan(1)) {
i64 ver = sym->ver_idx;
if (ver == -1)
ver = VER_NDX_GLOBAL;
ctx.versym->contents[sym->get_dynsym_idx(ctx)] = ver;
}
}

template <typename E>
Expand Down
122 changes: 101 additions & 21 deletions elf/passes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1612,16 +1612,16 @@ template <typename E>
void apply_version_script(Context<E> &ctx) {
Timer t(ctx, "apply_version_script");

// If all patterns are simple (i.e. not containing any meta-
// characters and is not a C++ name), we can simply look up
// symbols.
auto is_simple = [&] {
for (VersionPattern &v : ctx.version_patterns)
if (v.is_cpp || v.pattern.find_first_of("*?[") != v.pattern.npos)
return false;
return true;
};

// If all patterns are simple (i.e. not containing any meta-
// characters and is not a C++ name), we can simply look up
// symbols.
if (is_simple()) {
for (VersionPattern &v : ctx.version_patterns) {
Symbol<E> *sym = get_symbol(ctx, v.pattern);
Expand Down Expand Up @@ -1747,44 +1747,124 @@ void compute_import_export(Context<E> &ctx) {
if (!ctx.arg.shared) {
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN) {
if (sym->ver_idx != VER_NDX_LOCAL ||
!ctx.default_version_from_version_script) {
std::scoped_lock lock(sym->mu);
sym->is_exported = true;
}
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN &&
sym->ver_idx != VER_NDX_LOCAL) {
std::scoped_lock lock(sym->mu);
sym->is_exported = true;
}
}
});
}

auto should_export = [&](Symbol<E> &sym) {
if (sym.visibility == STV_HIDDEN)
return false;

switch (sym.ver_idx) {
case -1:
if (ctx.arg.shared)
return !((ObjectFile<E> *)sym.file)->exclude_libs;
return ctx.arg.export_dynamic;
case VER_NDX_LOCAL:
return false;
default:
return true;
}
};

// Export symbols that are not hidden or marked as local.
// We also want to mark imported symbols as such.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (!sym->file || sym->visibility == STV_HIDDEN ||
sym->ver_idx == VER_NDX_LOCAL)
continue;

// If we are using a symbol in a DSO, we need to import it at runtime.
if (sym->file != file && sym->file->is_dso && !sym->is_absolute()) {
std::scoped_lock lock(sym->mu);
sym->is_imported = true;
// If we are using a symbol in a DSO, we need to import it.
if (sym->file && sym->file->is_dso) {
if (!sym->is_absolute()) {
std::scoped_lock lock(sym->mu);
sym->is_imported = true;
}
continue;
}

// If we are creating a DSO, all global symbols are exported by default.
if (sym->file == file) {
std::scoped_lock lock(sym->mu);
// If we have a definition of a symbol, we may want to export it.
if (sym->file == file && should_export(*sym)) {
sym->is_exported = true;

if (ctx.arg.shared && sym->visibility != STV_PROTECTED &&
// Exported symbols are marked as imported as well by default
// for DSOs.
if (ctx.arg.shared &&
sym->visibility != STV_PROTECTED &&
!ctx.arg.Bsymbolic &&
!(ctx.arg.Bsymbolic_functions && sym->get_type() == STT_FUNC))
sym->is_imported = true;
}
}
});


// Apply --dynamic-list, --export-dynamic-symbol and
// --export-dynamic-symbol-list options.
//
// The semantics of these options vary depending on whether we are
// creating an executalbe or a shared object.
//
// For executable, matched symbols are exported.
//
// For shared objects, matched symbols are imported if it is already
// exported so that they are interposable. In other words, symbols
// that did not match will be bound locally within the output file,
// effectively turning them into protected symbols.
MultiGlob matcher;
MultiGlob cpp_matcher;

auto handle_match = [&](Symbol<E> *sym) {
if (ctx.arg.shared) {
if (sym->is_exported)
sym->is_imported = true;
} else {
if (sym->file && !sym->file->is_dso && sym->visibility != STV_HIDDEN)
sym->is_exported = true;
}
};

for (DynamicPattern &p : ctx.dynamic_list_patterns) {
if (p.is_cpp) {
if (!cpp_matcher.add(p.pattern, 1))
Fatal(ctx) << p.source << ": invalid dynamic list entry: "
<< p.pattern;
continue;
}

if (p.pattern.find_first_of("*?[") != p.pattern.npos) {
if (!matcher.add(p.pattern, 1))
Fatal(ctx) << p.source << ": invalid dynamic list entry: "
<< p.pattern;
continue;
}

handle_match(get_symbol(ctx, p.pattern));
}

if (!matcher.empty() || !cpp_matcher.empty()) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file != file)
continue;
if (ctx.arg.shared && !sym->is_exported)
continue;

std::string_view name = sym->name();

if (matcher.find(name)) {
handle_match(sym);
} else if (!cpp_matcher.empty()) {
if (std::optional<std::string_view> s = cpp_demangle(name))
name = *s;
if (cpp_matcher.find(name))
handle_match(sym);
}
}
});
}
}

// Compute the "address-taken" bit for each input section.
Expand Down

0 comments on commit da3f5dd

Please sign in to comment.