mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Report duplicate symbols as ld-prime does, and accept -allow_dead_duplicates
ld-prime reports a symbol live objects define strongly more than once as "duplicate symbol '<name>' in:" followed by the real path of each file defining it (an archive member as "lib.a[N](member.o)"), then fails with "<N> duplicate symbols". Under -dead_strip it lists only the symbols whose kept definition is live but counts them all, and -allow_dead_duplicates lets one stay whose other definitions are dead. -duplicate_symbols warning|error is read and has nothing to act on. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
fdb4247cd8
commit
a9c2e988e2
4 files changed
+113
-36
No files matched your search
+13
-1
@@ -425,6 +425,9 @@ pub struct Args {
|
||||
/// -keep_duplicate / -keep_duplicates_list: the functions (local
|
||||
/// ones too) function deduplication leaves alone.
|
||||
pub keep_duplicates: Glob,
|
||||
/// -allow_dead_duplicates: a symbol defined more than once is fine
|
||||
/// if -dead_strip removes all its definitions but the one kept.
|
||||
pub allow_dead_duplicates: bool,
|
||||
/// -w: suppress warnings.
|
||||
pub suppress_warnings: bool,
|
||||
pub fatal_warnings: bool,
|
||||
@@ -722,6 +725,7 @@ impl Default for Args {
|
||||
force_weak: Glob::new(),
|
||||
force_not_weak: Glob::new(),
|
||||
keep_duplicates: Glob::new(),
|
||||
allow_dead_duplicates: false,
|
||||
suppress_warnings: false,
|
||||
fatal_warnings: false,
|
||||
demangle: false,
|
||||
@@ -1572,7 +1576,9 @@ pub(crate) fn missing_argument(opt: &str) -> String {
|
||||
"-force_symbols_not_weak_list" => {
|
||||
return "-force_symbols_weak_list missing <path>".to_string();
|
||||
}
|
||||
"-read_only_relocs" | "-arch_variant_lto_cache_mismatch" => "missing <option>",
|
||||
"-read_only_relocs" | "-arch_variant_lto_cache_mismatch" | "-duplicate_symbols" => {
|
||||
"missing <option>"
|
||||
}
|
||||
"-target" => "missing <target-triple>",
|
||||
"-alias" => "missing <real-name> <alias-name>",
|
||||
"-dylib_file" => "missing <path:path>",
|
||||
@@ -2407,6 +2413,12 @@ pub fn parse_args(target: &TargetTraits, cmdline: &[Cow<'_, OsStr>]) -> Args {
|
||||
keep_duplicates.add(pat.as_bytes(), 0);
|
||||
}
|
||||
}
|
||||
b"-allow_dead_duplicates" => args.allow_dead_duplicates = true,
|
||||
// For duplicate symbols ld-prime would only warn of, which
|
||||
// it has no more: it takes the treatment and does nothing.
|
||||
b"-duplicate_symbols" => {
|
||||
parse_treatment(name, next_arg(&mut i, name), false);
|
||||
}
|
||||
|
||||
b"-dyld_env" => {
|
||||
let arg = next_arg(&mut i, name).as_bytes();
|
||||
|
||||
+52
-33
@@ -2831,16 +2831,18 @@ pub fn coalesce_weak_defs<E: Target>(ctx: &mut Context<E>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports two live strong definitions of one name. Resolution keeps
|
||||
/// the first strong definition it meets; a strong definition in any
|
||||
/// other live object that lost to it is an error (a weak or common one
|
||||
/// yields quietly). Reported after resolution settles, sorted by name,
|
||||
/// so the messages are deterministic: mold's
|
||||
/// check_duplicate_symbols. As in ld-prime, a final link reports them
|
||||
/// only if no symbol is undefined, and only of the symbols -dead_strip
|
||||
/// leaves live.
|
||||
/// Reports the symbols live objects define strongly more than once, as
|
||||
/// ld-prime does once resolution settles (and, in a final link, no
|
||||
/// symbol is undefined): each with the files that define it, then their
|
||||
/// number. Resolution keeps the first strong definition it meets; a weak
|
||||
/// or common one yields quietly. Under -dead_strip only a symbol whose
|
||||
/// kept definition is live is reported, and -allow_dead_duplicates lets
|
||||
/// one stay whose other definitions are all dead; ld-prime counts the
|
||||
/// symbols it doesn't report in the number all the same. It lists them
|
||||
/// in no stable order; mold sorts them by name.
|
||||
pub fn check_duplicate_symbols<E: Target>(ctx: &Context<E>) {
|
||||
let mut duplicates: Vec<(crate::symbol::SymbolId, usize)> = ctx
|
||||
// Each losing definition, and whether it is live.
|
||||
let mut losers: Vec<(SymbolId, usize, bool)> = ctx
|
||||
.objs
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
@@ -2852,37 +2854,54 @@ pub fn check_duplicate_symbols<E: Target>(ctx: &Context<E>) {
|
||||
|| !nlist.is_extern()
|
||||
|| !matches!(nlist.n_type(), N_SECT | N_ABS)
|
||||
|| nlist.n_desc & N_WEAK_DEF != 0
|
||||
|| !matches!(ctx.symbols[sym_id].file(), Some(FileId::Obj(owner)) if owner as usize != obj_idx)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let sym = &ctx.symbols[sym_id];
|
||||
match sym.file() {
|
||||
Some(FileId::Obj(owner))
|
||||
if owner as usize != obj_idx
|
||||
&& sym
|
||||
.input_section()
|
||||
.is_none_or(|i| ctx.isecs[i as usize].is_alive()) =>
|
||||
{
|
||||
Some((sym_id, obj_idx))
|
||||
}
|
||||
let isec = match nlist.n_type() {
|
||||
N_SECT => input_files::find_symbol_subsec(
|
||||
&ctx.isecs,
|
||||
&obj.subsecs,
|
||||
nlist.n_sect,
|
||||
nlist.n_value,
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
let live = isec.is_none_or(|(isec, _)| ctx.isecs[isec].is_alive());
|
||||
Some((sym_id, obj_idx, live))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
duplicates.sort_by_key(|&(sym_id, obj_idx)| (ctx.symbols[sym_id].name(), obj_idx));
|
||||
duplicates.dedup();
|
||||
for (sym_id, obj_idx) in duplicates {
|
||||
let prev = match ctx.symbols[sym_id].file() {
|
||||
Some(FileId::Obj(idx)) => file_display(&ctx.objs[idx as usize]),
|
||||
_ => "?".into(),
|
||||
};
|
||||
error!(
|
||||
"duplicate symbol: {}: {}: {}",
|
||||
file_display(&ctx.objs[obj_idx]),
|
||||
prev,
|
||||
ctx.symbols[sym_id]
|
||||
);
|
||||
if losers.is_empty() {
|
||||
return;
|
||||
}
|
||||
losers.sort_by_key(|&(sym_id, obj_idx, _)| (ctx.symbols[sym_id].name(), obj_idx));
|
||||
losers.dedup();
|
||||
|
||||
let mut count = 0;
|
||||
let mut reported = false;
|
||||
for group in losers.chunk_by(|a, b| a.0 == b.0) {
|
||||
let id = group[0].0;
|
||||
let sym = &ctx.symbols[id];
|
||||
if ctx.args.allow_dead_duplicates && group.iter().all(|&(_, _, live)| !live) {
|
||||
continue;
|
||||
}
|
||||
count += 1;
|
||||
if !sym.input_section().is_none_or(|isec| ctx.isecs[isec as usize].is_alive()) {
|
||||
continue;
|
||||
}
|
||||
reported = true;
|
||||
let Some(FileId::Obj(winner)) = sym.file() else { continue };
|
||||
let mut files: Vec<usize> = group.iter().map(|&(_, obj, _)| obj).collect();
|
||||
files.push(winner as usize);
|
||||
files.sort_unstable();
|
||||
crate::error::notice(format_args!("duplicate symbol '{sym}' in:"));
|
||||
for obj in files {
|
||||
crate::error::notice(format_args!(" {}", resolved_file_name(ctx.objs[obj].mf)));
|
||||
}
|
||||
}
|
||||
if reported {
|
||||
error!("{count} duplicate symbols");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
source "$(dirname "$0")"/common.inc
|
||||
|
||||
# Under -dead_strip ld-prime reports only the duplicate symbols whose
|
||||
# kept definition is live, though it counts all, and
|
||||
# -allow_dead_duplicates lets one stay whose other definitions are all
|
||||
# dead.
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int dup1() { return 1; }
|
||||
int dup2() { return 2; }
|
||||
int main() { return dup1(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc -
|
||||
int dup1() { return 3; }
|
||||
int dup2() { return 4; }
|
||||
EOF
|
||||
|
||||
not $CC --ld-path=$mold -o $t/exe $t/a.o $t/b.o -Wl,-dead_strip 2> $t/log
|
||||
grep "duplicate symbol '_dup1' in:" $t/log
|
||||
not grep -q "duplicate symbol '_dup2'" $t/log
|
||||
grep -q '2 duplicate symbols' $t/log
|
||||
|
||||
$CC --ld-path=$mold -o $t/exe $t/a.o $t/b.o -Wl,-dead_strip -Wl,-allow_dead_duplicates
|
||||
nm $t/exe | grep -q _dup1
|
||||
|
||||
# Without -dead_strip nothing is dead.
|
||||
not $CC --ld-path=$mold -o $t/exe $t/a.o $t/b.o -Wl,-allow_dead_duplicates 2> $t/log
|
||||
grep -q '2 duplicate symbols' $t/log
|
||||
|
||||
# -duplicate_symbols is taken, and changes nothing.
|
||||
not $CC --ld-path=$mold -o $t/exe $t/a.o $t/b.o -Wl,-dead_strip \
|
||||
-Wl,-duplicate_symbols,warning 2> $t/log
|
||||
grep -q '2 duplicate symbols' $t/log
|
||||
not $mold -o $t/exe $t/a.o -duplicate_symbols suppress 2> $t/log
|
||||
grep -q -- '-duplicate_symbols invalid option (warning | error)' $t/log
|
||||
not $mold -o $t/exe $t/a.o -duplicate_symbols 2> $t/log
|
||||
grep -q -- '-duplicate_symbols missing <option>' $t/log
|
||||
@@ -11,7 +11,15 @@ int main() {}
|
||||
EOF
|
||||
|
||||
! $CC --ld-path=$mold -o $t/exe $t/a.o $t/b.o 2> $t/log || false
|
||||
grep -q 'duplicate symbol: .*/b.o: .*/a.o: _hello' $t/log
|
||||
# (ld-prime lists the files in no stable order.)
|
||||
grep -v '^+' $t/log | sed -E 's|^(ld: \|mold: error: )||; s|/.*/||' > $t/msgs
|
||||
cat > $t/expected <<EOF
|
||||
duplicate symbol '_hello' in:
|
||||
a.o
|
||||
b.o
|
||||
1 duplicate symbols
|
||||
EOF
|
||||
grep -v 'linker command failed' $t/msgs | sort | diff - <(sort $t/expected)
|
||||
|
||||
# A relocatable link must fail before publishing an erroneous object.
|
||||
# Exercise both the default parent/child protocol and the debugger mode.
|
||||
@@ -22,7 +30,7 @@ for no_fork in no yes; do
|
||||
else
|
||||
! $mold -r -arch $ARCH -o $t/merged.o $t/a.o $t/b.o 2> $t/log || false
|
||||
fi
|
||||
grep -q 'duplicate symbol: .*_hello' $t/log
|
||||
grep -q "duplicate symbol '_hello' in:" $t/log
|
||||
test ! -e $t/merged.o
|
||||
done
|
||||
|
||||
|
||||
Reference in new issue
Block a user