This is an archive of the discontinued LLVM Phabricator instance.

[lld-macho] Avoid force-loading the same archive twice
ClosedPublic

Authored by int3 on Jun 15 2021, 9:54 PM.

Details

Summary

We need to dedup archive loads (similar to what we do for dylib
loads).

I noticed this issue after building some Swift stuff that used
-force_load_swift_libs, as it caused some Swift archives to be loaded
many times.

Diff Detail

Event Timeline

int3 created this revision.Jun 15 2021, 9:54 PM
Herald added a project: Restricted Project. · View Herald Transcript
int3 requested review of this revision.Jun 15 2021, 9:54 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 15 2021, 9:54 PM
int3 updated this revision to Diff 352343.Jun 15 2021, 11:06 PM

tweak test

(So ld64 -force_load behaves similar to link.exe -wholearchive: where input filenames are (unfortunately) deduplicated. I like the ELF --whole-archive no-magic version...)

thakis accepted this revision.Jun 16 2021, 10:35 AM
thakis added a subscriber: thakis.
thakis added inline comments.
lld/test/MachO/force-load.s
23

Hm, should we have an explicit test for this? ld64 does seem to do realpath(), and we kind of want to do that at some point too. So it seems a bit strange to have a test preventing this.

(My local ld64 test:

% ln -s libfoo.a libbar.a
% clang mainfoo.o libfoo.a -force_load libfoo.a -force_load libfoo.a -lfoo -L . libbar.a ../llvm-project/libfoo.a

)

This revision is now accepted and ready to land.Jun 16 2021, 10:35 AM

(I agree with MaskRay that the ELF behavior is nicer, but you solve the problems you have, not the ones you wish you had.)

int3 added inline comments.Jun 16 2021, 12:21 PM
lld/test/MachO/force-load.s
23

What are the contents of libfoo.a and libbar.a here?

This force-load.s test passes for me when I swap out LLD for ld64. I also tried using an explicit symlink instead of a CWD-relative path, and that passes for me too (i.e. raises a duplicate symbol error.)

You may need a test using -force_load for different archives.

MaskRay added a comment.EditedJun 16 2021, 12:36 PM

Also, have you checked how symlinks/hardlinks behave? lld-link uses sys::fs::getUniqueID.

int3 added a comment.Jun 16 2021, 1:42 PM

Yeah as mentioned in my inline comment, my experiments show that ld64 treats symlinks as distinct inputs. But @thakis seems to think its otherwise, so we'll see...

If symlinks are distinct, then hardlinks should be distinct too, and that seems to hold up. I'll update the diff to show my test cases, which pass both with LLD and ld64.

int3 updated this revision to Diff 352544.Jun 16 2021, 1:42 PM

test symlinks and hardlinks

MaskRay accepted this revision.Jun 16 2021, 2:29 PM
MaskRay added inline comments.
lld/test/MachO/force-load.s
28

This needs to unsupport testing on Windows .

int3 added inline comments.Jun 16 2021, 2:36 PM
lld/test/MachO/force-load.s
28

as the XXX comment above says, I'm not sure this is worth committing, especially since it doesn't allow us to run the test on Windows. I just uploaded it to show you and @thakis that ld64 behaves similarly. I think the relative path check on line 21 sufficiently demonstrates that realpath() isn't being called, and naturally so symlinks and hardlinks wouldn't get resolved either.

I don't feel strongly about this, so lmk if you would prefer these test cases to be committed.

thakis added inline comments.Jun 16 2021, 5:04 PM
lld/test/MachO/force-load.s
23

libfoo.a contained void foo() {} in compiled, and libbar.a is a symlink to libffo.a. (and mainfoo.o is void foo(); int main() { foo(); }) This links fine for me with ld64.

28

We treat them as distinct inputs, but ld64 apparently doesn't (didn't test hardlinks, just symlinks). And we want to realpath() eventually, so as I said I wouldn't add these tests.

int3 added inline comments.Jun 16 2021, 5:51 PM
lld/test/MachO/force-load.s
23

(I meant to ask about mainfoo.o instead of libbar.a... thanks for noticing :p)

I think your example links fine because the -force_loads aren't being applied to two different paths. Having an archive duplicated multiple times on the command line isn't an error if it's not being force-loaded.

E.g. these fail with ld64:

clang mainfoo.o libfoo.a -force_load libfoo.a -force_load ../tmp/libfoo.a -isysroot $(xcrun -show-sdk-path)
clang mainfoo.o libfoo.a -force_load libfoo.a -force_load libbar.a -isysroot $(xcrun -show-sdk-path)
int3 updated this revision to Diff 352593.Jun 16 2021, 6:24 PM

remove use of ln; add test for force-loading multiple non-conflicting archives, and a test for non-force-loading multiple conflicting archives

thakis added inline comments.Jun 17 2021, 5:45 AM
lld/test/MachO/force-load.s
23

Ah derp, of course. Sorry 😳

int3 marked an inline comment as done.Jun 17 2021, 8:12 AM
int3 added inline comments.
lld/test/MachO/force-load.s
23

No worries :)

