[PATCH] aarch64: Add support for %dtprel(var) and R_AARCH64_TLS_DTPREL64

Alice Carlotti alice.carlotti@arm.com
Tue Mar 31 21:37:47 GMT 2026


On Sat, Mar 14, 2026 at 02:11:53AM +0530, Shivam Gupta wrote:
> This patch allows R_AARCH64_TLS_DTPREL64 relocations in non-allocated
> sections, which is required for DWARF debug information when using
> Thread Local Storage. This matches the behavior in LLD.
> 
> Also a new syntax to parse dtprel operator use to describe tls
> location in debug information. Please see the reference 3 below.
> 
> References:
>   - https://github.com/llvm/llvm-project/pull/146572
>     [AArch64] Support TLS variables in debug info
>   - https://github.com/llvm/llvm-project/pull/183962
>     [LLD][AArch64] Handle R_AARCH64_TLS_DTPREL64 in non-alloc sections
>   - https://github.com/ARM-software/abi-aa/pull/330
>     [AAELF64] Allow R_AARCH64_TLS_DTPREL to be used statically.
> 

Thanks, and apologies for the delay getting back to you (mostly due to holidays
and the upcoming GCC release).  I have a few comments and questions below.

> bfd/
>         * elfnn-aarch64.c (elfNN_aarch64_final_link_relocate): Handle
>         BFD_RELOC_AARCH64_TLS_DTPREL.
> 
> gas/
> 	* config/tc-aarch64.c (s_aarch64_cons): Parse %dtprel(var) syntax.
> 	* testsuite/gas/aarch64/tls-debug.s: New test.
> 	* testsuite/gas/aarch64/tls-debug.d: Run the test.
> 
> gold/
>         * aarch64-reloc-property.h (code_to_array_index): Handle
>         R_AARCH64_TLS_DTPREL64.
>         * aarch64.cc (Relocate::relocate): Handle R_AARCH64_TLS_DTPREL64.
> 
> ld/
> 	* testsuite/ld-aarch64/tls-debug.s: New test.
> 	* testsuite/ld-aarch64/tls-debug.d: Run the test.
> 
> Signed-off-by: Shivam Gupta <shivam98.tkg@gmail.com>
> ---
>  bfd/elfnn-aarch64.c                     | 12 +++++++
>  gas/config/tc-aarch64.c                 | 44 +++++++++++++++++++++++++
>  gas/testsuite/gas/aarch64/tls-debug.d   | 10 ++++++
>  gas/testsuite/gas/aarch64/tls-debug.s   |  9 +++++
>  gold/aarch64-reloc-property.h           |  2 ++
>  gold/aarch64.cc                         |  8 ++++-
>  ld/testsuite/ld-aarch64/aarch64-elf.exp |  1 +
>  ld/testsuite/ld-aarch64/tls-debug.d     |  9 +++++
>  ld/testsuite/ld-aarch64/tls-debug.s     |  9 +++++
>  9 files changed, 103 insertions(+), 1 deletion(-)
>  create mode 100644 gas/testsuite/gas/aarch64/tls-debug.d
>  create mode 100644 gas/testsuite/gas/aarch64/tls-debug.s
>  create mode 100644 ld/testsuite/ld-aarch64/tls-debug.d
>  create mode 100644 ld/testsuite/ld-aarch64/tls-debug.s
> 
> diff --git a/bfd/elfnn-aarch64.c b/bfd/elfnn-aarch64.c
> index 66049c81..acf207dd 100644
> --- a/bfd/elfnn-aarch64.c
> +++ b/bfd/elfnn-aarch64.c
> @@ -6496,6 +6496,18 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
>        *unresolved_reloc_p = false;
>        break;
>  
> +    case BFD_RELOC_AARCH64_TLS_DTPREL:
> +      /* Handle R_AARCH64_TLS_DTPREL64 in non-alloc sections  */
> +      if (!(input_section->flags & SEC_ALLOC))

For SEC_ALLOC sections, shouldn't we still return bfd_reloc_notsupported?  This
seems to just silently skip over that case.

