PE-COFF: Fix link failure of C++ code with debug info after partial linking

Message ID 2696145.Lt9SDvczpP@fomalhaut
State New
Headers
Series PE-COFF: Fix link failure of C++ code with debug info after partial linking |

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

Eric Botcazou March 25, 2026, 3:56 p.m. UTC
  Hi,

if you apply the following recipe to the attached C/C++ files with a PE-COFF 
toolchain, you get the specified output:

1. g++ -c clib.cpp cpplib.cpp test.c -g
2. g++ -o pl.o clib.o cpplib.o -nostdlib -Wl,-r
3. objcopy pl.o
objcopy.exe: pl.o: warning: COMDAT symbol 
'.debug_frame$_ZNSt12_Vector_baseIiSaIiEE12_Vector_implD1Ev' does not match 
section name '.debug_frame'
4. g++ -o test test.o pl.o
ld.exe: pl.o: warning: COMDAT symbol 
'.debug_frame$_ZNSt12_Vector_baseIiSaIiEE12_Vector_implD1Ev' does not match 
section name '.debug_frame'
pl.o:clib.cpp:(.pdata$_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPiy+0x0): 
relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
`.text$_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPiy'
pl.o:clib.cpp:(.pdata$_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPiy+0x4): 
relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
`.text$_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPiy'
pl.o:clib.cpp:(.pdata$_ZNSt12_Vector_baseIiSaIiEE19_M_get_Tp_allocatorEv+0x0): 
relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
`.text$_ZNSt12_Vector_baseIiSaIiEE19_M_get_Tp_allocatorEv'
pl.o:clib.cpp:(.pdata$_ZNSt12_Vector_baseIiSaIiEE19_M_get_Tp_allocatorEv+0x4): 
relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
`.text$_ZNSt12_Vector_baseIiSaIiEE19_M_get_Tp_allocatorEv'
pl.o:clib.cpp:(.pdata$_ZNSt15__new_allocatorIiE10deallocateEPiy+0x0): 
relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
`.text$_ZNSt15__new_allocatorIiE10deallocateEPiy'
pl.o:clib.cpp:(.pdata$_ZNSt15__new_allocatorIiE10deallocateEPiy+0x4): 
relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
`.text$_ZNSt15__new_allocatorIiE10deallocateEPiy'
collect2.exe: error: ld returned 1 exit status

The problem pertains to section symbols generated for COMDAT sections: they
are marked as local symbols as per Microsoft's PE-COFF specification, but
partial linking discards the duplicate COMDAT sections without being able
to either merge them, or remove them when they are used in a relocation.

So they end up as undefined local symbols after the partial link, which in
turn may cause the final link to fail (in practice you need e.g. a call to
objcopy in between, because it moves them to the end of the symbol list).

This change instructs the linker to "relocate" them instead, that is to say
to attach them to the one COMDAT section that is output among the multiple
COMDAT sections that are duplicate.  It also prevents partial linking from
prematurely globing the .[z]debug_frame* sections together, as already done
for the .eh_frame* sections.

Tested on x86_64-w64-mingw32, OK for the mainline?


2026-03-25  Eric Botcazou  <ebotcazou@adacore.com>

bfd/
	* cofflink.c (_bfd_coff_link_input_bfd): For a relocatable output,
	relocate section symbols for input sections that are not going to
	be emitted because they are duplicate of another one in the link.
ld/
	* scripttempl/pe.sc (.debug_frame): Do not glob all .debug_frame*
	sections together when not relocating.
	(.zdebug_frame): Likewise for .zdebug_frame* sections.
	* scripttempl/pep.sc (.debug_frame): Likewise.
	(.zdebug_frame): Likewise.
  

Comments

Jan Beulich March 27, 2026, 2:33 p.m. UTC | #1
On 25.03.2026 16:56, Eric Botcazou wrote:
> Hi,
> 
> if you apply the following recipe to the attached C/C++ files with a PE-COFF 
> toolchain, you get the specified output:
> 
> 1. g++ -c clib.cpp cpplib.cpp test.c -g
> 2. g++ -o pl.o clib.o cpplib.o -nostdlib -Wl,-r
> 3. objcopy pl.o
> objcopy.exe: pl.o: warning: COMDAT symbol 
> '.debug_frame$_ZNSt12_Vector_baseIiSaIiEE12_Vector_implD1Ev' does not match 
> section name '.debug_frame'
> 4. g++ -o test test.o pl.o
> ld.exe: pl.o: warning: COMDAT symbol 
> '.debug_frame$_ZNSt12_Vector_baseIiSaIiEE12_Vector_implD1Ev' does not match 
> section name '.debug_frame'
> pl.o:clib.cpp:(.pdata$_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPiy+0x0): 
> relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
> `.text$_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPiy'
> pl.o:clib.cpp:(.pdata$_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPiy+0x4): 
> relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
> `.text$_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPiy'
> pl.o:clib.cpp:(.pdata$_ZNSt12_Vector_baseIiSaIiEE19_M_get_Tp_allocatorEv+0x0): 
> relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
> `.text$_ZNSt12_Vector_baseIiSaIiEE19_M_get_Tp_allocatorEv'
> pl.o:clib.cpp:(.pdata$_ZNSt12_Vector_baseIiSaIiEE19_M_get_Tp_allocatorEv+0x4): 
> relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
> `.text$_ZNSt12_Vector_baseIiSaIiEE19_M_get_Tp_allocatorEv'
> pl.o:clib.cpp:(.pdata$_ZNSt15__new_allocatorIiE10deallocateEPiy+0x0): 
> relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
> `.text$_ZNSt15__new_allocatorIiE10deallocateEPiy'
> pl.o:clib.cpp:(.pdata$_ZNSt15__new_allocatorIiE10deallocateEPiy+0x4): 
> relocation truncated to fit: IMAGE_REL_AMD64_ADDR32NB against 
> `.text$_ZNSt15__new_allocatorIiE10deallocateEPiy'
> collect2.exe: error: ld returned 1 exit status
> 
> The problem pertains to section symbols generated for COMDAT sections: they
> are marked as local symbols as per Microsoft's PE-COFF specification, but
> partial linking discards the duplicate COMDAT sections without being able
> to either merge them, or remove them when they are used in a relocation.
> 
> So they end up as undefined local symbols after the partial link, which in
> turn may cause the final link to fail (in practice you need e.g. a call to
> objcopy in between, because it moves them to the end of the symbol list).
> 
> This change instructs the linker to "relocate" them instead, that is to say
> to attach them to the one COMDAT section that is output among the multiple
> COMDAT sections that are duplicate.  It also prevents partial linking from
> prematurely globing the .[z]debug_frame* sections together, as already done
> for the .eh_frame* sections.
> 
> Tested on x86_64-w64-mingw32, OK for the mainline?

