I have some code that crashes when using lld to link a shared object. So essentially it boils down to an object file having a common symbol, and a shared object having the definition of said object. Look here:
$ nm libfckit_lite.so | grep "\$data_init\$fckit_owned_object_module_"
0000000000008700 D $data_init$fckit_owned_object_module_
$ nm atlas_object.F90.o | grep "\$data_init\$fckit_owned_object_module_"
0000000000000238 C $data_init$fckit_owned_object_module_
When linking and creating a shared object this is the result(I also added the final executable):
$ nm libatlas_lite_lld.so | grep "\$data_init\$fckit_owned_object_module_"
000000000000e240 B $data_init$fckit_owned_object_module_
$ nm libatlas_lite_bfd.so | grep "\$data_init\$fckit_owned_object_module_"
U $data_init$fckit_owned_object_module_
$ nm exe_lld | grep "\$data_init\$fckit_owned_object_module_"
0000000000404700 B $data_init$fckit_owned_object_module_
$ nm exe_bfd | grep "\$data_init\$fckit_owned_object_module_"
U $data_init$fckit_owned_object_module_
It’s also worth noting that mold seems to behave like bfd, and leave the common symbol undefined. Is this an lld issue?
That is a difficult question to answer as far as I know there isn’t anything that I know of in the ELF specification that covers this case, which would put it in the realm of implementation defined behaviour. Whether this is an lld issue for not conforming to the same implementation defined behaviour as GNU ld and mold, or a bug in the program for relying upon it is a matter of debate. LLD normally likes to be compatible with GNU ld where possible so it could be worth raising a github issue for it.
Normally a non-common non-weak definition with the same name as a common symbol will take precedent. So by that logic a non-common definition in a shared object would take precedence over a common definition from a relocatable object. This does run counter to the usual symbol search path rules that a definition in an executable takes precedent over a definition in a shared library.
I think we need the linker command line and input files to answer your question (use LLD_REPRODUCE or -Wl,--reproduce=, and upload the reproduce tarball), because (a) the order of libfckit_lite.so and atlas_object.F90.o and (b) whether atlas_object.F90.o is in --start-lib/--end-lib change the result. We need more information to know why exe_bfd keeps $data_init$fckit_owned_object_module_ undefined.
In ELF, for a relocatable object file definition, the precedence is STB_GLOBAL > COMMON > STB_WEAK.
If we have a defined symbol ins a relocatable (whether it is STB_GLOBAL, COMMON, or STB_WEAK), the shared object definition will be ignored except its st_size (see below). I think lld implements the correct semantics.
In regards to mold (bfd code isn’t the easiest to follow, at least for myself), by the looks of this code, it doesn’t seem like they do that rule you mentioned. But this is the portion of code that switches the definition of the COMMON symbol from the .o file to the .so.
When a common symbol is merged with a dynamic symbol, GNU ld displays different behaviors depending on whether the dynamic symbol is relative to a BSS section.
See the definition of newdyncommon in bfd/elflink.c:_bfd_elf_merge_symbol (originally bfd/elflink.h from “19990502 sourceware import”).
as -o a.o <<< '.comm x,8,4'
as -o bss.o <<< '.bss; .globl x; x: .quad 0; .size x, .-x' && ld -shared bss.o -o bss.so
as -o data.o <<< '.data; .globl x; x: .quad 0; .size x, .-x' && ld -shared data.o -o data.so
ld.bfd a.o bss.so -o bfd.bss # x is defined
ld.bfd a.o data.so -o bfd.data # x is undefined
ld.lld a.o bss.so -o lld.bss # x is defined
ld.lld a.o data.so -o lld.data # x is defined
This special case does not make sense to me. In a relocatable object file, a COMMON symbol is stronger than a weak symbol.
Since a weak symbol takes precedence over a dynamic symbol, a COMMON symbol should as well.
gold and ld.lld unconditionally let the common symbol win.
Let’s say the st_size is expected. If the DSO’s x references bind to the executable (symbol interposition), whether or not x is defined or undefined in the executable does not matter.
Would you mind elaborating a bit more on symbol precedence regarding dynamic symbols? I’m just a bit confused because I thought this symbol would be a GLOBAL symbol based on this readelf output:
77: 0000000000008700 568 OBJECT GLOBAL DEFAULT 19 $data_init$fckit_owned_object_module_
And based on your blog post, wouldn’t this have more precedence, because of: STB_GLOBAL > COMMON > STB_WEAK?
My post says: “In ELF, for a definition in a relocatable object file, the precedence is STB_GLOBAL > COMMON > STB_WEAK.”
The interaction between a relocatable object file definition and a dynamic symbol (definition in a DSO) is not well specified in the generic ABI. They belong to implementation details. I have some notes on Symbol processing | MaskRay " Relocatable object file overriding shared object" has the relevant discussion.
If we have COMMON symbols in a.o and b.o, with st_size(x in a.o) > st_size(x in b.o). When linking a.o b.so, it’s dangerous to leave x undefined in the executable since there would likely be out-of-bounds access. If we keep x defined and apply the max st_size rule, with symbol interposition, accesses to x will be correct.
I think GNU ld’s behavior is inconsistent (due it changing behavior whether x is in BSS or not), but we probably should not say its behavior is incorrect. This interaction with dynamic symbols is largely implementation defined. In this case we could say it’s a user fault to give linkers fishy input.
Looks like the behavior was added to bfd with this commit:
commit 77bd8dfa1f3678ea3c3d05f40de29a36802d21f5
Author: Ian Lance Taylor <ian@airs.com>
Date: Thu May 15 21:38:26 1997 +0000
* elflink.h (elf_link_add_object_symbols): Consistently treat
uninitialized symbols in shared objects as common symbols.
diff --git a/bfd/ChangeLog b/bfd/ChangeLog
index 6bcd44a5b94..84f3d403a55 100644
--- a/bfd/ChangeLog
+++ b/bfd/ChangeLog
@@ -11,7 +11,8 @@ Thu May 15 14:31:28 1997 Nick Clifton <nickc@cygnus.com>
Thu May 15 16:40:20 1997 Ian Lance Taylor <ian@cygnus.com>
* elflink.h (elf_link_add_object_symbols): Force ELF symbol size
- to common symbol size.
+ to common symbol size. Consistently treat uninitialized symbols
+ in shared objects as common symbols.
Fri May 9 10:15:27 1997 Nick Clifton <nickc@cygnus.com>
diff --git a/bfd/elflink.h b/bfd/elflink.h
index 43425c02f8b..78f57ffc63e 100644
--- a/bfd/elflink.h
+++ b/bfd/elflink.h
@@ -913,26 +913,16 @@ elf_link_add_object_symbols (abfd, info)
&& definition
&& h->root.type == bfd_link_hash_common
&& (sec->flags & SEC_ALLOC) != 0
- && (sec->flags & SEC_LOAD) == 0)
+ && (sec->flags & SEC_LOAD) == 0
+ && sym.st_size > 0
+ && bind != STB_WEAK
+ && ELF_ST_TYPE (sym.st_info) != STT_FUNC)
{
- if (! ((*info->callbacks->multiple_common)
- (info, h->root.root.string,
- h->root.u.c.p->section->owner, bfd_link_hash_common,
- h->root.u.c.size, abfd, bfd_link_hash_common,
- sym.st_size)))
- goto error_return;
-
- /* If the symbol in the shared library is smaller than
- the one we already have, then override it to stick
- with the larger symbol. Set SIZE_CHANGE_OK because
- we only want to warn if requested with --warn-common. */
- if (sym.st_size < h->size)
- {
- override = true;
- sec = bfd_und_section_ptr;
- definition = false;
- size_change_ok = true;
- }
+ override = true;
+ sec = bfd_com_section_ptr;
+ definition = false;
+ value = sym.st_size;
+ size_change_ok = true;
}
/* Similarly, if we are not looking at a dynamic object, and
Not really any other context, but I guess it’s good to know when it was added.