> +	{
> +	  value -= dtpoff_base (info);
> +	  value += rel->r_addend;
> +
> +	  bfd_put_64 (output_bfd, value, contents + rel->r_offset);
> +	}
> +      *unresolved_reloc_p = false;
> +      break;
> +
>      case BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC:
>      case BFD_RELOC_AARCH64_TLSDESC_OFF_G1:
>        if (globals->root.sgot == NULL)
> diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
> index cd761634..6cc7481a 100644
> --- a/gas/config/tc-aarch64.c
> +++ b/gas/config/tc-aarch64.c
> @@ -2229,6 +2229,40 @@ s_aarch64_cons (int nbytes)
>      {
>        struct reloc_table_entry *reloc;
>  
> +      /* Check for %dtprel(var) syntax */
> +      if (*input_line_pointer == '%')
> +	{
> +	  char *p = ++input_line_pointer; /* skip '%' */
> +
> +	  /* Look for "dtprel" */
> +	  if (strncmp (p, "dtprel", 6) == 0 && p[6] == '(')
> +	    {
> +	      input_line_pointer = p + 7; /* Skip "dtprel(" */
> +
> +	      /* Parse the symbol inside the parentheses */
> +	      expression (&exp);
> +
> +	      /* Ensure we have a closing parenthesis */
> +	      if (*input_line_pointer == ')')
> +		input_line_pointer++;
> +	      else
> +		as_bad (_("missing ')' after %%dtprel(...)"));

Should we skip the rest of the line here and return?  It doesn't seem sensible
to continue parsing when the syntax is already broken.


> +
> +	      /* Create the fixup using your DTPREL ID */
> +	      addressT where = frag_now_fix ();
> +	      fix_new_exp (frag_now, where, nbytes, &exp, 0,
> +			   BFD_RELOC_AARCH64_TLS_DTPREL);
> +
> +	      char *dest = frag_more (nbytes);
> +	      memset (dest, 0, nbytes);
> +	      continue;
> +	    }
> +	  else
> +	    {
> +	      as_bad (_("unknown relocation operator %%%s"), p);

Same here?  Additionally, is p an appropriate argument to passing here with this
error message?

> +	    }
> +	}
> +
>        expression (&exp);
>  
>        if (exp.X_op != O_symbol)
> @@ -3292,6 +3326,15 @@ static struct reloc_table_entry reloc_table[] =
>     0,
>     0},
>  
> +  /* TLS relative offset.  */
> +  {"dtprel", 0,
> +   0,        /* adr_type */
> +   0,
> +   0,
> +   0,
> +   0,
> +   BFD_RELOC_AARCH64_TLS_DTPREL},
> +

This hunk looks wrong (BFD_RELOC_AARCH64_TLS_DTPREL is not a ld_literal_type
relocation), and also unnecessary (this table is used for instruction
relocations, but BFD_RELOC_AARCH64_TLS_DTPREL is a data relocation).