This revision was automatically updated to reflect the committed changes.
int3 marked an inline comment as done.

Looks like this breaks tests on some windows bots: https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket.appspot.com/8844046562894240432/+/u/package_clang/stdout?format=raw

$ ":" "RUN: at line 19"
$ "ld64.lld" "-arch" "x86_64" "-platform_version" "macos" "10.15" "11.0" "-syslibroot" "C:/b/s/w/ir/cache/builder/src/third_party/llvm/lld/test\MachO\Inputs\MacOSX.sdk" "-fatal_warnings" "-lSystem" "C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.o" "-force_load" "C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.a" "-force_load" "C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.a" "C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/test.o" "-o" "/dev/null"
# command stderr:
ld64.lld: error: duplicate symbol: _bar

>>> defined in C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.a(archive-foo.o)

>>> defined in C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.a(archive-foo.o)

Looks like this breaks tests on some windows bots: https://logs.chromium.org/logs/chromium/buildbucket/cr-buildbucket.appspot.com/8844046562894240432/+/u/package_clang/stdout?format=raw

$ ":" "RUN: at line 19"
$ "ld64.lld" "-arch" "x86_64" "-platform_version" "macos" "10.15" "11.0" "-syslibroot" "C:/b/s/w/ir/cache/builder/src/third_party/llvm/lld/test\MachO\Inputs\MacOSX.sdk" "-fatal_warnings" "-lSystem" "C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.o" "-force_load" "C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.a" "-force_load" "C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.a" "C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/test.o" "-o" "/dev/null"
# command stderr:
ld64.lld: error: duplicate symbol: _bar

>>> defined in C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.a(archive-foo.o)

>>> defined in C:\b\s\w\ir\cache\builder\src\third_party\llvm-bootstrap\tools\lld\test\MachO\Output\force-load.s.tmp/foo.a(archive-foo.o)

Hm, but that didn't happen in our nightly build last night. So I guess something else broke the test since then?

thakis added inline comments.Jun 18 2021, 5:17 PM
lld/test/MachO/force-load.s
26

Hm, there's no FileCheck line with --check-prefix=DUP…

thakis added inline comments.Jun 18 2021, 5:21 PM
lld/MachO/Driver.cpp
268

I don't know if that's what's happening, but if loadArchiveMember() loads a .o file that contains LC_LINKER_OPTIONS that has a -l flag that can recursively call addFile() again which can resize loadedArchives and invalidate the cachedFile ref.

thakis added inline comments.Jun 18 2021, 5:26 PM
lld/MachO/Driver.cpp
268

Reverted in c9b241efd68c for now to green up bots. This sounds like a plausible theory to me.

thakis added inline comments.Jun 18 2021, 6:22 PM
lld/MachO/Driver.cpp
268

Reverted in c9b241efd68c for now to green up bots. This sounds like a plausible theory to me.

Not sure why it's only on that bot, but the test failed 3 times in succession:

https://ci.chromium.org/ui/p/chromium/builders/try/win_upload_clang/1608/overview
https://ci.chromium.org/ui/p/chromium/builders/try/win_upload_clang/1609/overview
https://ci.chromium.org/ui/p/chromium/builders/try/win_upload_clang/1610/overview

But after the revert this test is happy:

https://ci.chromium.org/ui/p/chromium/builders/try/win_upload_clang/1611/overview

So it looks like the revert helped.

int3 added a comment.Jun 18 2021, 7:27 PM

The real culprit is https://github.com/llvm/llvm-project/commit/1d31fb8d122b1117cf20a9edc09812db8472e930, which I (unfortunately) thought simple enough to be landed without review. And I only landed it today, which explains why the breakage only happened today.

The issue is that it made rerootPath return a temporary std::string instead of an interned string, which then gets passed to addFile as a StringRef, so it gets prematurely freed.

I'll back out the std::string change and re-land this force-load diff.

lld/test/MachO/force-load.s
26

whoops. I'll fix it as part of the re-land.

thakis added inline comments.Jun 20 2021, 6:56 PM
lld/MachO/Driver.cpp
268

I don't know if that's what's happening, but if loadArchiveMember() loads a .o file that contains LC_LINKER_OPTIONS that has a -l flag that can recursively call addFile() again which can resize loadedArchives and invalidate the cachedFile ref.

I see you removed the ref in the reland. For completeness, here's a test case to for this. It doesn't trigger a crash even with asan with the first iteration of this patch, but it does cause a recursive call of this function and things only work by luck. (But it's addressed in the reland.)

% cat g.cc
void g() {}
% clang -c g.cc
% libtool -static -o libfoo.a g.o
% cat autolink_libfoo.cc
asm(".linker_option \"-lfoo\"");
void g();
void f() { g(); }
% clang -c autolink_libfoo.cc
% libtool -static -o libbar.a autolink_libfoo.o
% cat main_f.cc
void g();
int main() { g(); }
% clang -c main_f.cc
% out/gn/bin/ld64.lld -dynamic -arch x86_64 main_f.o -force_load libbar.a -platform_version macos 10.15.0 10.15.0 -o a.out -L. -lSystem