Yes, albeit preferably with a few small adjustments (can't comment very well
on a patch that comes only as attachment): The new variable wants to be
pointer-to-const (unless I'm overlooking a reason why it cannot be), and it
would likely help the code if the variable had *secpp as initializer, such
that in place of the three (*secpp) the local variable can then be used.

Jan
  

Patch

diff --git a/bfd/cofflink.c b/bfd/cofflink.c
index 02b68814ab4..e7adab2f0d8 100644
--- a/bfd/cofflink.c
+++ b/bfd/cofflink.c
@@ -1857,12 +1857,28 @@  _bfd_coff_link_input_bfd (struct coff_final_link_info *flaginfo, bfd *input_bfd)
 	      /* Compute new symbol location.  */
 	    if (isym.n_scnum > 0)
 	      {
-		isym.n_scnum = (*secpp)->output_section->target_index;
-		isym.n_value += (*secpp)->output_offset;
+		asection *s;
+
+		/* Relocate section symbols for sections that are not going to
+		   be emitted because they are duplicate of another one.  */
+		if (bfd_link_relocatable (flaginfo->info)
+		    && isym.n_sclass == C_STAT
+		    && isym.n_type == T_NULL
+		    && isym.n_numaux > 0
+		    && ((*secpp)->output_section == bfd_abs_section_ptr
+			|| bfd_section_removed_from_list
+			   (output_bfd, (*secpp)->output_section))
+		    && (*secpp)->kept_section)
+		  s = (*secpp)->kept_section;
+		else
+		  s = *secpp;
+
+		isym.n_scnum = s->output_section->target_index;
+		isym.n_value += s->output_offset;
 		if (! obj_pe (input_bfd))
-		  isym.n_value -= (*secpp)->vma;
+		  isym.n_value -= s->vma;
 		if (! obj_pe (flaginfo->output_bfd))
-		  isym.n_value += (*secpp)->output_section->vma;
+		  isym.n_value += s->output_section->vma;
 	      }
 	    break;
 
diff --git a/ld/scripttempl/pe.sc b/ld/scripttempl/pe.sc
index f8d7c8ef6e7..a669f279eda 100644
--- a/ld/scripttempl/pe.sc
+++ b/ld/scripttempl/pe.sc
@@ -364,11 +364,11 @@  SECTIONS
 
   .debug_frame ${RELOCATING+BLOCK(__section_alignment__)} ${RELOCATING+(NOLOAD)} :
   {
-    *(.debug_frame*)
+    *(.debug_frame${RELOCATING+*})
   }
   .zdebug_frame ${RELOCATING+BLOCK(__section_alignment__)} ${RELOCATING+(NOLOAD)} :
   {
-    *(.zdebug_frame*)
+    *(.zdebug_frame${RELOCATING+*})
   }
 
   .debug_str ${RELOCATING+BLOCK(__section_alignment__)} ${RELOCATING+(NOLOAD)} :
diff --git a/ld/scripttempl/pep.sc b/ld/scripttempl/pep.sc
index a5b4679544c..45e785492c5 100644
--- a/ld/scripttempl/pep.sc
+++ b/ld/scripttempl/pep.sc
@@ -373,11 +373,11 @@  SECTIONS
 
   .debug_frame ${RELOCATING+BLOCK(__section_alignment__)} ${RELOCATING+(NOLOAD)} :
   {
-    *(.debug_frame*)
+    *(.debug_frame${RELOCATING+*})
   }
   .zdebug_frame ${RELOCATING+BLOCK(__section_alignment__)} ${RELOCATING+(NOLOAD)} :
   {
-    *(.zdebug_frame*)
+    *(.zdebug_frame${RELOCATING+*})
   }
 
   .debug_str ${RELOCATING+BLOCK(__section_alignment__)} ${RELOCATING+(NOLOAD)} :