>    /* Lower 16 bit offset into GOT entry for a symbol */
>    {"tlsdesc_off_g0_nc", 0,
>     0,				/* adr_type */
> @@ -10114,6 +10157,7 @@ md_apply_fix (fixS * fixP, valueT * valP, segT seg)
>      case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1:
>      case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
>      case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2:
> +    case BFD_RELOC_AARCH64_TLS_DTPREL:
>        S_SET_THREAD_LOCAL (fixP->fx_addsy);
>        /* Should always be exported to object file, see
>  	 aarch64_force_relocation().  */
> diff --git a/gas/testsuite/gas/aarch64/tls-debug.d b/gas/testsuite/gas/aarch64/tls-debug.d
> new file mode 100644
> index 00000000..a64ff67f
> --- /dev/null
> +++ b/gas/testsuite/gas/aarch64/tls-debug.d
> @@ -0,0 +1,10 @@
> +#as:
> +#objdump: -r
> +#target: aarch64*-*-*
> +
> +#...
> +RELOCATION RECORDS FOR \[\.debug_info\]:
> +OFFSET +TYPE +VALUE
> +0+0 R_AARCH64_TLS_DTPREL64 +var
> +0+8 R_AARCH64_TLS_DTPREL64 +var\+0x0+1

Minor regex nit: I think it would make more sense to use 0* instead of 0+ for
leading zeroes, but it's fine either way.

> +#...
> diff --git a/gas/testsuite/gas/aarch64/tls-debug.s b/gas/testsuite/gas/aarch64/tls-debug.s
> new file mode 100644
> index 00000000..9590ad40
> --- /dev/null
> +++ b/gas/testsuite/gas/aarch64/tls-debug.s
> @@ -0,0 +1,9 @@
> +.section .tdata,"awT",@progbits
> +.skip 8          // Force var to have an offset of 8
> +.globl var
> +var:
> +  .word 0
> +
> +.section        .debug_info,"",@progbits
> +  .xword  %dtprel(var)
> +  .xword  %dtprel(var+1)
> diff --git a/gold/aarch64-reloc-property.h b/gold/aarch64-reloc-property.h
> index 986adf9d..c1ce0033 100644
> --- a/gold/aarch64-reloc-property.h
> +++ b/gold/aarch64-reloc-property.h
> @@ -220,6 +220,8 @@ class AArch64_reloc_property_table
>    code_to_array_index(unsigned int code) const
>    {
>      if (code == 0) return 0;
> +    if (code == elfcpp::R_AARCH64_TLS_DTPREL64)
> +      return 0;

I have no idea why this change would be needed.

I'll note that gold is currently deprecated, so I don't how much value there is
in adding new features to it - perhaps someone else can comment on the
situation.

>      if (!((code >= elfcpp::R_AARCH64_ABS64 &&
>  	   code <= elfcpp::R_AARCH64_LD64_GOTPAGE_LO15)
>  	  || (code >= elfcpp::R_AARCH64_TLSGD_ADR_PREL21 &&
> diff --git a/gold/aarch64.cc b/gold/aarch64.cc
> index 9ef5df79..5575f78b 100644
> --- a/gold/aarch64.cc
> +++ b/gold/aarch64.cc
> @@ -7330,13 +7330,19 @@ Target_aarch64<size, big_endian>::Relocate::relocate(
>  				  gsym, psymval, view, address);
>        break;
>  
> +    case elfcpp::R_AARCH64_TLS_DTPREL64:
> +      {
> +	uint64_t val = psymval->value (object, 0) + rela.get_r_addend ();
> +	elfcpp::Swap<64, big_endian>::writeval (view, val);
> +	break;
> +      }
> +
>      // These are dynamic relocations, which are unexpected when linking.
>      case elfcpp::R_AARCH64_COPY:
>      case elfcpp::R_AARCH64_GLOB_DAT:
>      case elfcpp::R_AARCH64_JUMP_SLOT:
>      case elfcpp::R_AARCH64_RELATIVE:
>      case elfcpp::R_AARCH64_IRELATIVE:
> -    case elfcpp::R_AARCH64_TLS_DTPREL64:
>      case elfcpp::R_AARCH64_TLS_DTPMOD64:
>      case elfcpp::R_AARCH64_TLS_TPREL64:
>      case elfcpp::R_AARCH64_TLSDESC:
> diff --git a/ld/testsuite/ld-aarch64/aarch64-elf.exp b/ld/testsuite/ld-aarch64/aarch64-elf.exp
> index a0a8867d..096f5f9f 100644
> --- a/ld/testsuite/ld-aarch64/aarch64-elf.exp
> +++ b/ld/testsuite/ld-aarch64/aarch64-elf.exp
> @@ -252,6 +252,7 @@ run_dump_test "tls-relax-ld-le-small-ilp32"
>  run_dump_test "tls-relax-ld-le-tiny"
>  run_dump_test "tls-relax-ld-le-tiny-ilp32"
>  run_dump_test "tls-desc-ie"
> +run_dump_test "tls-debug"
>  run_dump_test "tls-desc-ie-ilp32"
>  run_dump_test "tls-relax-gdesc-le-2"
>  run_dump_test "tls-relax-gdesc-le-2-ilp32"
> diff --git a/ld/testsuite/ld-aarch64/tls-debug.d b/ld/testsuite/ld-aarch64/tls-debug.d
> new file mode 100644
> index 00000000..4344b02e
> --- /dev/null
> +++ b/ld/testsuite/ld-aarch64/tls-debug.d
> @@ -0,0 +1,9 @@
> +#source: tls-debug.s
> +#ld: -shared -T relocs.ld -e0
> +#objdump: -s -j .debug_info
> +
> +#...
> +Contents of section .debug_info:
> +#...
> + [0-9a-f]+ 08000000 00000000 09000000 00000000 .*
> +#...
> diff --git a/ld/testsuite/ld-aarch64/tls-debug.s b/ld/testsuite/ld-aarch64/tls-debug.s
> new file mode 100644
> index 00000000..9590ad40
> --- /dev/null
> +++ b/ld/testsuite/ld-aarch64/tls-debug.s
> @@ -0,0 +1,9 @@
> +.section .tdata,"awT",@progbits
> +.skip 8          // Force var to have an offset of 8
> +.globl var
> +var:
> +  .word 0
> +
> +.section        .debug_info,"",@progbits
> +  .xword  %dtprel(var)
> +  .xword  %dtprel(var+1)
> -- 
> 2.34.1
> 


More information about the Binutils mailing list