[PATCH v2] Write alignment of `.tls` into `_tls_used.Characteristics`

LIU Hao lh_mouse@126.com
Fri Sep 18 14:56:29 GMT 2026


This adds a check for `tls_sect->alignment_power <= 13`, similar to LLD: 
https://github.com/llvm/llvm-project/blob/8fe013c728a2d29722925e9bebbd454a5e3ad365/llvm/include/llvm/Object/COFF.h#L598


 From 05e0987c5196c8656bf35daede0bafb05166fb35 Mon Sep 17 00:00:00 2001
From: LIU Hao <lh_mouse@126.com>
Date: Tue, 15 Sep 2026 17:16:29 +0800
Subject: [PATCH] Write alignment of `.tls` into `_tls_used.Characteristics`

Since Windows 8.1 SDK, Microsoft have replaced
`IMAGE_TLS_DIRECTORY{32,64}::Characteristics` with

    union {
      DWORD Characteristics;
      struct {
        DWORD Reserved0 : 20;
        DWORD Alignment : 4;
        DWORD Reserved1 : 8;
      };
    };

When a user defines an over-aligned thread-local variable like

    alignas(128) thread_local int overaligned = 42;

the alignment of TLS template data is now encoded in `_tls_used.Alignment`, in
the same way as the characteristics of a section. The system will allocate
storage for TLS variables with this alignment, so the variables will be aligned
properly.

This matches output of Clang (and also MSVC):

    $ echo 'alignas(128) thread_local int overaligned = 42;
    > int main(void) { return overaligned; }' \
    > | clang++ -xc++ - \
    > && llvm-readobj --coff-tls-directory a.exe

    File: a.exe
    Format: COFF-x86-64
    Arch: x86_64
    AddressSize: 64bit
    TLSDirectory {
      StartAddressOfRawData: 0x140007000
      EndAddressOfRawData: 0x140007088
      AddressOfIndex: 0x140005018
      AddressOfCallBacks: 0x1400034C8
      SizeOfZeroFill: 0x0
      Characteristics [ (0x800000)
        IMAGE_SCN_ALIGN_128BYTES (0x800000)
      ]
    }

On older systems, `_tls_used.Characteristics` is ignored and TLS variables are
always allocated with default alignment (tested on Windows 98 SE, XP SP3 and 7
SP2); it doesn't fail at least.

Signed-off-by: LIU Hao <lh_mouse@126.com>
---
  bfd/peXXigen.c | 53 +++++++++++++++++++++++++++++++++++++++++++++-----
  1 file changed, 48 insertions(+), 5 deletions(-)

diff --git a/bfd/peXXigen.c b/bfd/peXXigen.c
index f604a84ec0a..cc2544a73ed 100644
--- a/bfd/peXXigen.c
+++ b/bfd/peXXigen.c
@@ -4615,11 +4615,54 @@ _bfd_XXi_final_link_postscript (bfd * abfd, struct coff_final_link_info *pfinfo)
  	   || h1->root.type == bfd_link_hash_defweak)
  	  && h1->root.u.def.section != NULL
  	  && h1->root.u.def.section->output_section != NULL)
