Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ELF] Suppress --no-allow-shlib-undefined diagnostic when a SharedSymbol is overridden by a hidden visibility Defined which is later discarded #70130

Closed
wants to merge 1 commit into from

Conversation

MaskRay
Copy link
Member

@MaskRay MaskRay commented Oct 24, 2023

Commit 1981b1b unexpectedly strengthened
--no-allow-shlib-undefined to catch a kind of ODR violation.
More precisely, when all three conditions are met, the new
--no-allow-shlib-undefined code reports an error.

  • There is a DSO undef that has been satisfied by a definition from another DSO.
  • The SharedSymbol is overridden by a non-exported (usually of hidden visibility) definition in a relocatable object file (Defined).
  • The section containing the Defined is garbage-collected (it is not part of .dynsym and is not marked as live).

Technically, the hidden Defined in the executable can be intentional: it can be
meant to remain non-exported and not interact with any dynamic symbols of the same
name that might exist in other DSOs. To allow for such use cases, allocate a new bit in
Symbol and relax the --no-allow-shlib-undefined check to before commit 1981b1b.

@llvmbot
Copy link
Collaborator

llvmbot commented Oct 24, 2023

@llvm/pr-subscribers-lld

@llvm/pr-subscribers-lld-elf

Author: Fangrui Song (MaskRay)

Changes

Commit 1981b1b unexpectedly made --no-allow-shlib-undefined stronger:

  • There is a DSO undef that can be satisfied by a SharedSymbol.
  • The SharedSymbol is overridden by a hidden visibility Defined.
  • The hidden visibility Defined can be garbage-collected (it is not part of .dynsym and is not marked as live).

When all the conditions are satisfied, the new --no-allow-shlib-undefined code
reports an error.

Technically, the hidden Defined in the executable can be intentional: it can be
meant to remain non-exported and not interact with any dynamic symbols of the same
name that might exist in other DSOs. To allow for such use cases, add a new bit in
Symbols::flags and relax the --no-allow-shlib-undefined check to before commit 1981b1b.


Possible future extension: create a linker option to error about the cases
ignored by this patch, and generalize it to included garbage-collected Defined
and DSO definitions. I have managed to identify several unintended
hidden/default duplicate symbol issues internally.

I haven't yet identified an entirely legitimate use case of a hidden Defined
"preempting" a DSO undef/def. In addition, the option can apply to DSO
definitions as well, not just requiredSymbols.


Full diff: https://github.com/llvm/llvm-project/pull/70130.diff

4 Files Affected:

  • (modified) lld/ELF/InputFiles.cpp (+2)
  • (modified) lld/ELF/Symbols.h (+1)
  • (modified) lld/ELF/Writer.cpp (+6-1)
  • (modified) lld/test/ELF/allow-shlib-undefined.s (+2-1)
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index a0d4be8ff9885b0..c332470cfd57320 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1545,6 +1545,7 @@ template <class ELFT> void SharedFile::parse() {
       auto *s = symtab.addSymbol(
           SharedSymbol{*this, name, sym.getBinding(), sym.st_other,
                        sym.getType(), sym.st_value, sym.st_size, alignment});
+      s->setFlags(HAS_SHARED_DEF);
       if (s->file == this)
         s->verdefIndex = ver;
     }
@@ -1562,6 +1563,7 @@ template <class ELFT> void SharedFile::parse() {
     auto *s = symtab.addSymbol(
         SharedSymbol{*this, saver().save(name), sym.getBinding(), sym.st_other,
                      sym.getType(), sym.st_value, sym.st_size, alignment});
+    s->setFlags(HAS_SHARED_DEF);
     if (s->file == this)
       s->verdefIndex = idx;
   }
diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h
index 4addb79d1257914..8c1be13dbc493d7 100644
--- a/lld/ELF/Symbols.h
+++ b/lld/ELF/Symbols.h
@@ -54,6 +54,7 @@ enum {
   NEEDS_TLSGD_TO_IE = 1 << 6,
   NEEDS_GOT_DTPREL = 1 << 7,
   NEEDS_TLSIE = 1 << 8,
+  HAS_SHARED_DEF = 1 << 9,
 };
 
 // Some index properties of a symbol are stored separately in this auxiliary
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 57e1aa06c6aa873..a46a97755ea5f89 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -2016,6 +2016,11 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
       // to catch more cases. That is too much for us. Our approach resembles
       // the one used in ld.gold, achieves a good balance to be useful but not
       // too smart.
