[PATCH v3] x86: Add tls check in gas

Jan Beulich jbeulich@suse.com
Thu Sep 12 15:04:14 GMT 2024


On 11.09.2024 08:08, Cui, Lili wrote:
> @@ -6353,6 +6379,305 @@ static INLINE bool may_need_pass2 (const insn_template *t)
>  	       && (t->base_opcode | 8) == 0x2c);
>  }
>  
> +static enum x86_tls_error_type
> +x86_check_tls_relocation (enum bfd_reloc_code_real r_type)
> +{
> +  switch (r_type)
> +    {
> +    case BFD_RELOC_386_TLS_GOTDESC:
> +      /* Check GDesc access model:
> +
> +	 leal x@tlsdesc(%ebx), %reg --> Memory reg must be %ebx and
> +					SIB is not supported.
> +       */
> +      if (i.tm.mnem_off != MN_lea)
> +	return x86_tls_error_insn;
> +      if (i.index_reg)
> +	return x86_tls_error_sib;
> +      if (!i.base_reg)
> +	return x86_tls_error_no_base_reg;
> +      if (i.base_reg->reg_type.bitfield.instance != RegB)
> +	return x86_tls_error_ebx;
> +      if (!i.op[1].regs->reg_type.bitfield.dword)
> +	return x86_tls_error_dest_reg_size;
> +      break;
> +
> +    case BFD_RELOC_386_TLS_GD:
> +      /* Check GD access model:
> +
> +	 leal foo@tlsgd(,%ebx,1), %eax   --> Only this fixed format is supported.
> +	 leal foo@tlsgd(%reg), %eax      --> Dest reg must be '%eax'
> +					     Memory reg can't be %eax.
> +       */
> +      if (i.tm.mnem_off != MN_lea)
> +	return x86_tls_error_insn;
> +      if (i.op[1].regs->reg_type.bitfield.instance != Accum)
> +	return x86_tls_error_dest_eax;
> +      if (!i.op[1].regs->reg_type.bitfield.dword)
> +	return x86_tls_error_dest_reg_size;
> +      if (i.index_reg)
> +	{
> +	  if (i.base_reg)
> +	    return x86_tls_error_base_reg_name;
> +	  if (i.index_reg->reg_type.bitfield.instance != RegB)
> +	    return x86_tls_error_index_ebx;
> +	  if (i.log2_scale_factor)
> +	    return x86_tls_error_scale_factor;
> +	}
> +      else
> +	{
> +	  if (!i.base_reg)
> +	    return x86_tls_error_no_base_reg;
> +	  if (i.base_reg->reg_type.bitfield.instance == Accum)
> +	    return x86_tls_error_RegA;
> +	}
> +      break;
> +
> +    case BFD_RELOC_386_TLS_LDM:
> +      /*  Check LDM access model:
> +
> +	  leal foo@tlsldm(%reg), %eax --> Dest reg must be '%eax'
> +					  Memory reg can't be %eax and SIB
> +					  is not supported.
> +       */
> +      if (i.tm.mnem_off != MN_lea)
> +	return x86_tls_error_insn;
> +      if (i.index_reg)
> +	return x86_tls_error_sib;
> +      if (!i.base_reg)
> +	return x86_tls_error_no_base_reg;
> +      if (i.base_reg->reg_type.bitfield.instance == Accum)
> +	return x86_tls_error_RegA;
> +      if (i.op[1].regs->reg_type.bitfield.instance != Accum)
> +	return x86_tls_error_dest_eax;
> +      if (!i.op[1].regs->reg_type.bitfield.dword)
> +	return x86_tls_error_dest_reg_size;
> +      break;
> +
> +    case BFD_RELOC_X86_64_GOTPC32_TLSDESC:
> +      /* Check GOTPC32 TLSDESC access model:
> +
> +	 --- LP64 mode ---
> +	 leaq x@tlsdesc(%rip), %reg64 --> Memory reg must be %rip.
> +
> +	 --- X32 mode ---
> +	 rex/rex2 leal x@tlsdesc(%rip), %reg32 --> Memory reg must be %rip.
> +
> +	 In X32 mode, when dest is Egpr, gas will automatically add rex2 to it.
> +	 When dest is Gpr, we need to check whether there is a rex/rex2 prefix.
> +       */
> +      if (i.tm.mnem_off != MN_lea)
> +	return x86_tls_error_insn;
> +      if (!i.base_reg)
> +	return x86_tls_error_no_base_reg;
> +      if (i.base_reg->reg_num != RegIP)
> +	return x86_tls_error_rip;

Just to double check: %eip as "memory reg" is okay?

> +      if (i.op[1].regs->reg_type.bitfield.dword
> +	       && !(i.op[1].regs->reg_flags & RegRex2)

Nit: Indentation looks to be wrong starting from here.

> +	       && (pp.rex2_encoding == false
> +		   && pp.rex_encoding == false
> +		   && i.prefix[REX_PREFIX] != REX_OPCODE))

Why is this last set separately parenthesized? And as mentionbed before:
Please don't ever compare booleans against true or false. Use booleans
as booleans; that's what they are for.

Where's the operand size check for both cases, yielding
x86_tls_error_dest_reg_size when it fails?

> +	return x86_tls_error_REX_prefix;
> +      break;
> +
> +    case BFD_RELOC_X86_64_TLSGD:
> +      /* Check GD access model:
> +
> +	 leaq foo@tlsgd(%rip), %rdi --> Only this fixed format is supported.
> +       */
> +    case BFD_RELOC_X86_64_TLSLD:
> +      /* Check LD access model:
> +
> +	 leaq foo@tlsld(%rip), %rdi --> Only this fixed format is supported.
> +       */
> +      if (i.tm.mnem_off != MN_lea)
> +	return x86_tls_error_insn;
> +      if (!i.base_reg)
> +	return x86_tls_error_no_base_reg;
> +      if (i.base_reg->reg_num != RegIP)
> +	return x86_tls_error_rip;
> +      if (!i.op[1].regs->reg_type.bitfield.qword
> +	  || i.op[1].regs->reg_num != EDI_REG_NUM
> +	  || i.op[1].regs->reg_flags)
> +	return x86_tls_error_dest_rdi;
> +      break;
> +
> +    case BFD_RELOC_386_TLS_GOTIE:
> +      /* Check GOTIE access model:
> +
> +	 subl foo@gotntpoff(%reg1), %reg2
> +	 movl foo@gotntpoff(%reg1), %reg2
> +	 addl foo@gotntpoff(%reg1), %reg2
> +
> +	 Memory operand: SIB is not supported.
> +       */
> +    case BFD_RELOC_386_TLS_IE_32:
> +      /* Check IE_32 access model:
> +
> +	 subl foo@gottpoff(%reg1), %reg2
> +	 movl foo@gottpoff(%reg1), %reg2
> +	 addl foo@gottpoff(%reg1), %reg2
> +
> +	 Memory operand: SIB is not supported.
> +       */
> +      if (i.tm.mnem_off != MN_sub
> +	  && i.tm.mnem_off != MN_add
> +	  && i.tm.mnem_off != MN_mov)
> +	return x86_tls_error_insn;

If I'm not mistaken my prior question here wasn't answered: All forms
of these are okay? Without checking, how do you even know ...

> +      if (!i.base_reg)
> +	return x86_tls_error_no_base_reg;
> +      if (i.index_reg)
> +	return x86_tls_error_sib;
> +      if (!i.op[1].regs->reg_type.bitfield.dword)
> +	return x86_tls_error_dest_reg_size;

... that i.op[1].regs is valid (i.e. that operand is a register)? Just
to take an example.

> +      break;
> +
> +    case BFD_RELOC_386_TLS_IE:
> +      /* Check IE access model:
> +
> +	 movl foo@indntpoff, %reg --> Mod == 00 && r/m == 5
> +	 addl foo@indntpoff, %reg --> Mod == 00 && r/m == 5
> +       */
> +      if (i.tm.mnem_off != MN_add && i.tm.mnem_off != MN_mov)
> +	return x86_tls_error_insn;
> +      if (i.base_reg || i.index_reg)
> +	return x86_tls_error_require_no_base_index_reg;
> +      if (!i.op[1].regs->reg_type.bitfield.dword)
> +	return x86_tls_error_dest_reg_size;

Same issue here.

> +      break;
> +
> +    case BFD_RELOC_X86_64_GOTTPOFF:
> +      /* Check GOTTPOFF access model:
> +
> +	 mov foo@gottpoff(%rip), %reg --> Memory Reg must be %rip.
> +	 add foo@gottpoff(%rip), %reg --> Memory Reg must be %rip.
> +       */
> +      if (i.tm.mnem_off != MN_add && i.tm.mnem_off != MN_mov)
> +	return x86_tls_error_insn;
> +      if (!i.base_reg)
> +	return x86_tls_error_no_base_reg;
> +      if (i.base_reg->reg_num != RegIP)
> +	return x86_tls_error_rip;

And similarly here, even if in this case you don't de-reference a
pointer that may not point to where you want it.

> +      break;
> +
> +    case BFD_RELOC_386_TLS_DESC_CALL:
> +      /* Check GDesc access model:
> +
> +	 call *x@tlscall(%eax) --> Memory reg must be %eax and
> +				   SIB is not supported.
> +       */
> +    case BFD_RELOC_X86_64_TLSDESC_CALL:
> +      /* Check GDesc access model:
> +
> +	 call *x@tlscall(%rax) <--- LP64 mode.
> +	 call *x@tlscall(%eax) <--- X32 mode.
> +
> +	 Only these fixed formats are supported.
> +       */
> +      if (i.tm.mnem_off != MN_call)
> +	return x86_tls_error_insn;
> +      if (i.index_reg)
> +	return x86_tls_error_sib;
> +      if (!i.base_reg)
> +	return x86_tls_error_no_base_reg;
> +      if (i.base_reg->reg_type.bitfield.instance != Accum)
> +	return x86_tls_error_RegA;

No address size check?

> +static void
> +x86_report_tls_error (enum x86_tls_error_type tls_error, enum bfd_reloc_code_real r_type)
> +{
> +  unsigned int k;
> +  for (k = 0; k < ARRAY_SIZE (gotrel); k++)
> +    if (gotrel[k].rel[object_64bit] == r_type)
> +      break;
> +
> +  switch (tls_error)
> +    {
> +    case x86_tls_error_insn:
> +      as_bad (_("@%s operator cannot be used with `%s'"),
> +	      gotrel[k].str, insn_name (&i.tm));
> +      return;
> +
> +    case x86_tls_error_sib:
> +      as_bad (_("@%s operator requires no SIB"), gotrel[k].str);
> +      return;
> +
> +    case x86_tls_error_no_base_reg:
> +      as_bad (_("@%s operator requires base register"), gotrel[k].str);
> +      return;
> +
> +    case x86_tls_error_require_no_base_index_reg:
> +      as_bad (_("@%s operator requires no base/index register"),
> +	      gotrel[k].str);
> +      return;
> +
> +    case x86_tls_error_base_reg_name:
> +      as_bad (_("@%s operator requires no base register"), gotrel[k].str);
> +      return;

What does "name" stand for in the enumerator's identifier?

> +    case x86_tls_error_index_ebx:
> +      as_bad (_("@%s operator requires `%sebx' as index register"),
> +	      gotrel[k].str, register_prefix);
> +      return;
> +
> +    case x86_tls_error_RegA:
> +      as_bad (_("@%s operator requires `%seax/rax' as base register"),
> +	      gotrel[k].str, register_prefix);
> +      return;

This diag is okay only if indeed both registers are okay. I think that
isn't (always) the case. And of course the % prefix needs to appear on
every register name (as long as we're in prefix-enabled mode).

Jan


More information about the Binutils mailing list