This is an archive of the discontinued LLVM Phabricator instance.

lld-link: Reject more than one resource .obj file
ClosedPublic

Authored by thakis on Jun 10 2019, 4:55 PM.

Details

Summary

Users are exepcted to pass all .res files to the linker, which then
merges all the resource in all .res files into a tree structure and then
converts the final tree structure to a .obj file with .rsrc$01 and
.rsrc$02 sections and then links that.

If the user instead passes several .obj files containing such resources,
the correct thing to do would be to have custom code to merge the trees
in the resource sections instead of doing normal section merging -- but
link.exe rejects if multiple resource obj files are passed in with
LNK4078, so let lld-link do that too instead of silently writing broken
.rsrc sections in that case.

The only real way to run into this is if users manually convert .res
files to .obj files by running cvtres and then handing the resulting
.obj files to lld-link instead, which in practice likely never happens.

(lld-link is slightly stricter than link.exe now: If link.exe is passed
one .obj file created by cvtres, and a .res file, for some reason it
just emits a warning instead of an error and outputs strange looking
data. lld-link now errors out on mixed input like this.)

One way users could accidentally run into this is the following
scenario: If a .res file is passed to lib.exe, then lib.exe calls
cvtres.exe on the .res file before putting it in the output .lib.
(llvm-lib currently doesn't do this.)
link.exe's /wholearchive seems to only add obj files referenced from the
static library index, but lld-link current really adds all files in the
archive. So if lld-link /wholearchive is used with .lib files produced
by lib.exe and .res files were among the files handed to lib.exe, we
previously silently produced invalid output, but now we error out.

link.exe's /wholearchive semantics on the other hand mean that it
wouldn't load the resource object files from the .lib file at all.
Since this scenario is probably still an unlikely corner case,
the difference in behavior here seems fine -- and lld-link might have to
change to use link.exe's /wholearchive semantics in the future anyways.

Vaguely related to PR42180.

Diff Detail

Repository
rL LLVM

Event Timeline

thakis created this revision.Jun 10 2019, 4:55 PM
thakis marked an inline comment as done.Jun 10 2019, 4:56 PM
thakis added inline comments.
lld/COFF/DriverUtils.cpp
728 ↗(On Diff #203938)

(I added this so that searching for "cvtres" in this file finds the related code.)

thakis updated this revision to Diff 203972.Jun 10 2019, 9:13 PM

ruiu: ping :)

ruiu added inline comments.Jun 11 2019, 5:15 AM
lld/COFF/SymbolTable.h
123 ↗(On Diff #203972)

This pointer is kept only to print out a filename in an error message, but the visibility of this variable seems a bit too broad. SymbolTable does a lot of things, so when I first saw this variable, this is part of a logic that does some nontrivial thing, which isn't true.

Do you think you can write a function that scans all members of ObjFile::Instances after we get all input files to see if there are more than two files having a resource section? That way I think we can avoid adding a new member to the symbol table.

thakis updated this revision to Diff 204053.Jun 11 2019, 6:59 AM
thakis marked an inline comment as done.

address comment

lld/COFF/SymbolTable.h
123 ↗(On Diff #203972)

That's a good suggestion, done.

ruiu accepted this revision.Jun 11 2019, 7:01 AM

LGTM

This revision is now accepted and ready to land.Jun 11 2019, 7:01 AM
This revision was automatically updated to reflect the committed changes.

The only real way to run into this is if users manually convert .res files to .obj files by running cvtres and then handing the resulting .obj files to lld-link instead, which in practice likely never happens.

In MinGW land, GNU windres (and compatible drop-in replacements) default to converting the resource output data to object file form. But if you say this already produces invalid results with lld, I presume this is a good change as it makes it clearer instead of producing subtly broken files. Not sure if it's common for MinGW built projects to have multiple resource object files or not.

FWIW, I ran into one error with this on VLC. In one case, a static library ends up (erroneously) containing a resource object file. This static library is included with the --whole-archive option (indirectly, via lots of libtool magic) when linking a DLL, which also contains a resource object files of its own.

In that case, it's possible to fix the issue by removing the intermediate static library which seems to be pointless at the moment. But extending lld to ignore resource object files when linking with --whole-archive would probably be good.

FWIW, I ran into one error with this on VLC. In one case, a static library ends up (erroneously) containing a resource object file. This static library is included with the --whole-archive option (indirectly, via lots of libtool magic) when linking a DLL, which also contains a resource object files of its own.

In that case, it's possible to fix the issue by removing the intermediate static library which seems to be pointless at the moment. But extending lld to ignore resource object files when linking with --whole-archive would probably be good.

Actually, the intermediate static library does seem to be there for a reason (the reason being automake and libtool details), so the solution is either to fight more automake weirdness to try to avoid adding the resource object file to it, or make lld ignore such objects when used with --whole-archive.

link.exe's /wholearchive semantics on the other hand mean that it wouldn't load the resource object files from the .lib file at all. Since this scenario is probably still an unlikely corner case, the difference in behavior here seems fine -- and lld-link might have to change to use link.exe's /wholearchive semantics in the future anyways.

Do you have any pointers on the best way of accomplishing that? That would fix the VLC build issue I'm looking into.

It would be straightforward to pass a flag to LinkerDriver::addArchiveBuffer to indicate whether resource object files should be dropped or not, but even at that stage, we don't know what kind of object file it is until we pass it to SymTab in Symtab->addFile(Obj); (which invokes the parse() method), and at that point it's, at least semantically, a bit too late to back out.

Actually, I'm leaning towards making this only a warning in MinGW cases. If multiple resource objects are passed and the .rsrc$01 sections are concatenated, is the effect that only the first one actually is read and used, or what does happen?

It turned out that ignoring resource objs in wholearchive cases won't work - in some (all?) projects built for mingw with cmake, with the makefile generator instead of ninja, the object files are packed into an intermediate static library, which is then passed to the linker with --whole-archive. So in such cases we actually need to keep the resource object files in such cases, and the VLC issue needs to be dealt with differently.

ruiu added a comment.Jun 23 2019, 9:42 PM

Actually, I'm leaning towards making this only a warning in MinGW cases. If multiple resource objects are passed and the .rsrc$01 sections are concatenated, is the effect that only the first one actually is read and used, or what does happen?

Sounds like a good idea. That seems to make more sense than changing the sematics of a resource file in an archive file.

Sorry for not replying, I'm on vacation.

I've learned that link.exe's /wholearchive is buggy and will do what you'd expect in a future release. So it's going to match lld's current behavior.

A rsrc section contains a tree structure of all resources. Since those don't get merged, if you hit this diag you _will_ get incorrect output. I don't care too much about mingw, but it might make more sense to fix the project running into this, and if that takes a long time, maybe add a /force: thingy for this to downgrade the error to a warning only if users explicitly ask for it.

Sorry for not replying, I'm on vacation.

Oh - sorry for bothering you with this issue then.

I've learned that link.exe's /wholearchive is buggy and will do what you'd expect in a future release. So it's going to match lld's current behavior.

Ok, that's great news, that makes things a bit more consistent at least.

A rsrc section contains a tree structure of all resources. Since those don't get merged, if you hit this diag you _will_ get incorrect output.

Yes, you obviously won't get the contents of both resources. But will the contents of the first resource object be accessible and the other one ignored, or will things get messed up even further? In the VLC case, it's not data resources that something would try to load, which would be missed and things would fail. It's just two identical copies of a VERSIONINFO struct.

I don't care too much about mingw, but it might make more sense to fix the project running into this, and if that takes a long time, maybe add a /force: thingy for this to downgrade the error to a warning only if users explicitly ask for it.

Yes, fixing the project triggering it would of course be ideal; in this case it's not too trivial unfortunately. As GNU ld doesn't error out on these, demoting the error to a warning feels like a tolerable strategy to me.

Actually, it turns out that GNU windres does things differently - GNU ld can link two resource object files from GNU windres, and resources from both end up reachable. When faced with two resource files from llvm-cvtres, only resources from the first one ends up accessible. In the converse case, if lld is given a resource object file from GNU windres (even a single one), those resources aren't reachable at all.

I'll try to dig into this issue first before going further trying to change lld's behaviour here.

Actually, it turns out that GNU windres does things differently - GNU ld can link two resource object files from GNU windres, and resources from both end up reachable. When faced with two resource files from llvm-cvtres, only resources from the first one ends up accessible. In the converse case, if lld is given a resource object file from GNU windres (even a single one), those resources aren't reachable at all.

I think all four combinations of {llvm-cvtres,cvtres.exe} x {llvm-link,link.exe} should have the same behavior. What does link.exe say to two windres outputs? What does GNU ld do with two cvtres.exe inputs?

I think all four combinations of {llvm-cvtres,cvtres.exe} x {llvm-link,link.exe} should have the same behavior. What does link.exe say to two windres outputs? What does GNU ld do with two cvtres.exe inputs?

After a bit more testing, I have the following result matrix:

           windres .o    cvtres .obj      llvm-cvtres.obj     .res
GNU ld     Both          First            First               not supported
link.exe   First         Error            Error               Both
lld-link   None          Error            Error               Both

Noted oddities:

  • When lld-link produces an error in these cases (faced with two resource obj files), the output exe file is still present on disk though, contrary to link.exe
  • lld-link seems to fail to use resources packaged by windres into object files. Will look into why.
  • lld-link also seems to have issues with object files produces by cvtres (tested with cvtres from both MSVC 2019 and MSVC2015), the resources aren't located at runtime - even when only given one single file, while files from llvm-cvtres works fine. Will also look into why for this case.

Thus, for lld-link, neither windres nor cvtres object files work at the moment, only llvm-cvtres (and plain .res files).

thakis added a comment.Aug 8 2019, 7:45 AM

I think all four combinations of {llvm-cvtres,cvtres.exe} x {llvm-link,link.exe} should have the same behavior. What does link.exe say to two windres outputs? What does GNU ld do with two cvtres.exe inputs?

After a bit more testing, I have the following result matrix:

           windres .o    cvtres .obj      llvm-cvtres.obj     .res
GNU ld     Both          First            First               not supported
link.exe   First         Error            Error               Both
lld-link   None          Error            Error               Both

Noted oddities:

  • When lld-link produces an error in these cases (faced with two resource obj files), the output exe file is still present on disk though, contrary to link.exe
  • lld-link seems to fail to use resources packaged by windres into object files. Will look into why.
  • lld-link also seems to have issues with object files produces by cvtres (tested with cvtres from both MSVC 2019 and MSVC2015), the resources aren't located at runtime - even when only given one single file, while files from llvm-cvtres works fine. Will also look into why for this case.

Thus, for lld-link, neither windres nor cvtres object files work at the moment, only llvm-cvtres (and plain .res files).

Did this ever get sorted out? I saw D63837 go by a while ago which from a distance looks related. Did that fix everything? Do all tools agree now?

I think all four combinations of {llvm-cvtres,cvtres.exe} x {llvm-link,link.exe} should have the same behavior. What does link.exe say to two windres outputs? What does GNU ld do with two cvtres.exe inputs?

After a bit more testing, I have the following result matrix:

           windres .o    cvtres .obj      llvm-cvtres.obj     .res
GNU ld     Both          First            First               not supported
link.exe   First         Error            Error               Both
lld-link   None          Error            Error               Both

Noted oddities:

  • When lld-link produces an error in these cases (faced with two resource obj files), the output exe file is still present on disk though, contrary to link.exe
  • lld-link seems to fail to use resources packaged by windres into object files. Will look into why.
  • lld-link also seems to have issues with object files produces by cvtres (tested with cvtres from both MSVC 2019 and MSVC2015), the resources aren't located at runtime - even when only given one single file, while files from llvm-cvtres works fine. Will also look into why for this case.

Thus, for lld-link, neither windres nor cvtres object files work at the moment, only llvm-cvtres (and plain .res files).

Did this ever get sorted out? I saw D63837 go by a while ago which from a distance looks related. Did that fix everything? Do all tools agree now?

Sorry for not following up on this thread earlier.

The immediate issue I had in VLC turned out to be easy to fix after all (you know automake, it's hard to determine if something is "almost trivial but I haven't figured it out yet" or "near impossible" :P), so sorry for raising this as such a big issue before.

The commit you saw above fixed the fact that lld failed to pick up resource objs from both windres and MS cvtres, fixing most of what I was surprised by above.

Lld still doesn't error out if you pass it two resource objects produced by GNU windres. I'm planning on fixing this, but I'm conveniently postponing it a little (to keep this case accidentally working), I hope that is ok with you. (MS cvtres and llvm-cvtres produce sections called .rsrc$01 and .rsrc$02, while GNU windres just produces one, .rsrc.)

Namely: I found out that there are cases in mingw where one often will have two resource objects in each link, namely if linking using lld, in msys2, with certain packages installed, with GCC as the driver calling the linker. Since some time, GCC got the concept of a "default manifest" resource object. If such a file exists on disk (the default-manifest package is installed), it will be automatically added to each link command. Since the advent of this concept (a couple years ago), GNU ld has support for merging resource trees from multiple resource objects, with logic kind of like what lld has, to warn if there are duplicates, but with special logic for silently dropping the default manifest if the user actually provided one.

I'm considering implementing this, but until then, accidentally accepting this situation (where only the user provided resource object ends up visible and the later default manifest one is ignored) feels ok.

The issue where lld errors out due to multiple resource objects, but the output file is left on disk, is something I haven't looked at (or tried to reproduce) yet.

Noted oddities:

  • When lld-link produces an error in these cases (faced with two resource obj files), the output exe file is still present on disk though, contrary to link.exe

The issue where lld errors out due to multiple resource objects, but the output file is left on disk, is something I haven't looked at (or tried to reproduce) yet.

It turned out this was the common design that on error(), lld continues (to gather as many possible error messages for the user) until a later if (errorCount()) return; There's no such check between diagnoseMultipleResourceObjFiles() and writeResults(), and even then, the situation with erroring out but output file left on disk also happens for any other errors that show up during the writeResults phase. (I guess a more correct course of action would be to make sure the output file gets removed in these cases?)

ruiu added a comment.Aug 20 2019, 4:22 AM

We are using FileOutputBuffer. That class creates a temporary file then atomically rename it a desitnation filename when commit() is called. So I'd think we should add a checkpoint just before commit().

We are using FileOutputBuffer. That class creates a temporary file then atomically rename it a desitnation filename when commit() is called. So I'd think we should add a checkpoint just before commit().

Implemented this in D66491, fwiw.