+      //
+      // If a DSO reference is resolved by a SharedSymbol, but the SharedSymbol
+      // is overridden by a hidden visibility Defined (which is later discarded
+      // due to GC), don't report the diagnostic. However, this may indicate an
+      // unintended SharedSymbol.
       for (SharedFile *file : ctx.sharedFiles) {
         bool allNeededIsKnown =
             llvm::all_of(file->dtNeeded, [&](StringRef needed) {
@@ -2024,7 +2029,7 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
         if (!allNeededIsKnown)
           continue;
         for (Symbol *sym : file->requiredSymbols)
-          if (sym->isUndefined() && !sym->isWeak())
+          if (sym->isUndefined() && !sym->isWeak() && !sym->hasFlag(HAS_SHARED_DEF))
             diagnose("undefined reference due to --no-allow-shlib-undefined: " +
                      toString(*sym) + "\n>>> referenced by " + toString(file));
       }
diff --git a/lld/test/ELF/allow-shlib-undefined.s b/lld/test/ELF/allow-shlib-undefined.s
index 03f047b02d75d52..16407b9645c8a07 100644
--- a/lld/test/ELF/allow-shlib-undefined.s
+++ b/lld/test/ELF/allow-shlib-undefined.s
@@ -46,7 +46,8 @@
 # RUN: not ld.lld --gc-sections %t.o %t.so %tdef-hidden.o -o /dev/null 2>&1 | FileCheck %s
 ## The definition %tdef.so is ignored.
 # RUN: ld.lld -shared -soname=tdef.so %tdef.o -o %tdef.so
-# RUN: not ld.lld --gc-sections %t.o %t.so %tdef.so %tdef-hidden.o -o /dev/null 2>&1 | FileCheck %s
+# RUN: ld.lld --gc-sections %t.o %t.so %tdef.so %tdef-hidden.o -o /dev/null --fatal-warnings
+# RUN: ld.lld --gc-sections %t.o %t.so %tdef-hidden.o %tdef.so -o /dev/null --fatal-warnings
 
 .globl _start
 _start:

@github-actions
Copy link

github-actions bot commented Oct 24, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

@MaskRay
Copy link
Member Author

MaskRay commented Oct 25, 2023

See also the Discourse post Relocatable file definitions shared with DSO

@smithp35
Copy link
Collaborator

See also the Discourse post Relocatable file definitions shared with DSO

Not had a chance to look at the code yet. I've replied on the Discourse post, hopefully I understood it correctly.

MaskRay added a commit to MaskRay/llvm-project that referenced this pull request Nov 3, 2023
Commit 1981b1b unexpectedly strengthened
--no-allow-shlib-undefined which will be relaxed by llvm#70130.
This patch adds --no-allow-non-exported-symbols-shared-with-dso to restore
the check and make the check catch more cases:

* report errors in the presence of a DSO definition
* make the check work whether or not --gc-sections discards the symbol

Commit 1981b1b has caught several brittle
build issues. For example,

```
libfdio.so: reference to _Znam
libclang_rt.asan.so: shared definition of _Znam
libc++.a(stdlib_new_delete.cpp.obj): definition of _Znam
```

The executable contains a definition from `libc++.a` while at run-time,
libfdio.so's `_Znam` reference gets resolved to `libclang_rt.asan.so`. This
scenarios is often undesired. In this case, a possible improvement is to switch
to an asan-instrumented libc++.a that does not define `_Znam`.

I have also seen problems due to mixing multiple definitions from `libgcc.a`
(hidden visibility) and `libclang_rt.builtins.a` (default visibility) and
relying on archive member extraction rules to work.

---

* I wonder whether this option is useful enough to justify a new option.
* Using protected visibility can cause similar multiple definition issues. Is it
  useful to generalize this option?
MaskRay added a commit to MaskRay/llvm-project that referenced this pull request Nov 4, 2023
For a Defined/Common symbol with a false `inDynamicList`, both
`versionId==VER_NDX_LOCAL` and `!exportDynamic` indicate that the symbol
should not be exported, which means that the two fields have overlapping
purposes. We can merge them together by reserving `versionId==-1` to
indicate a symbol that is not exported:

* -1 (initial): not exported, binding unchanged
* 0: VER_NDX_LOCAL, not exported, binding changed to STB_LOCAL
* 1: VER_NDX_GLOBAL, exported, binding unchanged
* others: verdef index, exported, binding unchanged

-1 and 0 are similar, but -1 does not change the binding to STB_LOCAL.

The saved bit can be use for another purpose, e.g. whether a symbol has
a DSO definition (llvm#70130).

--version-script is almost never used for executables. If used, a minor behavior
change is that a version pattern that is not `local:` will export the matched
symbols.
MaskRay added a commit to MaskRay/llvm-project that referenced this pull request Nov 14, 2023
The two fields are similar.

`versionId` is the Verdef index in the output file. It is set for
version script patterns and `sym@ver` symbols.

`verdefIndex` is the Verdef index of a SharedSymbol. The default value
-1 is also used to indicate that the symbol has not been matched by a
version script pattern (https://reviews.llvm.org/D65716).

It seems confusing to have two fields. Merge them so that we can
allocate one bit for llvm#70130 (suppress --no-allow-shlib-undefined
error in the presence of a DSO definition).
MaskRay added a commit that referenced this pull request Nov 14, 2023
The two fields are similar.

`versionId` is the Verdef index in the output file. It is set for
version script patterns and `sym@ver` symbols.

`verdefIndex` is the Verdef index of a SharedSymbol. The default value
-1 is also used to indicate that the symbol has not been matched by a
version script pattern (https://reviews.llvm.org/D65716).

It seems confusing to have two fields. Merge them so that we can
allocate one bit for #70130 (suppress --no-allow-shlib-undefined
error in the presence of a DSO definition).
@MaskRay
Copy link
Member Author

MaskRay commented Nov 14, 2023

Rebase after "[ELF] Merge verdefIndex into versionId. NFC (#72208)" (I managed to find a way to use a regular bitfield, not an atomic bit)

MaskRay added a commit to MaskRay/llvm-project that referenced this pull request Nov 16, 2023
The two fields are similar.

`versionId` is the Verdef index in the output file. It is set for
version script patterns and `sym@ver` symbols.

`verdefIndex` is the Verdef index of a SharedSymbol. The default value
-1 is also used to indicate that the symbol has not been matched by a
version script pattern (https://reviews.llvm.org/D65716).

It seems confusing to have two fields. Merge them so that we can
allocate one bit for llvm#70130 (suppress --no-allow-shlib-undefined
error in the presence of a DSO definition).
MaskRay added a commit that referenced this pull request Nov 16, 2023
The two fields are similar.

`versionId` is the Verdef index in the output file. It is set for
`--exclude-id=`, version script patterns, and `sym@ver` symbols.

`verdefIndex` is the Verdef index of a Sharedfile (SharedSymbol or a
copy-relocated Defined), the default value -1 is also used to indicate
that the symbol has not been matched by a version script pattern
(https://reviews.llvm.org/D65716).

It seems confusing to have two fields. Merge them so that we can
allocate one bit for #70130 (suppress --no-allow-shlib-undefined
error in the presence of a DSO definition).
sr-tream pushed a commit to sr-tream/llvm-project that referenced this pull request Nov 20, 2023
The two fields are similar.

`versionId` is the Verdef index in the output file. It is set for
`--exclude-id=`, version script patterns, and `sym@ver` symbols.

`verdefIndex` is the Verdef index of a Sharedfile (SharedSymbol or a
copy-relocated Defined), the default value -1 is also used to indicate
that the symbol has not been matched by a version script pattern
(https://reviews.llvm.org/D65716).

It seems confusing to have two fields. Merge them so that we can
allocate one bit for llvm#70130 (suppress --no-allow-shlib-undefined
error in the presence of a DSO definition).
zahiraam pushed a commit to zahiraam/llvm-project that referenced this pull request Nov 20, 2023
The two fields are similar.

`versionId` is the Verdef index in the output file. It is set for
version script patterns and `sym@ver` symbols.

`verdefIndex` is the Verdef index of a SharedSymbol. The default value
-1 is also used to indicate that the symbol has not been matched by a
version script pattern (https://reviews.llvm.org/D65716).

It seems confusing to have two fields. Merge them so that we can
allocate one bit for llvm#70130 (suppress --no-allow-shlib-undefined
error in the presence of a DSO definition).
zahiraam pushed a commit to zahiraam/llvm-project that referenced this pull request Nov 20, 2023
The two fields are similar.

`versionId` is the Verdef index in the output file. It is set for
`--exclude-id=`, version script patterns, and `sym@ver` symbols.

`verdefIndex` is the Verdef index of a Sharedfile (SharedSymbol or a
copy-relocated Defined), the default value -1 is also used to indicate
that the symbol has not been matched by a version script pattern
(https://reviews.llvm.org/D65716).

It seems confusing to have two fields. Merge them so that we can
allocate one bit for llvm#70130 (suppress --no-allow-shlib-undefined
error in the presence of a DSO definition).
@MaskRay
Copy link
Member Author

MaskRay commented Jan 22, 2024

Ping. I think we need this for 18.1 release, but probably not need #70163

Copy link
Collaborator

@smithp35 smithp35 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to remind myself of where we were up to by re-reading https://discourse.llvm.org/t/relocatable-file-definitions-shared-with-dso/74391/7

I'm guessing the main reason to make the change is that an error message has been tightened and this might surprise some users picking up the next official release of LLD for the first time.

IIUC this restores the previous behaviour, and #70163 builds on top of this (but adds a new option) that we are not sure is worth the additional complexity.

Overall LGTM.

I do have one question about the description:

* There is a DSO undef that can be satisfied by a definition from another DSO.

Should this be

* There is a DSO undef that has been satisfied by a definition from another DSO.

The use of "can be satisfied" implies that we can have:

  • DSO undef of sym foo.
  • non-exported Defined foo.
  • Section containing Defined is garbage collected.
    In that case there is no definition of foo available for the DSO undef so we should give an error.

@smithp35
Copy link
Collaborator

Will be worth updating the discourse thread (https://discourse.llvm.org/t/relocatable-file-definitions-shared-with-dso/74391) when the commit is merged.

…bol is overridden by a hidden visibility Defined which is later discarded

Commit 1981b1b unexpectedly strengthened
--no-allow-shlib-undefined to catch a kind of ODR violation.
More precisely, when all three conditions are met, the new
`--no-allow-shlib-undefined` code reports an error.

* There is a DSO undef that has been satisfied by a definition from
  another DSO.
* The `SharedSymbol` is overridden by a non-exported (usually of hidden
  visibility) definition in a relocatable object file (`Defined`).
* The section containing the `Defined` is garbage-collected (it is not
  part of `.dynsym` and is not marked as live).

Technically, the hidden Defined in the executable can be intentional: it
can be meant to remain non-exported and not interact with any dynamic
symbols of the same name that might exist in other DSOs. To allow for
such use cases, allocate a new bit in
Symbol and relax the --no-allow-shlib-undefined check to before
commit 1981b1b.
@MaskRay
Copy link
Member Author

MaskRay commented Jan 22, 2024

Had to remind myself of where we were up to by re-reading discourse.llvm.org/t/relocatable-file-definitions-shared-with-dso/74391/7

I'm guessing the main reason to make the change is that an error message has been tightened and this might surprise some users picking up the next official release of LLD for the first time.

IIUC this restores the previous behaviour, and #70163 builds on top of this (but adds a new option) that we are not sure is worth the additional complexity.

Overall LGTM.

I do have one question about the description:

* There is a DSO undef that can be satisfied by a definition from another DSO.

Should this be

* There is a DSO undef that has been satisfied by a definition from another DSO.

The use of "can be satisfied" implies that we can have:

  • DSO undef of sym foo.
  • non-exported Defined foo.
  • Section containing Defined is garbage collected.
    In that case there is no definition of foo available for the DSO undef so we should give an error.

Thanks for the wording suggestion. Updated. Merged in e390bda

Made a reply to https://discourse.llvm.org/t/relocatable-file-definitions-shared-with-dso/74391/10

@MaskRay MaskRay closed this Jan 22, 2024
@MaskRay MaskRay deleted the lld-hidden-preempt-shared branch January 22, 2024 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants