[2/2] ld-pe: Add support for -undefined, -include directives

Message ID AS4PR02MB79022E31710C9D4F0BC23868838B2@AS4PR02MB7902.eurprd02.prod.outlook.com
State New
Headers
Series [1/2] ld-pe: Refactor code dealing with -exclude-symbols |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed

Commit Message

Luca Bacci Sept. 30, 2026, 9:33 a.m. UTC
  Add support for specifying undefined symbol names:

 * As linker directives in COFF object files (.drectve section)
 * As statements in DEF module-definition files

See git commit 37513c1e for a similar change.

This enables support for retaining attribute `used' symbols.
Note that attribute `retain' is ELF-specific. The consensus
is that attribute `used' on PE, MACH-O should keep both the
compiler and the linker from discarding the symbol [1] [2].

References:

 1. https://reviews.llvm.org/D96838
 2. https://clang.llvm.org/docs/AttributeReference.html#retain
---
 ld/deffile.h                                  |  3 ++
 ld/deffilep.y                                 | 45 ++++++++++++++++++-
 ld/pe-dll.c                                   |  3 ++
 ld/testsuite/ld-pe/pe.exp                     |  4 ++
 ld/testsuite/ld-pe/undefined-def-i386.d       | 16 +++++++
 ld/testsuite/ld-pe/undefined-def-i386.def     |  4 ++
 ld/testsuite/ld-pe/undefined-def-i386.s       | 19 ++++++++
 ld/testsuite/ld-pe/undefined-def-x86_64.d     | 16 +++++++
 ld/testsuite/ld-pe/undefined-def-x86_64.def   |  4 ++
 ld/testsuite/ld-pe/undefined-def-x86_64.s     | 19 ++++++++
 ld/testsuite/ld-pe/undefined-embedded-i386.d  | 16 +++++++
 ld/testsuite/ld-pe/undefined-embedded-i386.s  | 21 +++++++++
 .../ld-pe/undefined-embedded-x86_64.d         | 16 +++++++
 .../ld-pe/undefined-embedded-x86_64.s         | 21 +++++++++
 14 files changed, 206 insertions(+), 1 deletion(-)
 create mode 100755 ld/testsuite/ld-pe/undefined-def-i386.d
 create mode 100755 ld/testsuite/ld-pe/undefined-def-i386.def
 create mode 100755 ld/testsuite/ld-pe/undefined-def-i386.s
 create mode 100755 ld/testsuite/ld-pe/undefined-def-x86_64.d
 create mode 100755 ld/testsuite/ld-pe/undefined-def-x86_64.def
 create mode 100755 ld/testsuite/ld-pe/undefined-def-x86_64.s
 create mode 100755 ld/testsuite/ld-pe/undefined-embedded-i386.d
 create mode 100755 ld/testsuite/ld-pe/undefined-embedded-i386.s
 create mode 100755 ld/testsuite/ld-pe/undefined-embedded-x86_64.d
 create mode 100755 ld/testsuite/ld-pe/undefined-embedded-x86_64.s
  

Comments

Luca Bacci Oct. 1, 2026, 8:57 a.m. UTC | #1
Hello dear binutils maintainers,

I discussed this patch with mingw-w64 developers. We came to the conclusion that some things should be done differently; namely:
We should avoid extending the set of DEF statements
We should use --require-defined instead of --undefined
A new patch will be sent in a while.

Best Regards,
Luca
  

Patch

diff --git a/ld/deffile.h b/ld/deffile.h
index 9269d488..a7d7bd67 100644
--- a/ld/deffile.h
+++ b/ld/deffile.h
@@ -105,6 +105,9 @@  typedef struct def_file {
 
   /* From EXCLUDE_SYMBOLS or embedded directives. */
   def_file_symbol_list exclude_symbols;
+
+  /* From UNDEFINED, INCLUDE or embedded directives. */
+  def_file_symbol_list undefined_symbols;
 } def_file;
 
 extern def_file *def_file_empty (void);
diff --git a/ld/deffilep.y b/ld/deffilep.y
index bac19249..547fb4d1 100644
--- a/ld/deffilep.y
+++ b/ld/deffilep.y
@@ -104,6 +104,8 @@  static void def_version (int, int);
 static void def_directive (char *);
 static void def_aligncomm (char *str, int align);
 static void def_exclude_symbols (char *str);
+static void def_undefined (char *str);
+static void def_include (char *str);
 static int def_parse (void);
 static void def_error (const char *);
 static int def_lex (void);
@@ -124,7 +126,7 @@  static const char *lex_parse_string_end = 0;
 
 %token NAME LIBRARY DESCRIPTION STACKSIZE_K HEAPSIZE CODE DATAU DATAL
 %token SECTIONS EXPORTS IMPORTS VERSIONK BASE CONSTANTU CONSTANTL
-%token PRIVATEU PRIVATEL ALIGNCOMM EXCLUDE_SYMBOLS
+%token PRIVATEU PRIVATEL ALIGNCOMM EXCLUDE_SYMBOLS INCLUDE UNDEFINED
 %token READ WRITE EXECUTE SHARED_K NONAMEU NONAMEL DIRECTIVE EQUAL
 %token <id> ID
 %token <digits> DIGITS
@@ -159,6 +161,8 @@  command:
 	|	DIRECTIVE ID { def_directive ($2);}
 	|	ALIGNCOMM anylang_id ',' NUMBER { def_aligncomm ($2, $4);}
 	|	EXCLUDE_SYMBOLS symbol_list
+	|	INCLUDE inclist
+	|	UNDEFINED undlist
 	;
 
 
@@ -256,6 +260,7 @@  keyword_as_name: BASE { $$ = "BASE"; }
 	 | EXPORTS { $$ = "EXPORTS"; }
 	 | HEAPSIZE { $$ = "HEAPSIZE"; }
 	 | IMPORTS { $$ = "IMPORTS"; }
+	 | INCLUDE { $$ = "INCLUDE"; }
 /* Disable LIBRARY keyword as valid symbol-name.  This is necessary
    for libtool, which places this command after EXPORTS command.
    This behavior is illegal by specification, but sadly required by
@@ -270,6 +275,7 @@  keyword_as_name: BASE { $$ = "BASE"; }
 	 | READ { $$ = "READ"; }
 	 | SHARED_K  { $$ = "SHARED"; }
 	 | STACKSIZE_K { $$ = "STACKSIZE"; }
+	 | UNDEFINED { $$ = "UNDEFINED"; }
 	 | VERSIONK { $$ = "VERSION"; }
 	 | WRITE { $$ = "WRITE"; }
 	 ;
@@ -344,6 +350,18 @@  symbol_list:
 	|	symbol_list ',' anylang_id { def_exclude_symbols ($3); }
 	;
 
+undlist:
+	anylang_id { def_undefined ($1); }
+	|	undlist anylang_id { def_undefined ($2); }
+	|	undlist ',' anylang_id { def_undefined ($3); }
+	;
+
+inclist:
+	anylang_id { def_include ($1); }
+	|	inclist anylang_id { def_include ($2); }
+	|	inclist ',' anylang_id { def_include ($3); }
+	;
+
 opt_digits: DIGITS	{ $$ = $1; }
 	|		{ $$ = ""; }
 	;
@@ -496,6 +514,7 @@  def_file_free (def_file *fdef)
     }
 
   symbol_list_clear (&fdef->exclude_symbols);
+  symbol_list_clear (&fdef->undefined_symbols);
 
   free (fdef);
 }
@@ -1056,6 +1075,13 @@  def_file_add_exclude_symbol (def_file *fdef,
   symbol_list_add (&fdef->exclude_symbols, name);
 }
 
+static void
+def_file_add_undefined_symbol (def_file *fdef,
+			       const char *name)
+{
+  symbol_list_add (&fdef->undefined_symbols, name);
+}
+
 struct
 {
   char *param;
@@ -1069,6 +1095,8 @@  diropts[] =
   { "-export", EXPORTS },
   { "-aligncomm", ALIGNCOMM },
   { "-exclude-symbols", EXCLUDE_SYMBOLS },
+  { "-include", INCLUDE },
+  { "-undefined", UNDEFINED },
   { 0, 0 }
 };
 
@@ -1387,6 +1415,19 @@  def_exclude_symbols (char *str)
   def_file_add_exclude_symbol (def, str);
 }
 
+static void
+def_undefined (char *str)
+{
+  def_file_add_undefined_symbol (def, str);
+}
+
+static void
+def_include (char *str)
+{
+  /* include is an alias for undefined */
+  def_undefined (str);
+}
+
 static void
 def_error (const char *err)
 {
@@ -1437,6 +1478,7 @@  tokens[] =
   { "EXPORTS", EXPORTS },
   { "HEAPSIZE", HEAPSIZE },
   { "IMPORTS", IMPORTS },
+  { "INCLUDE", INCLUDE },
   { "LIBRARY", LIBRARY },
   { "NAME", NAME },
   { "NONAME", NONAMEU },
@@ -1448,6 +1490,7 @@  tokens[] =
   { "SEGMENTS", SECTIONS },
   { "SHARED", SHARED_K },
   { "STACKSIZE", STACKSIZE_K },
+  { "UNDEFINED", UNDEFINED },
   { "VERSION", VERSIONK },
   { "WRITE", WRITE },
   { 0, 0 }
diff --git a/ld/pe-dll.c b/ld/pe-dll.c
index 6fb3dce2..972742b5 100644
--- a/ld/pe-dll.c
+++ b/ld/pe-dll.c
@@ -784,6 +784,9 @@  process_def_file_and_drectve (bfd *abfd ATTRIBUTE_UNUSED, struct bfd_link_info *
   for (ui = 0; ui < pe_def_file->exclude_symbols.count; ui++)
     pe_dll_add_excludes (pe_def_file->exclude_symbols.array[ui], EXCLUDESYMS);
 
+  for (ui = 0; ui < pe_def_file->undefined_symbols.count; ui++)
+    lang_add_gc_name (pe_def_file->undefined_symbols.array[ui]);
+
   /* If we are building an executable and there is nothing
      to export, we do not build an export table at all.  */
   if (bfd_link_executable (info) && pe_def_file->num_exports == 0
diff --git a/ld/testsuite/ld-pe/pe.exp b/ld/testsuite/ld-pe/pe.exp
index b95f61b8..7f6806ca 100644
--- a/ld/testsuite/ld-pe/pe.exp
+++ b/ld/testsuite/ld-pe/pe.exp
@@ -177,6 +177,10 @@  run_dump_test "exclude-symbols-embedded-i386"
 run_dump_test "exclude-symbols-embedded-x86_64"
 run_dump_test "exclude-symbols-def-i386"
 run_dump_test "exclude-symbols-def-x86_64"
+run_dump_test "undefined-embedded-i386"
+run_dump_test "undefined-embedded-x86_64"
+run_dump_test "undefined-def-i386"
+run_dump_test "undefined-def-x86_64"
 
 if [check_shared_lib_support] {
     set old_ASFLAGS $ASFLAGS
diff --git a/ld/testsuite/ld-pe/undefined-def-i386.d b/ld/testsuite/ld-pe/undefined-def-i386.d
new file mode 100755
index 00000000..dd7943ee
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-def-i386.d
@@ -0,0 +1,16 @@ 
+#source: undefined-def-i386.s
+#target: i?86-*-cygwin* i?86-*-pe i?86-*-mingw*
+#ld: --gc-sections -shared ${srcdir}/${subdir}/undefined-def-i386.def
+#objdump: -h
+
+#not .*sym3.*
+#...
+Sections:
+Idx Name.*
+\s+\d+ export1 .*
+^.*$
+\s+\d+ sym1 .*
+^.*$
+\s+\d+ sym2 .*
+^.*$
+#pass
diff --git a/ld/testsuite/ld-pe/undefined-def-i386.def b/ld/testsuite/ld-pe/undefined-def-i386.def
new file mode 100755
index 00000000..3c023fc4
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-def-i386.def
@@ -0,0 +1,4 @@ 
+LIBRARY undefined-def.dll
+UNDEFINED
+_sym1
+_sym2
diff --git a/ld/testsuite/ld-pe/undefined-def-i386.s b/ld/testsuite/ld-pe/undefined-def-i386.s
new file mode 100755
index 00000000..2b89178a
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-def-i386.s
@@ -0,0 +1,19 @@ 
+.global _export1
+.global _sym1
+.global _sym2
+.global _sym3
+.section export1,"xr"
+_export1:
+  ret
+.section sym1,"xr"
+_sym1:
+  ret
+.section sym2,"xr"
+_sym2:
+  ret
+.section sym3,"xr"
+_sym3:
+  ret
+
+.section .drectve,"yn"
+.ascii " -export:export1"
diff --git a/ld/testsuite/ld-pe/undefined-def-x86_64.d b/ld/testsuite/ld-pe/undefined-def-x86_64.d
new file mode 100755
index 00000000..52314c91
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-def-x86_64.d
@@ -0,0 +1,16 @@ 
+#source: undefined-def-x86_64.s
+#target: x86_64-*-cygwin* x86_64-*-pe x86_64-*-mingw*
+#ld: --gc-sections -shared ${srcdir}/${subdir}/undefined-def-x86_64.def
+#objdump: -h
+
+#not .*sym3.*
+#...
+Sections:
+Idx Name.*
+\s+\d+ export1 .*
+^.*$
+\s+\d+ sym1 .*
+^.*$
+\s+\d+ sym2 .*
+^.*$
+#pass
diff --git a/ld/testsuite/ld-pe/undefined-def-x86_64.def b/ld/testsuite/ld-pe/undefined-def-x86_64.def
new file mode 100755
index 00000000..18b4b6d9
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-def-x86_64.def
@@ -0,0 +1,4 @@ 
+LIBRARY undefined-def.dll
+UNDEFINED
+sym1
+sym2
diff --git a/ld/testsuite/ld-pe/undefined-def-x86_64.s b/ld/testsuite/ld-pe/undefined-def-x86_64.s
new file mode 100755
index 00000000..6d2c70a5
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-def-x86_64.s
@@ -0,0 +1,19 @@ 
+.global export1
+.global sym1
+.global sym2
+.global sym3
+.section export1,"xr"
+export1:
+  ret
+.section sym1,"xr"
+sym1:
+  ret
+.section sym2,"xr"
+sym2:
+  ret
+.section sym3,"xr"
+sym3:
+  ret
+
+.section .drectve,"yn"
+.ascii " -export:export1"
diff --git a/ld/testsuite/ld-pe/undefined-embedded-i386.d b/ld/testsuite/ld-pe/undefined-embedded-i386.d
new file mode 100755
index 00000000..690dbf77
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-embedded-i386.d
@@ -0,0 +1,16 @@ 
+#source: undefined-embedded-i386.s
+#target: i?86-*-cygwin* i?86-*-pe i?86-*-mingw*
+#ld: --gc-sections -shared
+#objdump: -h
+
+#not .*sym3.*
+#...
+Sections:
+Idx Name.*
+\s+\d+ export1 .*
+^.*$
+\s+\d+ sym1 .*
+^.*$
+\s+\d+ sym2 .*
+^.*$
+#pass
diff --git a/ld/testsuite/ld-pe/undefined-embedded-i386.s b/ld/testsuite/ld-pe/undefined-embedded-i386.s
new file mode 100755
index 00000000..d229429f
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-embedded-i386.s
@@ -0,0 +1,21 @@ 
+.global _export1
+.global _sym1
+.global _sym2
+.global _sym3
+.section export1,"xr"
+_export1:
+  ret
+.section sym1,"xr"
+_sym1:
+  ret
+.section sym2,"xr"
+_sym2:
+  ret
+.section sym3,"xr"
+_sym3:
+  ret
+
+.section .drectve,"yn"
+.ascii " -export:export1"
+.ascii " -undefined:_sym1,_unknownsym"
+.ascii " -include:_unknownsym,_sym2"
diff --git a/ld/testsuite/ld-pe/undefined-embedded-x86_64.d b/ld/testsuite/ld-pe/undefined-embedded-x86_64.d
new file mode 100755
index 00000000..5ceceb40
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-embedded-x86_64.d
@@ -0,0 +1,16 @@ 
+#source: undefined-embedded-x86_64.s
+#target: x86_64-*-cygwin* x86_64-*-pe x86_64-*-mingw*
+#ld: --gc-sections -shared
+#objdump: -h
+
+#not .*sym3.*
+#...
+Sections:
+Idx Name.*
+\s+\d+ export1 .*
+^.*$
+\s+\d+ sym1 .*
+^.*$
+\s+\d+ sym2 .*
+^.*$
+#pass
diff --git a/ld/testsuite/ld-pe/undefined-embedded-x86_64.s b/ld/testsuite/ld-pe/undefined-embedded-x86_64.s
new file mode 100755
index 00000000..1331c3f0
--- /dev/null
+++ b/ld/testsuite/ld-pe/undefined-embedded-x86_64.s
@@ -0,0 +1,21 @@ 
+.global export1
+.global sym1
+.global sym2
+.global sym3
+.section export1,"xr"
+export1:
+  ret
+.section sym1,"xr"
+sym1:
+  ret
+.section sym2,"xr"
+sym2:
+  ret
+.section sym3,"xr"
+sym3:
+  ret
+
+.section .drectve,"yn"
+.ascii " -export:export1"
+.ascii " -undefined:sym1,unknownsym"
+.ascii " -include:unknownsym,sym2"