-	pe_data (abfd)->pe_opthdr.DataDirectory[PE_TLS_TABLE].VirtualAddress =
-	  (h1->root.u.def.value
-	   + h1->root.u.def.section->output_section->vma
-	   + h1->root.u.def.section->output_offset
-	   - pe_data (abfd)->pe_opthdr.ImageBase);
+	{
+	  pe_data (abfd)->pe_opthdr.DataDirectory[PE_TLS_TABLE].VirtualAddress =
+	    (h1->root.u.def.value
+	     + h1->root.u.def.section->output_section->vma
+	     + h1->root.u.def.section->output_offset
+	     - pe_data (abfd)->pe_opthdr.ImageBase);
+
+	  /* Set the alignment of the output `.tls` section which contains
+	     TLS template data, into `_tls_used.Characteristics`.  The other
+	     bits are set to zeros.  */
+	  asection *tls_sect = bfd_get_section_by_name (abfd, ".tls");
+	  if (tls_sect != NULL)
+	    {
+	      /* Valid alignment values are 1 to 2**13 (8192 bytes).  */
+	      if (tls_sect->alignment_power <= 13)
+		{
+#if !defined(COFF_WITH_pep) && !defined(COFF_WITH_pex64) && !defined(COFF_WITH_peAArch64) && 
!defined(COFF_WITH_peLoongArch64) && !defined (COFF_WITH_peRiscV64)
+		  uint32_t offset_of_Characteristics = 0x14;
+#else
+		  uint32_t offset_of_Characteristics = 0x24;
+#endif
+		  char temp[4];
+		  bfd_put_32 (abfd, IMAGE_SCN_ALIGN_POWER_CONST (tls_sect->alignment_power), temp);
+		  if (!bfd_set_section_contents (abfd,
+						 h1->root.u.def.section->output_section,
+						 temp,
+						 (h1->root.u.def.value
+						  + h1->root.u.def.section->output_offset
+						  + offset_of_Characteristics),
+						 4))
+		    {
+		      _bfd_error_handler
+			(_("%pB: unable to fill in DataDirectory[%d]: could not"
+			   " write %s.Characteristics"),
+			 abfd, PE_TLS_TABLE, name);
+		      result = false;
+		    }
+		}
+	      else
+		{
+		  _bfd_error_handler
+		    (_("%pB: unable to fill in DataDirectory[%d]: alignment of"
+		       " .tls section (%d) is too large"),
+		     abfd, PE_TLS_TABLE, 1 << tls_sect->alignment_power);
+		  result = false;
+		}
+	    }
+	}
        else
  	{
  	  _bfd_error_handler
-- 
2.55.0


-------------- next part --------------
From 05e0987c5196c8656bf35daede0bafb05166fb35 Mon Sep 17 00:00:00 2001
From: LIU Hao <lh_mouse@126.com>
Date: Tue, 15 Sep 2026 17:16:29 +0800
Subject: [PATCH] Write alignment of `.tls` into `_tls_used.Characteristics`

Since Windows 8.1 SDK, Microsoft have replaced
`IMAGE_TLS_DIRECTORY{32,64}::Characteristics` with

   union {
     DWORD Characteristics;
     struct {
       DWORD Reserved0 : 20;
       DWORD Alignment : 4;
       DWORD Reserved1 : 8;
     };
   };

When a user defines an over-aligned thread-local variable like

   alignas(128) thread_local int overaligned = 42;

the alignment of TLS template data is now encoded in `_tls_used.Alignment`, in
the same way as the characteristics of a section. The system will allocate
storage for TLS variables with this alignment, so the variables will be aligned
properly.

This matches output of Clang (and also MSVC):

   $ echo 'alignas(128) thread_local int overaligned = 42;
   > int main(void) { return overaligned; }' \
   > | clang++ -xc++ - \
   > && llvm-readobj --coff-tls-directory a.exe

   File: a.exe
   Format: COFF-x86-64
   Arch: x86_64
   AddressSize: 64bit
   TLSDirectory {
     StartAddressOfRawData: 0x140007000
     EndAddressOfRawData: 0x140007088
     AddressOfIndex: 0x140005018
     AddressOfCallBacks: 0x1400034C8
     SizeOfZeroFill: 0x0
     Characteristics [ (0x800000)
       IMAGE_SCN_ALIGN_128BYTES (0x800000)
     ]
   }

On older systems, `_tls_used.Characteristics` is ignored and TLS variables are
always allocated with default alignment (tested on Windows 98 SE, XP SP3 and 7
SP2); it doesn't fail at least.

Signed-off-by: LIU Hao <lh_mouse@126.com>
---
 bfd/peXXigen.c | 53 +++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 48 insertions(+), 5 deletions(-)

diff --git a/bfd/peXXigen.c b/bfd/peXXigen.c
index f604a84ec0a..cc2544a73ed 100644
--- a/bfd/peXXigen.c
+++ b/bfd/peXXigen.c
@@ -4615,11 +4615,54 @@ _bfd_XXi_final_link_postscript (bfd * abfd, struct coff_final_link_info *pfinfo)
 	   || h1->root.type == bfd_link_hash_defweak)
 	  && h1->root.u.def.section != NULL
 	  && h1->root.u.def.section->output_section != NULL)
-	pe_data (abfd)->pe_opthdr.DataDirectory[PE_TLS_TABLE].VirtualAddress =
-	  (h1->root.u.def.value
-	   + h1->root.u.def.section->output_section->vma
-	   + h1->root.u.def.section->output_offset
-	   - pe_data (abfd)->pe_opthdr.ImageBase);
+	{
+	  pe_data (abfd)->pe_opthdr.DataDirectory[PE_TLS_TABLE].VirtualAddress =
+	    (h1->root.u.def.value
+	     + h1->root.u.def.section->output_section->vma
+	     + h1->root.u.def.section->output_offset
+	     - pe_data (abfd)->pe_opthdr.ImageBase);
+
+	  /* Set the alignment of the output `.tls` section which contains
+	     TLS template data, into `_tls_used.Characteristics`.  The other
+	     bits are set to zeros.  */
+	  asection *tls_sect = bfd_get_section_by_name (abfd, ".tls");
+	  if (tls_sect != NULL)
+	    {
+	      /* Valid alignment values are 1 to 2**13 (8192 bytes).  */
+	      if (tls_sect->alignment_power <= 13)
+		{
+#if !defined(COFF_WITH_pep) && !defined(COFF_WITH_pex64) && !defined(COFF_WITH_peAArch64) && !defined(COFF_WITH_peLoongArch64) && !defined (COFF_WITH_peRiscV64)
+		  uint32_t offset_of_Characteristics = 0x14;
+#else
+		  uint32_t offset_of_Characteristics = 0x24;
+#endif
+		  char temp[4];
+		  bfd_put_32 (abfd, IMAGE_SCN_ALIGN_POWER_CONST (tls_sect->alignment_power), temp);
+		  if (!bfd_set_section_contents (abfd,
+						 h1->root.u.def.section->output_section,
+						 temp,
+						 (h1->root.u.def.value
+						  + h1->root.u.def.section->output_offset
+						  + offset_of_Characteristics),
+						 4))
+		    {
+		      _bfd_error_handler
+			(_("%pB: unable to fill in DataDirectory[%d]: could not"
+			   " write %s.Characteristics"),
+			 abfd, PE_TLS_TABLE, name);
+		      result = false;
+		    }
+		}
+	      else
+		{
+		  _bfd_error_handler
+		    (_("%pB: unable to fill in DataDirectory[%d]: alignment of"
+		       " .tls section (%d) is too large"),
+		     abfd, PE_TLS_TABLE, 1 << tls_sect->alignment_power);
+		  result = false;
+		}
+	    }
+	}
       else
 	{
 	  _bfd_error_handler
-- 
2.55.0

-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature.asc
Type: application/pgp-signature
Size: 840 bytes
Desc: OpenPGP digital signature
URL: <https://sourceware.org/pipermail/binutils/attachments/20260918/db6bb4aa/attachment.sig>


More information about the Binutils mailing list