[PATCH] ld: don't use SAME_INODE for the duplicate-script check on hosts without inodes

Cole Munz Munzzyy1@proton.me
Mon Aug 17 16:59:00 GMT 2026


Since 2.47, ld rejects a perfectly ordinary link on native Windows:

  ld.exe: error: linker script file '../common_arm/ldscript.common
  (ldscript-flash)' appears multiple times

when the only thing on the command line is a single -T, and that script
INCLUDEs one other file. The two names in the message are the giveaway:
the file being opened and an entry already recorded are different files,
so the comparison that matched them is wrong.

Two changes stack up to produce it. d048eee29108 ("ld: Use stat to check
if linker script appears multiple times") changed the PR 24576 check from
a name comparison to stat plus SAME_INODE. Then 47071f8b14a0
("same-inode.h: don't depend on _GL_WINDOWS_STAT_INODES") dropped the
guard in include/same-inode.h that had been expanding SAME_INODE to a
literal 0 on native Windows. binutils never defines
_GL_WINDOWS_STAT_INODES, so on Windows the check went from dead code to
live in one release.

The Windows CRT sets st_ino to 0 for every file. The guard that survived
only rejects st_ino == 0 && st_dev == 0, and st_dev is the drive number,
so on D: it is 3 and the guard passes. Every file on the drive then
compares equal to every other file, and the first INCLUDE inside a -T
script looks like a repeat of the script itself. The commit message of
47071f8b14a0 anticipates this: "this doesn't really make SAME_INODE
usable on windows hosts as a number of the likely filesystems (FAT,
HPFS, or NTFS) don't support st_ino."

Fall back to comparing file names when stat gives no usable inode, so the
duplicate detection keeps working on hosts where inodes are real and stops
firing on files that merely share a device. PR 24576's own testcases still
pass, including the ././/script spelling that a name comparison alone would
miss, because hosts with real inodes still take the inode path.

Signed-off-by: Cole Munz <Munzzyy1@proton.me>
---
Notes on how far I got testing this, since I could not test on Windows.

I reproduced the failure on Linux by interposing stat/lstat/fstat to return
what the Windows CRT returns (st_ino = 0, st_dev = 3) and running the real
link command from the project that hit this. Stock 2.47 under that shim gives
the byte-identical error; with this patch it links. On normal POSIX stat the
same build still rejects -T script -T script, and still rejects the
-T ././/script -T script spelling, which is the case a plain name comparison
would miss.

Not covered: I have no mingw-w64 host, so this was never compiled for or run
on native Windows, and I have no dejagnu here so I could not run the real
ld testsuite. The PR 24576 cases above were replayed by hand from
ld/testsuite/ld-scripts/pr24576-1.d and -2.d rather than run under the
harness.

For what it is worth on impact: MSYS2 currently ships 2.47 as
arm-none-eabi-binutils in ucrt64, and it breaks the bootrom link for every
user of the proxmark3 project's Windows environment, not just their CI.

--- a/ld/ldfile.c
+++ b/ld/ldfile.c
@@ -882,19 +882,30 @@
 
  success:
   /* PR 24576: Catch the case where the user has accidentally included
-     the same linker script twice.  */
-  if (stat (filename, &sbuf1) == 0)
-    {
-      struct stat sbuf2;
-      for (script = processed_scripts;
-	   script != NULL;
-	   script = script->next)
-	if ((open_how != script_nonT || script->open_how != script_nonT)
-	    && stat (script->name, &sbuf2) == 0
-	    && SAME_INODE (sbuf1, sbuf2))
+     the same linker script twice.  Not every host has usable inodes:
+     native Windows stat always reports st_ino as zero, which would make
+     every script look like every other one.  Compare file names there.  */
+  {
+    bool have_inode = stat (filename, &sbuf1) == 0 && sbuf1.st_ino != 0;
+
+    for (script = processed_scripts;
+	 script != NULL;
+	 script = script->next)
+      {
+	struct stat sbuf2;
+
+	if (open_how == script_nonT && script->open_how == script_nonT)
+	  continue;
+
+	if (have_inode
+	    ? (stat (script->name, &sbuf2) == 0
+	       && sbuf2.st_ino != 0
+	       && SAME_INODE (sbuf1, sbuf2))
+	    : filename_cmp (filename, script->name) == 0)
 	  fatal (_("%P: error: linker script file '%s (%s)'"
 		   " appears multiple times\n"), filename, script->name);
-    }
+      }
+  }
 
   len = strlen (filename);
   script = xmalloc (sizeof (*script) + len);

-- 
2.55.0



More information about the Binutils mailing list