1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
|
https://bugs.gentoo.org/917089
https://github.com/microsoft/mimalloc/issues/360
https://github.com/rui314/mold/issues/1071
https://github.com/rui314/mold/commit/da3f5dd4ecf4faaba466ba41c7c30ba4f8f73bfd
From da3f5dd4ecf4faaba466ba41c7c30ba4f8f73bfd Mon Sep 17 00:00:00 2001
From: Rui Ueyama <ruiu@cs.stanford.edu>
Date: Sat, 11 Nov 2023 17:59:54 +0900
Subject: [PATCH] Fix --dynamic-list for DSOs
--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 https://github.com/rui314/mold/issues/1071
---
elf/cmdline.cc | 25 +++-----
elf/linker-script.cc | 35 ++++++-----
elf/main.cc | 16 -----
elf/mold.h | 17 +++--
elf/output-chunks.cc | 8 ++-
elf/passes.cc | 122 +++++++++++++++++++++++++++++-------
test/elf/dynamic-list4.sh | 44 +++++++++++++
test/elf/version-script6.sh | 4 +-
8 files changed, 194 insertions(+), 77 deletions(-)
create mode 100755 test/elf/dynamic-list4.sh
diff --git a/elf/cmdline.cc b/elf/cmdline.cc
index 6bc13a300..6c06b4b14 100644
--- a/elf/cmdline.cc
+++ b/elf/cmdline.cc
@@ -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")) {
@@ -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);
diff --git a/elf/linker-script.cc b/elf/linker-script.cc
index 4bdc19e7c..7ad500bb8 100644
--- a/elf/linker-script.cc
+++ b/elf/linker-script.cc
@@ -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});
@@ -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") {
@@ -376,11 +377,11 @@ 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, "}");
@@ -388,29 +389,32 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
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;
@@ -418,6 +422,7 @@ 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
diff --git a/elf/main.cc b/elf/main.cc
index c4f3cd6ff..6df00cfe9 100644
--- a/elf/main.cc
+++ b/elf/main.cc
@@ -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});
diff --git a/elf/mold.h b/elf/mold.h
index 7ff7c2e6f..d593f6840 100644
--- a/elf/mold.h
+++ b/elf/mold.h
@@ -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
@@ -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;
@@ -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;
diff --git a/elf/output-chunks.cc b/elf/output-chunks.cc
index f44d448ac..00d5538df 100644
--- a/elf/output-chunks.cc
+++ b/elf/output-chunks.cc
@@ -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>
diff --git a/elf/passes.cc b/elf/passes.cc
index c6ee0f66b..8c7d5d0f5 100644
--- a/elf/passes.cc
+++ b/elf/passes.cc
@@ -1612,9 +1612,6 @@ 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)
@@ -1622,6 +1619,9 @@ void apply_version_script(Context<E> &ctx) {
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);
@@ -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.
diff --git a/test/elf/dynamic-list4.sh b/test/elf/dynamic-list4.sh
new file mode 100755
index 000000000..83d88887e
--- /dev/null
+++ b/test/elf/dynamic-list4.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+. $(dirname $0)/common.inc
+
+cat <<EOF | $CC -o $t/a.o -c -xc - -fPIC
+#include <stdio.h>
+
+void foo() { printf("foo1 "); }
+void bar() { printf("bar1 "); }
+void baz() { printf("baz1 "); }
+
+void print() {
+ foo();
+ bar();
+ baz();
+ printf("\n");
+}
+EOF
+
+cat <<EOF > $t/dyn
+{ foo; bar; };
+EOF
+
+$CC -B. -shared -o $t/b.so $t/a.o -Wl,--dynamic-list=$t/dyn
+
+cat <<EOF | $CC -o $t/c.o -c -xc - -fPIC
+#include <stdio.h>
+void foo() { printf("foo2 "); }
+void bar() { printf("bar2 "); }
+void baz() { printf("baz2 "); }
+EOF
+
+$CC -B. -shared -o $t/d.so $t/c.o
+
+cat <<EOF | $CC -o $t/e.o -c -xc -
+#include <stdio.h>
+void print();
+int main() { print(); }
+EOF
+
+$CC -B. -o $t/exe1 $t/e.o -Wl,-push-state,-no-as-needed $t/b.so -Wl,-pop-state
+$QEMU $t/exe1 | grep -q 'foo1 bar1 baz1'
+
+$CC -B. -o $t/exe2 $t/e.o -Wl,-push-state,-no-as-needed $t/d.so $t/b.so -Wl,-pop-state
+$QEMU $t/exe2 | grep -q 'foo2 bar2 baz1'
diff --git a/test/elf/version-script6.sh b/test/elf/version-script6.sh
index 74e2f9a89..44f809ef3 100755
--- a/test/elf/version-script6.sh
+++ b/test/elf/version-script6.sh
@@ -9,10 +9,10 @@ EOF
cat <<EOF | $CXX -fPIC -c -o $t/b.o -xc -
int foo = 5;
int bar = 6;
+int quux = 100;
EOF
-$CC -B. -shared -Wl,--version-script=$t/a.ver \
- -o $t/c.so $t/b.o
+$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
cat <<'EOF' > $t/d.ver
VER_Y1 { local; *; };
|