For cases of compiler commands like: gcc -o file.c -lm file.c gets deleted, despite that the linking fails (undefined reference to main) gcc -fuse-ld=lld and -fuse-ld=mold don't have this problem, and the "output" remains intact regardless of the mistake in the command. Both BFD and gold will delete file.c in this case.
See ld/ldlang.c:lang_process, ld opens the output before processing the input files.
I see. Ideally, ld wouldn't be doing that. One option could be outputting to a temp file, and then renaming it after linking finishes successfully. I'll try taking a look at what mold does for this case.
Hi Peter, Can I check your testcase please ? > gcc -o file.c -lm You appear to be creating an output file called "file.c", is that right ? You also do not appear to have an input file. I am assuming that the test shpould have been something like this: gcc -o foo file.c -lm And then the problem is that "foo" is overwritten. I would argue that the linker's behaviour is correct (and that lld and mold are wrong). The reason being that if the output file is not deleted then it is harder for a user or script to detect that a problem has occurred. Consider a scenario like this: <user modifies their program> <user builds their program, but does not examine all the output messages> <user runs the built program - it works> If the link step fails, but does not delete the output file, then the user, not realizing that something went wrong, will run the old executable, see that it works and think that their change is correct. Cheers Nick
I think the risk of potentially overwriting/deleting the source file due to a user's mistake is more important than possibly causing confusion to inattentive users who don't check the exit status or error messages. I think the behavior exhibited is dubiously useful, and choosing the safer option would be better. I would greatly prefer mold and lld's behavior here. As you can see from the gcc bug, and several other times I have seen users do this: https://vxtwitter.com/tsoding/status/1756689101141524847/ I have also personally lost work due to this as well. Do you have anything specific in mind that changing this behavior to match mold and lld would break?
Just to be completely clear, the command is incorrect, but the way ld reacts to the mistake is the problem. In this case, the "file.c" will be deleted, the user has potentially lost a file, and day ruined. I think refusing to delete output here is the correct and preferable thing to do.
I agree with Nick's comment. Consider too that if the link had succeeded, your source file would have been overwritten (assuming of course that write access was allowed to the file).
(In reply to Peter Damianov from comment #5) > Just to be completely clear, the command is incorrect, but the way ld reacts > to the mistake is the problem. In this case, the "file.c" will be deleted, > the user has potentially lost a file, and day ruined. I think refusing to > delete output here is the correct and preferable thing to do. It may seem callous, but if you are using command line tools to build programs then I would argue that a certain level of competence is expected, and that loosing a source file to a silly mistake is actually a useful lesson. But maybe a compromise solution can be reached here. As Alan pointed out, setting the output file name to the name of an existing source file is going to cause problems regardless of whether the link succeeds or fails. So maybe the correct thing to do is to have the "-o <file>" option fail if the output name matches the name of an existing file *and* that name matches a pattern of known source files (eg *.[cChosS] *.cpp) ? What do you think ?
(In reply to Alan Modra from comment #6) > I agree with Nick's comment. Consider too that if the link had succeeded, > your source file would have been overwritten (assuming of course that write > access was allowed to the file). I'm aware. But this is less likely to happen, perhaps maybe only when linking libraries that do expect to provide their own main (like SDL). It would be a lot harder to make this mistake for that case. If you miss out a source file your code is usually not going to link anyway. (In reply to Nick Clifton from comment #7) > (In reply to Peter Damianov from comment #5) > > Just to be completely clear, the command is incorrect, but the way ld reacts > > to the mistake is the problem. In this case, the "file.c" will be deleted, > > the user has potentially lost a file, and day ruined. I think refusing to > > delete output here is the correct and preferable thing to do. > > It may seem callous, but if you are using command line tools to build > programs then I would argue that a certain level of competence is expected, > and that loosing a source file to a silly mistake is actually a useful > lesson. I would argue there are better ways to teach that lesson. I think it is important for users to be able to learn how to invoke gcc without risking demotivating problems like these. > > But maybe a compromise solution can be reached here. As Alan pointed out, > setting the output file name to the name of an existing source file is going > to cause problems regardless of whether the link succeeds or fails. So > maybe the correct thing to do is to have the "-o <file>" option fail if the > output name matches the name of an existing file *and* that name matches a > pattern of known source files (eg *.[cChosS] *.cpp) ? I tried to implement this in the gcc driver and had some trouble, but I had to be more careful then because flags like -E and -S needed to be considered. I think this might also run the risk of maybe breaking some weird configure scripts. Just as a test, I tried: ls /usr/bin/*.* To see if there was anything it could affect The most common case of having a suffix was simply a version number (e.g. x86_64-pc-linux-gnu-gcc-14.1.1, python3.12) Next was .sh and .pl mkfs had some instances, like mkfs.ext4, mkfs.vfat And last were the linkers, ld.mold, ld.bfd, There wasn't anything this would break, but of course it's a small subset of what is out there. > > What do you think ? I think going all this way is not necessary, and simply leaving the file alone in the case of the link failing would be enough to cover most cases. And it's already tested/implemented by other linkers. But, this suggestion of "filename heuristics" could also work. If you think it's worth it, go for it.
(In reply to Nick Clifton from comment #7) > It may seem callous, but if you are using command line tools to build > programs then I would argue that a certain level of competence is expected, > and that loosing a source file to a silly mistake is actually a useful > lesson. Ouch! This can happen with makefiles too, e.g. $(CC) -o $(OUTF1LE) $< $(LDLIBS) with a typo in the OUTFILE variable, so that it's empty and the command expands to: gcc -o foo.c -lm Oops. > But maybe a compromise solution can be reached here. As Alan pointed out, > setting the output file name to the name of an existing source file is going > to cause problems regardless of whether the link succeeds or fails. If the output was written to a temporary file and only renamed on success, there would be no problem. The claim that it's going to cause problems regardless of whether it succeeds or fails is only true **if ld overwrites existing files on failure**. If it didn't do that, then making that mistake wouldn't cause problems in many cases that cause problems today. There would still be some cases where the link succeeds and so you still trash an input file (e.g. Peter's original gcc patch submission showed an example with partial linking, where the link succeeds despite missing symbols) but such cases are probably less likely to be used by newbies who may be more at risk of making this mistake in the first place. > So > maybe the correct thing to do is to have the "-o <file>" option fail if the > output name matches the name of an existing file *and* that name matches a > pattern of known source files (eg *.[cChosS] *.cpp) ? > > What do you think ? What if it's a linker script with no extension, or a custom extension like .linker_script? There's no need for heuristics if the output is simply written to a file with a temporary (mkstemp) name, or one opened with O_TMPFILE, and only renamed on success.
(In reply to Alan Modra from comment #6) > I agree with Nick's comment. Consider too that if the link had succeeded, > your source file would have been overwritten (assuming of course that write > access was allowed to the file). For the record, gcc won't even allow you to set the output to the name of an input file: $ gcc foo.c -o foo.c gcc: fatal error: input file ‘foo.c’ is the same as output file compilation terminated. That simple mistake also used to trash the source file, but it was fixed in gcc more than a decade ago, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36312 So now you need to be a bit smarter/dumber to trash the input file. It's no longer enough to typo "foo.c" instead of "foo.o", you need to forget the name of the output file entirely, or use an unset variable as in: gcc -o $objfile foo.c -lm That's harder to fix in GCC, but would be easy to fix in binutils by not overwriting the output on failure.
(In reply to Jonathan Wakely from comment #10) > For the record, gcc won't even allow you to set the output to the name of an > input file: > > $ gcc foo.c -o foo.c > gcc: fatal error: input file ‘foo.c’ is the same as output file > compilation terminated. It is the same with ld: $ ld foo.o -o foo.o ld: input file 'foo.o' is the same as output file $ echo $? 1 But I still think that the linker could be a bit more paranoid about overwriting existing files, so I will see what I can do.
I have posted a proposal for a patch which stops the linker from overwriting known source file types: https://sourceware.org/pipermail/binutils/2024-June/134886.html
The master branch has been updated by Nick Clifton <nickc@sourceware.org>: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=df85f9864f4ad9d2deb6a953107b916d701f7c1b commit df85f9864f4ad9d2deb6a953107b916d701f7c1b Author: Nick Clifton <nickc@redhat.com> Date: Mon Jun 24 15:00:14 2024 +0100 ld: Improve the documentation describing the -o option. PR 31761