diff mbox

[RFC] Add IFUNC support for MIPS

Message ID DCB1C42372B1674B8F912A294CCB775A71684631@BADAG02.ba.imgtec.org
State New
Headers show

Commit Message

Faraz Shahbazker July 31, 2015, 12:52 p.m. UTC
Hi,

This is a follow up on IFUNC support for MIPS. Previous patch discussion on this list was:
https://sourceware.org/ml/libc-ports/2013-08/msg00007.html

This patch is intended to work with the corresponding binutils patch, on-going review at:
https://sourceware.org/ml/binutils/2015-07/msg00213.html

Detailed description is in the attached text file. Please comment.

Thanks,
Faraz Shahbazker

Changelog:

elf/
	* elf.h
	(R_MIPS_IRELATIVE): New relocation type.
	(R_MIPS_NUM): Bump up to 129.
	(DT_MIPS_GENERAL_GOTNO): New dynamic tags.
	(DT_MIPS_NUM): Bump to 0x37.

sysdeps/mips
	* dl-irel.h: New file.
	(elf_ifunc_invoke): New function.
	(elf_irel): Likewise.
	* dl-machine.h
	Include new dl-irel.h
	(ELF_MACHINE_BEFORE_RTLD_RELOC): Use DT_MIPS_GENERAL_GOTNO tag, if
	present, to find the start of the normally relocated GOT.
	(elf_machine_reloc): Add skip_ifunc to parameter.
	Add case for R_MIPS_IRELATIVE. Modify REL32 to check for pre-emption
	in case symbol is IFUNC and perform IFUNC indirection.
	(elf_machine_rel): Add skip_ifunc to call to elf_machine_reloc().
	(elf_machine_rela):Add skip_ifunc to call to elf_machine_reloc().
	(RESOLVE_GOTSYM): Add check for STT_GNU_IFUNC.
	(elf_machine_got_rel): Add check for STT_GNU_IFUNC. Use
	DT_MIPS_GENERAL_GOTNO tag, if present, to find the start of the
	normally relocated GOT. Skip normal GOT relocation for global GOT
	entries corresponding to IFUNCs.
	* dl-trampoline.c:
	(__dl_runtime_resolve): Add check for STT_GNU_IFUNC.
---
 elf/elf.h                    |    6 ++-
 sysdeps/mips/dl-irel.h       |   63 +++++++++++++++++++++++++++++++
 sysdeps/mips/dl-machine.h    |   86 ++++++++++++++++++++++++++++++++++++------
 sysdeps/mips/dl-trampoline.c |    4 ++
 4 files changed, 145 insertions(+), 14 deletions(-)
 create mode 100644 sysdeps/mips/dl-irel.h

Comments

Joseph Myers July 31, 2015, 3:30 p.m. UTC | #1
On Fri, 31 Jul 2015, Faraz Shahbazker wrote:

> Hi,
> 
> This is a follow up on IFUNC support for MIPS. Previous patch discussion on this list was:
> https://sourceware.org/ml/libc-ports/2013-08/msg00007.html

To what extent does the previous description apply to this patch?  In 
particular, as regards the ABIs and other features supported.  How has 
this patch been tested?

> elf/
> 	* elf.h

glibc does not have per-subdirectory ChangeLog files (except for libidn 
and localedata).  So ChangeLog entries should contain the full paths to 
the files relative to the toplevel glibc source directory, elf/elf.h and 
sysdeps/mips/*.h.

I'd expect an addition to sysdeps/unix/sysv/linux/mips/libc-abis for 
binary compatibility checks using EI_ABIVERSION to work properly (along 
with binutils setting EI_ABIVERSION to 4 when IFUNCs are used for MIPS).
Richard Sandiford Aug. 2, 2015, 2:03 p.m. UTC | #2
Thanks for the patch.

Faraz Shahbazker <Faraz.Shahbazker@imgtec.com> writes:
> @@ -200,10 +201,13 @@ do {									\
>    if (__builtin_expect (map->l_addr == 0, 1))				\
>      break;								\
>  									\
> -  /* got[0] is reserved. got[1] is also reserved for the dynamic object	\
> -     generated by gnu ld. Skip these reserved entries from		\
> -     relocation.  */							\
> -  i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;				\
> +  if (__glibc_unlikely (map->l_info[DT_MIPS (GENERAL_GOTNO)] != NULL))	\
> +    i = map->l_info[DT_MIPS (GENERAL_GOTNO)]->d_un.d_val;		\
> +  else									\
> +    /* got[0] is reserved. got[1] is also reserved for the dynamic	\
> +       object generated by gnu ld. Skip these reserved entries from	\
> +       relocation.  */							\
> +    i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;			\
>    n = map->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;			\

I'm not sure we should consider the new tag to be unlikely.  If the
intention is to use ifuncs in libc then it would eventually become
pretty common.

> +		/* Symbol pre-empted, use value from GOT.
> +		 2nd part of condition is redundant, explicit for clarity.  */
> +		if (__glibc_unlikely (rmap->l_relocated)
> +		    && __glibc_unlikely (rmap != map))
> +		  {
> +		    const ElfW(Addr) *got
> +		      = (const ElfW(Addr) *) D_PTR (rmap, l_info[DT_PLTGOT]);
> +		    const ElfW(Word) local_gotno
> +		      = (const ElfW(Word))
> +		      rmap->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;
> +		    symidx = (sym
> +			      - (ElfW(Sym) *) D_PTR(rmap, l_info[DT_SYMTAB]))
> +		      / sizeof (sym);
> +		    reloc_value = got[symidx + local_gotno - gotsym];
> +		  }
> +		/* Symbol pre-empted by not yet relocated, use best guess.  */
> +		else if (__glibc_unlikely (rmap != map))
> +		  reloc_value = sym->st_value + rmap->l_addr;
> +		  /* Resolve IFUNC in this link unit.  */
> +		else if (__glibc_likely (ELFW(ST_TYPE) (sym->st_info)
> +					 == STT_GNU_IFUNC))
> +		  reloc_value = elf_ifunc_invoke (sym->st_value + map->l_addr);
> +		else
> +		  reloc_value += sym->st_value + map->l_addr;
> +	      }
> +#endif
>  	    else
>  	      {
>  #ifndef RTLD_BOOTSTRAP

Does this mean that an STT_GNU_IFUNC can have two GOT entries,
one in the new explicitly-relocated region and one in the normal
global GOT?  The way I'd imagined it working is that STT_GNU_IFUNC
would be an exception to the usual ABI rule that every symbol involved
in a relocation needs a GOT entry.  Instead STT_GNU_IFUNCs would be
kept below the DT_MIPS_GOTSYM threshold and the:

	    if ((ElfW(Word))symidx < gotsym)
	      {
	      }

block would then handle global symbols too.  The IFUNC handling would be
very similar to other targets.

I suppose one downside of doing that for all global symbols is that we
would then silently accept broken objects that older ld.sos wouldn't.
If we really want to guard against that, we could raise a fatal error
if a global symbol below gotsym is seen in a relocation and if
DT_MIPS_GENERAL_GOTNO isn't set.  The new behaviour would then be
restricted to objects that use the new GOT layout.

> @@ -795,8 +844,18 @@ elf_machine_got_rel (struct link_map *map, int lazy)
>        const struct r_found_version *version __attribute__ ((unused))	  \
>  	= vernum ? &map->l_versions[vernum[sym_index] & 0x7fff] : NULL;	  \
>        struct link_map *sym_map;						  \
> +      ElfW(Addr) value = 0;						  \
>        sym_map = RESOLVE_MAP (&ref, version, reloc);			  \
> -      ref ? sym_map->l_addr + ref->st_value : 0;			  \
> +      if (__glibc_likely(ref != NULL))					  \
> +	{								  \
> +	  value = sym_map->l_addr + ref->st_value;			  \
> +	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		  \
> +				== STT_GNU_IFUNC			  \
> +				&& sym_map != map			  \
> +				&& sym_map->l_relocated))		  \
> +	      value = elf_ifunc_invoke (value);				  \
> +	}								  \
> +      value;								  \
>      })

With that change, I think we should either:

(a) make STT_GNU_IFUNCs in the global GOT be a fatal error or

(b) call the resolver unconditionally, without the sym_map-based checks.
    We shouldn't resolve an IFUNC without calling the resolver.

It's a question of whether it's more defensive to support IFUNCs in the
ABI global GOT in the obvious way (b), in case we find a use for it later,
or whether it's more defensive to rule it out until we know it has
a use case (a).

Anything that did want to put IFUNCs in the ABI global GOT would need
to be sure that the resolver doesn't rely on unrelocated data.  But in
cases where that holds, traditional global entries might be (slightly)
more efficient.

Thanks,
Richard
Richard Sandiford Aug. 3, 2015, 12:41 p.m. UTC | #3
[Farez, sorry for the double message]

Richard Sandiford <rdsandiford@googlemail.com> writes:
> Faraz Shahbazker <Faraz.Shahbazker@imgtec.com> writes:
>> @@ -795,8 +844,18 @@ elf_machine_got_rel (struct link_map *map, int lazy)
>>        const struct r_found_version *version __attribute__ ((unused))	  \
>>  	= vernum ? &map->l_versions[vernum[sym_index] & 0x7fff] : NULL;	  \
>>        struct link_map *sym_map;						  \
>> +      ElfW(Addr) value = 0;						  \
>>        sym_map = RESOLVE_MAP (&ref, version, reloc);			  \
>> -      ref ? sym_map->l_addr + ref->st_value : 0;			  \
>> +      if (__glibc_likely(ref != NULL))					  \
>> +	{								  \
>> +	  value = sym_map->l_addr + ref->st_value;			  \
>> +	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		  \
>> +				== STT_GNU_IFUNC			  \
>> +				&& sym_map != map			  \
>> +				&& sym_map->l_relocated))		  \
>> +	      value = elf_ifunc_invoke (value);				  \
>> +	}								  \
>> +      value;								  \
>>      })
>
> With that change, I think we should either:
>
> (a) make STT_GNU_IFUNCs in the global GOT be a fatal error or
>
> (b) call the resolver unconditionally, without the sym_map-based checks.
>     We shouldn't resolve an IFUNC without calling the resolver.

Sorry, I misread this.  I thought it was checking the st_info of the
original symbol rather than the resolved one.

In that case I think we should do (b) -- remove the sym_map checks.

Thanks,
Richard
Faraz Shahbazker Aug. 3, 2015, 7:10 p.m. UTC | #4
On 08/02/2015 07:03 AM, Richard Sandiford wrote:
> I'm not sure we should consider the new tag to be unlikely.  If the
> intention is to use ifuncs in libc then it would eventually become
> pretty common.
Agreed.

>> +		/* Symbol pre-empted, use value from GOT.
>> +		 2nd part of condition is redundant, explicit for clarity.  */
>> +		if (__glibc_unlikely (rmap->l_relocated)
>> +		    && __glibc_unlikely (rmap != map))
>> +		  {
>> +		    const ElfW(Addr) *got
>> +		      = (const ElfW(Addr) *) D_PTR (rmap, l_info[DT_PLTGOT]);
>> +		    const ElfW(Word) local_gotno
>> +		      = (const ElfW(Word))
>> +		      rmap->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;
>> +		    symidx = (sym
>> +			      - (ElfW(Sym) *) D_PTR(rmap, l_info[DT_SYMTAB]))
>> +		      / sizeof (sym);
>> +		    reloc_value = got[symidx + local_gotno - gotsym];
>> +		  }
>> +		/* Symbol pre-empted by not yet relocated, use best guess.  */
>> +		else if (__glibc_unlikely (rmap != map))
>> +		  reloc_value = sym->st_value + rmap->l_addr;
>> +		  /* Resolve IFUNC in this link unit.  */
>> +		else if (__glibc_likely (ELFW(ST_TYPE) (sym->st_info)
>> +					 == STT_GNU_IFUNC))
>> +		  reloc_value = elf_ifunc_invoke (sym->st_value + map->l_addr);
>> +		else
>> +		  reloc_value += sym->st_value + map->l_addr;
>> +	      }
> 
> Does this mean that an STT_GNU_IFUNC can have two GOT entries,
> one in the new explicitly-relocated region and one in the normal
> global GOT?  
No. My intention is to keep global IFUNCs in normal global GOT only, so that the ABI method of mapping symbol index to GOT entry always works. The obvious anomaly is that the ABI global GOT now contains entries that are explicitly relocated. OTOH, by virtue of information present in the dynamic symbol table, these GOT entries are in no danger of being relocated twice.

> The way I'd imagined it working is that STT_GNU_IFUNC
> would be an exception to the usual ABI rule that every symbol involved
> in a relocation needs a GOT entry.  Instead STT_GNU_IFUNCs would be
> kept below the DT_MIPS_GOTSYM threshold and the:
> 
> 	    if ((ElfW(Word))symidx < gotsym)
> 	      {
> 	      }
> 
> block would then handle global symbols too.  The IFUNC handling would be
> very similar to other targets.
We would still need to be able to map symbol index to GOT entry for global symbols, right? So if we have a set of global GOT entries within the new explicit GOT region for all global preemptible IFUNC symbols, we would need them sorted to be contiguous in the dynamic symbol table and add a new dynamic tag(DT_MIPS_GENERAL_GOTSYM) to mark the first index in this range. All instances of global GOT lookup would then be modified to check whether the symbol is IFUNC or normal. Is that what you are nudging me towards?

>> @@ -795,8 +844,18 @@ elf_machine_got_rel (struct link_map *map, int lazy)
>>        const struct r_found_version *version __attribute__ ((unused))	  \
>>  	= vernum ? &map->l_versions[vernum[sym_index] & 0x7fff] : NULL;	  \
>>        struct link_map *sym_map;						  \
>> +      ElfW(Addr) value = 0;						  \
>>        sym_map = RESOLVE_MAP (&ref, version, reloc);			  \
>> -      ref ? sym_map->l_addr + ref->st_value : 0;			  \
>> +      if (__glibc_likely(ref != NULL))					  \
>> +	{								  \
>> +	  value = sym_map->l_addr + ref->st_value;			  \
>> +	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		  \
>> +				== STT_GNU_IFUNC			  \
>> +				&& sym_map != map			  \
>> +				&& sym_map->l_relocated))		  \
>> +	      value = elf_ifunc_invoke (value);				  \
>> +	}								  \
>> +      value;								  \
>>      })
> 
> With that change, I think we should either:
> (b) call the resolver unconditionally, without the sym_map-based checks.
>     We shouldn't resolve an IFUNC without calling the resolver.
> 
> It's a question of whether it's more defensive to support IFUNCs in the
> ABI global GOT in the obvious way (b), in case we find a use for it later,
> or whether it's more defensive to rule it out until we know it has
> a use case (a).
> 
> Anything that did want to put IFUNCs in the ABI global GOT would need
> to be sure that the resolver doesn't rely on unrelocated data.  But in
> cases where that holds, traditional global entries might be (slightly)
> more efficient.

My understanding is: anything that wants to _invoke_ an IFUNC resolver needs to be sure that the resolver doesn't rely on unrelocated data. I'd rather have those sym_map checks in place and allow the program to at least load, with the unresolved IFUNC, without causing a crash. There is the possibility that an IFUNC which was not resolved at load time, does not get invoked at all during execution. Allowing a crash in ld.so due to the way the application is written makes me uncomfortable. I know the sym_map-check won't handle the most general case(mutual dependency, etc). If you deem it unnecessary, I will gladly defer to your judgement on this!

I did earlier consider replacing the elf_ifunc_invoke() in this case with a GOT lookup:

+      if (__glibc_likely(ref != NULL))					       \
+	{								       \
+	  value = sym_map->l_addr + ref->st_value;			       \
+	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		       \
+				== STT_GNU_IFUNC			       \
+				&& sym_map != map			       \
+				&& sym_map->l_relocated))		       \
+	  {								       \
+	    const ElfW(Addr) *got					       \
+		= (const ElfW(Addr) *) D_PTR (sym_map, l_info[DT_PLTGOT]);     \
+	    const ElfW(Word) local_gotno				       \
+	        = (const ElfW(Word))					       \
+                  sym_map->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;	       \
+	    symidx = (ref - (ElfW(Sym) *) D_PTR(sym_map, l_info[DT_SYMTAB]))   \
+		  / sizeof (ref);					       \
+	    reloc_value = got[symidx + local_gotno - gotsym];		       \
+	  }								       \
+	}								       \
+      value;						                       \

But couldn't make up my mind on which code would be more efficient.

To be clear, ^that^ particular bit of code is only relevant for an undefined global symbol that resolves to an IFUNC in another ELF. IFUNCs in ABI global GOT are already excluded/deferred by the following hunk, as they are each assured to have an explicit R_MIPS_REL32 relocations.

@@ -866,5 +928,5 @@ elf_machine_got_rel (struct link_map *map, int lazy)
 	  if (sym->st_other == 0)
 	    *got += map->l_addr;
 	}
-      else
+      else if (ELFW(ST_TYPE) (sym->st_info) != STT_GNU_IFUNC)
 	*got = RESOLVE_GOTSYM (sym, vernum, symidx, R_MIPS_32);

Regards,
Faraz Shahbazker
Faraz Shahbazker Aug. 3, 2015, 7:29 p.m. UTC | #5
I fudged the text formatting on my mail client in the previous message. Hopefully, 
this version will read better:

On 08/02/2015 07:03 AM, Richard Sandiford wrote:
> I'm not sure we should consider the new tag to be unlikely.  If the
> intention is to use ifuncs in libc then it would eventually become
> pretty common.
Agreed.

>>> +		/* Symbol pre-empted, use value from GOT.
>>> +		 2nd part of condition is redundant, explicit for clarity.  */
>>> +		if (__glibc_unlikely (rmap->l_relocated)
>>> +		    && __glibc_unlikely (rmap != map))
>>> +		  {
>>> +		    const ElfW(Addr) *got
>>> +		      = (const ElfW(Addr) *) D_PTR (rmap, l_info[DT_PLTGOT]);
>>> +		    const ElfW(Word) local_gotno
>>> +		      = (const ElfW(Word))
>>> +		      rmap->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;
>>> +		    symidx = (sym
>>> +			      - (ElfW(Sym) *) D_PTR(rmap, l_info[DT_SYMTAB]))
>>> +		      / sizeof (sym);
>>> +		    reloc_value = got[symidx + local_gotno - gotsym];
>>> +		  }
>>> +		/* Symbol pre-empted by not yet relocated, use best guess.  */
>>> +		else if (__glibc_unlikely (rmap != map))
>>> +		  reloc_value = sym->st_value + rmap->l_addr;
>>> +		  /* Resolve IFUNC in this link unit.  */
>>> +		else if (__glibc_likely (ELFW(ST_TYPE) (sym->st_info)
>>> +					 == STT_GNU_IFUNC))
>>> +		  reloc_value = elf_ifunc_invoke (sym->st_value + map->l_addr);
>>> +		else
>>> +		  reloc_value += sym->st_value + map->l_addr;
>>> +	      }
> 
> Does this mean that an STT_GNU_IFUNC can have two GOT entries,
> one in the new explicitly-relocated region and one in the normal
> global GOT?  

No. My intention is to keep global IFUNCs in normal global GOT only, so that
the ABI method of mapping symbol index to GOT entry always works. The obvious
anomaly is that the ABI global GOT now contains entries that are explicitly
relocated. OTOH, by virtue of information present in the dynamic symbol table,
these GOT entries are in no danger of being relocated twice.

> The way I'd imagined it working is that STT_GNU_IFUNC
> would be an exception to the usual ABI rule that every symbol involved
> in a relocation needs a GOT entry.  Instead STT_GNU_IFUNCs would be
> kept below the DT_MIPS_GOTSYM threshold and the:
> 
> 	    if ((ElfW(Word))symidx < gotsym)
> 	      {
> 	      }
> 
> block would then handle global symbols too.  The IFUNC handling would be
> very similar to other targets.

We would still need to be able to map symbol index to GOT entry for global
symbols, right? So if we have a set of global GOT entries within the new
explicit GOT region for all global preemptible IFUNC symbols, we would need
them sorted to be contiguous in the dynamic symbol table and add a new dynamic
tag(DT_MIPS_GENERAL_GOTSYM) to mark the first index in this range. All
instances of global GOT lookup would then be modified to check whether the
symbol is IFUNC or normal. Is that what you are nudging me towards?

>>> @@ -795,8 +844,18 @@ elf_machine_got_rel (struct link_map *map, int lazy)
>>>        const struct r_found_version *version __attribute__ ((unused)) \
>>>  	= vernum ? &map->l_versions[vernum[sym_index] & 0x7fff] : NULL;	  \
>>>        struct link_map *sym_map;					  \
>>> +      ElfW(Addr) value = 0;					  \
>>>        sym_map = RESOLVE_MAP (&ref, version, reloc);		  \
>>> -      ref ? sym_map->l_addr + ref->st_value : 0;			  \
>>> +      if (__glibc_likely(ref != NULL))				  \
>>> +	{								  \
>>> +	  value = sym_map->l_addr + ref->st_value;			  \
>>> +	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		  \
>>> +				== STT_GNU_IFUNC			  \
>>> +				&& sym_map != map			  \
>>> +				&& sym_map->l_relocated))		  \
>>> +	      value = elf_ifunc_invoke (value);				  \
>>> +	}								  \
>>> +      value;							  \
>>>      })
> 
> With that change, I think we should either:
> (b) call the resolver unconditionally, without the sym_map-based checks.
>     We shouldn't resolve an IFUNC without calling the resolver.
> 
> It's a question of whether it's more defensive to support IFUNCs in the
> ABI global GOT in the obvious way (b), in case we find a use for it later,
> or whether it's more defensive to rule it out until we know it has
> a use case (a).
> 
> Anything that did want to put IFUNCs in the ABI global GOT would need
> to be sure that the resolver doesn't rely on unrelocated data.  But in
> cases where that holds, traditional global entries might be (slightly)
> more efficient.

My understanding is: anything that wants to _invoke_ an IFUNC resolver needs
to be sure that the resolver doesn't rely on unrelocated data. I'd rather have
those sym_map checks in place and allow the program to at least load, with the
unresolved IFUNC, without causing a crash. There is the possibility that an
IFUNC which was not resolved at load time, does not get invoked at all during
execution. Allowing a crash in ld.so due to the way the application is written
makes me uncomfortable. I know the sym_map-check won't handle the most general
case(mutual dependency, etc). If you deem it unnecessary, I will gladly defer
to your judgement on this!

I did earlier consider replacing the elf_ifunc_invoke() in this case with a
GOT lookup:

+      if (__glibc_likely(ref != NULL))					       \
+	{								       \
+	  value = sym_map->l_addr + ref->st_value;			       \
+	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		       \
+				== STT_GNU_IFUNC			       \
+				&& sym_map != map			       \
+				&& sym_map->l_relocated))		       \
+	  {								       \
+	    const ElfW(Addr) *got					       \
+		= (const ElfW(Addr) *) D_PTR (sym_map, l_info[DT_PLTGOT]);     \
+	    const ElfW(Word) local_gotno				       \
+	        = (const ElfW(Word))					       \
+                  sym_map->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;	       \
+	    symidx = (ref - (ElfW(Sym) *) D_PTR(sym_map, l_info[DT_SYMTAB]))   \
+		  / sizeof (ref);					       \
+	    reloc_value = got[symidx + local_gotno - gotsym];		       \
+	  }								       \
+	}								       \
+      value;						                       \

But couldn't make up my mind on which code would be more efficient.

To be clear, ^that^ particular bit of code is only relevant for an undefined
global symbol that resolves to an IFUNC in another ELF. IFUNCs in ABI global
GOT are already excluded/deferred by the following hunk, as they are each
assured to have an explicit R_MIPS_REL32 relocations.

@@ -866,5 +928,5 @@ elf_machine_got_rel (struct link_map *map, int lazy)
 	  if (sym->st_other == 0)
 	    *got += map->l_addr;
 	}
-      else
+      else if (ELFW(ST_TYPE) (sym->st_info) != STT_GNU_IFUNC)
 	*got = RESOLVE_GOTSYM (sym, vernum, symidx, R_MIPS_32);

Regards,
Faraz Shahbazker
Faraz Shahbazker Aug. 3, 2015, 10:48 p.m. UTC | #6
On 07/31/2015 08:30 AM, Joseph Myers wrote:
> On Fri, 31 Jul 2015, Faraz Shahbazker wrote:
>> This is a follow up on IFUNC support for MIPS. Previous patch discussion on
>> this list was: https://sourceware.org/ml/libc-ports/2013-08/msg00007.html
>
> To what extent does the previous description apply to this patch?  In 
> particular, as regards the ABIs and other features supported.  How has 
> this patch been tested?I
The previous description mostly doesn't apply, specially for shared libraries
and PIC. Mechanisms for non-PIC are roughly similar. Support for local IFUNC
symbols and non-default visibility are new additions. The story behind
what changed and why is spread across various threads on the binutils
mailing list. This is definitely not an 'incremental' update.

The intention as always is to support all ABI variants. I don't see
ABI-specific changes as far as glibc is concerned. I have tested with
o32/n32/n64/mips16/micromips (though the last 2 are due for rechecking due
to recent tweaks). I have entirely skipped vxworks.

I am working off an internal run-time testsuite - transitioning whatever can
be checked statically, to be included in the ld testsuite in binutils. Not quite
sure what to do with my functional tests - if someone could point me to where 
such tests belong ...

> glibc does not have per-subdirectory ChangeLog files (except for libidn
> and localedata).  So ChangeLog entries should contain the full paths to
> the files relative to the toplevel glibc source directory, elf/elf.h and
> sysdeps/mips/*.h.
Got it.

> I'd expect an addition to sysdeps/unix/sysv/linux/mips/libc-abis for
> binary compatibility checks using EI_ABIVERSION to work properly (along
> with binutils setting EI_ABIVERSION to 4 when IFUNCs are used for MIPS).
Got it.

Thanks for looking at this.

Regards,
Faraz Shahbazker
Richard Sandiford Aug. 4, 2015, 9:31 p.m. UTC | #7
[Sorry for mistyping your name in the previous message :-(]

Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
>>>> +		/* Symbol pre-empted, use value from GOT.
>>>> +		 2nd part of condition is redundant, explicit for clarity.  */
>>>> +		if (__glibc_unlikely (rmap->l_relocated)
>>>> +		    && __glibc_unlikely (rmap != map))
>>>> +		  {
>>>> +		    const ElfW(Addr) *got
>>>> +		      = (const ElfW(Addr) *) D_PTR (rmap, l_info[DT_PLTGOT]);
>>>> +		    const ElfW(Word) local_gotno
>>>> +		      = (const ElfW(Word))
>>>> +		      rmap->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;
>>>> +		    symidx = (sym
>>>> +			      - (ElfW(Sym) *) D_PTR(rmap, l_info[DT_SYMTAB]))
>>>> +		      / sizeof (sym);
>>>> +		    reloc_value = got[symidx + local_gotno - gotsym];
>>>> +		  }
>>>> +		/* Symbol pre-empted by not yet relocated, use best guess.  */
>>>> +		else if (__glibc_unlikely (rmap != map))
>>>> +		  reloc_value = sym->st_value + rmap->l_addr;
>>>> +		  /* Resolve IFUNC in this link unit.  */
>>>> +		else if (__glibc_likely (ELFW(ST_TYPE) (sym->st_info)
>>>> +					 == STT_GNU_IFUNC))
>>>> +		  reloc_value = elf_ifunc_invoke (sym->st_value + map->l_addr);
>>>> +		else
>>>> +		  reloc_value += sym->st_value + map->l_addr;
>>>> +	      }
>> 
>> Does this mean that an STT_GNU_IFUNC can have two GOT entries,
>> one in the new explicitly-relocated region and one in the normal
>> global GOT?  
>
> No. My intention is to keep global IFUNCs in normal global GOT only, so that
> the ABI method of mapping symbol index to GOT entry always works. The obvious
> anomaly is that the ABI global GOT now contains entries that are explicitly
> relocated. OTOH, by virtue of information present in the dynamic symbol table,
> these GOT entries are in no danger of being relocated twice.

I don't really see the benefit of that though.  It's possible for this
particular case because the specialness of the GOT entry is encoded in
the symbol type.  But the idea of the new GOT region was that we could
put any new GOT entry types there, even if the relocation is against a
normal function or data symbol (local or global).  We wouldn't have STT
to guide us then.

Even though it's possible to avoid putting global symbols in the new region
here, you seem to be jumping through some hoops to make it work.  And the
"best guess" worries me :-)  If we just treat the new GOT region as an
ordinary relocated region and allow it to refer to global symbols, we never
have to guess.  It's just a case of applying the relocation in the same way
as for any other part of the image.

There's already precendent for allowing global symbols not to be in the
directly-mapped GOT (for TLS), so it doesn't seem like that big a leap.

>> The way I'd imagined it working is that STT_GNU_IFUNC
>> would be an exception to the usual ABI rule that every symbol involved
>> in a relocation needs a GOT entry.  Instead STT_GNU_IFUNCs would be
>> kept below the DT_MIPS_GOTSYM threshold and the:
>> 
>> 	    if ((ElfW(Word))symidx < gotsym)
>> 	      {
>> 	      }
>> 
>> block would then handle global symbols too.  The IFUNC handling would be
>> very similar to other targets.
>
> We would still need to be able to map symbol index to GOT entry for global
> symbols, right?

No, I don't think so.  We just handle relocations against unmapped symbols
in the same way as other targets do.  I.e. we use RESOLVE_MAP rather than
the directly-mapped GOT cache.

> So if we have a set of global GOT entries within the new explicit GOT
> region for all global preemptible IFUNC symbols, we would need them
> sorted to be contiguous in the dynamic symbol table and add a new
> dynamic tag(DT_MIPS_GENERAL_GOTSYM) to mark the first index in this
> range. All instances of global GOT lookup would then be modified to
> check whether the symbol is IFUNC or normal. Is that what you are
> nudging me towards?

I'd like to avoid another directly-mapped region at all costs :-)

The ABI GOT scheme, including direct mapping, works very well for the
situation it was designed for.  But I don't think it extends naturally
to ifuncs (or to TLS).  Normal relocations seem like a better fit.

>>>> @@ -795,8 +844,18 @@ elf_machine_got_rel (struct link_map *map, int lazy)
>>>>        const struct r_found_version *version __attribute__ ((unused)) \
>>>>  	= vernum ? &map->l_versions[vernum[sym_index] & 0x7fff] : NULL;	  \
>>>>        struct link_map *sym_map;					  \
>>>> +      ElfW(Addr) value = 0;					  \
>>>>        sym_map = RESOLVE_MAP (&ref, version, reloc);		  \
>>>> -      ref ? sym_map->l_addr + ref->st_value : 0;			  \
>>>> +      if (__glibc_likely(ref != NULL))				  \
>>>> +	{								  \
>>>> +	  value = sym_map->l_addr + ref->st_value;			  \
>>>> +	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		  \
>>>> +				== STT_GNU_IFUNC			  \
>>>> +				&& sym_map != map			  \
>>>> +				&& sym_map->l_relocated))		  \
>>>> +	      value = elf_ifunc_invoke (value);				  \
>>>> +	}								  \
>>>> +      value;							  \
>>>>      })
>> 
>> With that change, I think we should either:
>> (b) call the resolver unconditionally, without the sym_map-based checks.
>>     We shouldn't resolve an IFUNC without calling the resolver.
>> 
>> It's a question of whether it's more defensive to support IFUNCs in the
>> ABI global GOT in the obvious way (b), in case we find a use for it later,
>> or whether it's more defensive to rule it out until we know it has
>> a use case (a).
>> 
>> Anything that did want to put IFUNCs in the ABI global GOT would need
>> to be sure that the resolver doesn't rely on unrelocated data.  But in
>> cases where that holds, traditional global entries might be (slightly)
>> more efficient.
>
> My understanding is: anything that wants to _invoke_ an IFUNC resolver needs
> to be sure that the resolver doesn't rely on unrelocated data. I'd rather have
> those sym_map checks in place and allow the program to at least load, with the
> unresolved IFUNC, without causing a crash. There is the possibility that an
> IFUNC which was not resolved at load time, does not get invoked at all during
> execution. Allowing a crash in ld.so due to the way the application is written
> makes me uncomfortable. I know the sym_map-check won't handle the most general
> case(mutual dependency, etc). If you deem it unnecessary, I will gladly defer
> to your judgement on this!

Heh.  I have no authority here. :-)  But IMO this is no different from DSOs
having to be careful about DT_INIT order.  Yes, it's bad if they get it wrong,
but we shouldn't second-guess the user.

At least a segfault at start-up is easier to debug than a symbol having
the wrong value.

That was the point of the example I sent on the binutils list.  The problem
isn't specific to MIPS and other targets call the resolver regardless.
I think we should be consistent here.

Thanks,
Richard
diff mbox

Patch

diff --git a/elf/elf.h b/elf/elf.h
index fbadda4..cfcf72c 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -1653,8 +1653,9 @@  typedef struct
 #define R_MIPS_GLOB_DAT		51
 #define R_MIPS_COPY		126
 #define R_MIPS_JUMP_SLOT        127
+#define R_MIPS_IRELATIVE        128
 /* Keep this the last entry.  */
-#define R_MIPS_NUM		128
+#define R_MIPS_NUM		129
 
 /* Legal values for p_type field of Elf32_Phdr.  */
 
@@ -1731,7 +1732,8 @@  typedef struct
    in a PIE as it stores a relative offset from the address of the tag
    rather than an absolute address.  */
 #define DT_MIPS_RLD_MAP_REL  0x70000035
-#define DT_MIPS_NUM	     0x36
+#define DT_MIPS_GENERAL_GOTNO  0x70000036 /* Number of relocated GOT entries */
+#define DT_MIPS_NUM	     0x37
 
 /* Legal values for DT_MIPS_FLAGS Elf32_Dyn entry.  */
 
diff --git a/sysdeps/mips/dl-irel.h b/sysdeps/mips/dl-irel.h
new file mode 100644
index 0000000..7e1fdc4
--- /dev/null
+++ b/sysdeps/mips/dl-irel.h
@@ -0,0 +1,63 @@ 
+/* Machine-dependent ELF indirect relocation inline functions.
+   MIPS version.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library.  If not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_IREL_H
+#define _DL_IREL_H
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sgidefs.h>
+#include <link.h>
+#include <elf.h>
+#include <ldsodefs.h>
+
+#define ELF_MACHINE_IREL	1
+
+static inline ElfW(Addr)
+__attribute ((always_inline))
+elf_ifunc_invoke (ElfW(Addr) addr)
+{
+  /* Print some debugging info if wanted.  */
+  if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_SYMBOLS, 0))
+    {
+      ElfW(Addr) t_addr =
+	((ElfW(Addr) (*) (unsigned long int)) (addr)) (GLRO(dl_hwcap));
+      GLRO(dl_debug_printf) ("In elf_ifunc_invoke(0x%lx), return(0x%lx)\n",
+			     (unsigned long int)addr,
+			     (unsigned long int)t_addr);
+    }
+
+  return ((ElfW(Addr) (*) (unsigned long int)) (addr)) (GLRO(dl_hwcap));
+}
+
+/* Allow either R_MIPS_RELATIVE or the nop R_MIPS_NONE.  */
+static inline void
+__attribute ((always_inline))
+elf_irel (const ElfW(Rel) *reloc)
+{
+  ElfW(Addr) *const reloc_addr = (void *) reloc->r_offset;
+  const unsigned long int r_type = ELFW(R_TYPE) (reloc->r_info);
+
+  if (__builtin_expect (r_type == R_MIPS_IRELATIVE, 1))
+    *reloc_addr = elf_ifunc_invoke (*reloc_addr);
+  else if (r_type)
+    __libc_fatal ("unexpected reloc type in static binary");
+}
+
+#endif /* dl-irel.h */
diff --git a/sysdeps/mips/dl-machine.h b/sysdeps/mips/dl-machine.h
index 8738564..e7e0dc4 100644
--- a/sysdeps/mips/dl-machine.h
+++ b/sysdeps/mips/dl-machine.h
@@ -33,6 +33,7 @@ 
 #include <sysdep.h>
 #include <sys/asm.h>
 #include <dl-tls.h>
+#include <dl-irel.h>
 
 /* The offset of gp from GOT might be system-dependent.  It's set by
    ld.  The same value is also */
@@ -200,10 +201,13 @@  do {									\
   if (__builtin_expect (map->l_addr == 0, 1))				\
     break;								\
 									\
-  /* got[0] is reserved. got[1] is also reserved for the dynamic object	\
-     generated by gnu ld. Skip these reserved entries from		\
-     relocation.  */							\
-  i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;				\
+  if (__glibc_unlikely (map->l_info[DT_MIPS (GENERAL_GOTNO)] != NULL))	\
+    i = map->l_info[DT_MIPS (GENERAL_GOTNO)]->d_un.d_val;		\
+  else									\
+    /* got[0] is reserved. got[1] is also reserved for the dynamic	\
+       object generated by gnu ld. Skip these reserved entries from	\
+       relocation.  */							\
+    i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;			\
   n = map->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;			\
 									\
   /* Add the run-time displacement to all local got entries. */		\
@@ -493,7 +497,8 @@  auto inline void
 __attribute__ ((always_inline))
 elf_machine_reloc (struct link_map *map, ElfW(Addr) r_info,
 		   const ElfW(Sym) *sym, const struct r_found_version *version,
-		   void *reloc_addr, ElfW(Addr) r_addend, int inplace_p)
+		   void *reloc_addr, ElfW(Addr) r_addend, int inplace_p,
+		   int skip_ifunc)
 {
   const unsigned long int r_type = ELFW(R_TYPE) (r_info);
   ElfW(Addr) *addr_field = (ElfW(Addr) *) reloc_addr;
@@ -599,6 +604,41 @@  elf_machine_reloc (struct link_map *map, ElfW(Addr) r_info,
 #endif
 		  reloc_value += sym->st_value + map->l_addr;
 	      }
+#ifndef RTLD_BOOTSTRAP
+	    /* Resolve IFUNC symbols with pre-emption.  */
+	    else if (sym
+		  && __glibc_unlikely (ELFW(ST_TYPE) (sym->st_info)
+				       == STT_GNU_IFUNC)
+		  && !skip_ifunc)
+	      {
+		struct link_map *rmap = RESOLVE_MAP (&sym, version, r_type);
+
+		/* Symbol pre-empted, use value from GOT.
+		 2nd part of condition is redundant, explicit for clarity.  */
+		if (__glibc_unlikely (rmap->l_relocated)
+		    && __glibc_unlikely (rmap != map))
+		  {
+		    const ElfW(Addr) *got
+		      = (const ElfW(Addr) *) D_PTR (rmap, l_info[DT_PLTGOT]);
+		    const ElfW(Word) local_gotno
+		      = (const ElfW(Word))
+		      rmap->l_info[DT_MIPS (LOCAL_GOTNO)]->d_un.d_val;
+		    symidx = (sym
+			      - (ElfW(Sym) *) D_PTR(rmap, l_info[DT_SYMTAB]))
+		      / sizeof (sym);
+		    reloc_value = got[symidx + local_gotno - gotsym];
+		  }
+		/* Symbol pre-empted by not yet relocated, use best guess.  */
+		else if (__glibc_unlikely (rmap != map))
+		  reloc_value = sym->st_value + rmap->l_addr;
+		  /* Resolve IFUNC in this link unit.  */
+		else if (__glibc_likely (ELFW(ST_TYPE) (sym->st_info)
+					 == STT_GNU_IFUNC))
+		  reloc_value = elf_ifunc_invoke (sym->st_value + map->l_addr);
+		else
+		  reloc_value += sym->st_value + map->l_addr;
+	      }
+#endif
 	    else
 	      {
 #ifndef RTLD_BOOTSTRAP
@@ -698,6 +738,14 @@  elf_machine_reloc (struct link_map *map, ElfW(Addr) r_info,
 	break;
       }
 
+    case R_MIPS_IRELATIVE:
+      /* The resolver routine is the symbol referenced by this relocation.
+	 To get the address of the function to use at runtime, the resolver
+	 routine is called and its return value is the address of the target
+	 functon which is final relocation value.  */
+      *addr_field = elf_ifunc_invoke (map->l_addr + *addr_field);
+      break;
+
 #if _MIPS_SIM == _ABI64
     case R_MIPS_64:
       /* For full compliance with the ELF64 ABI, one must precede the
@@ -727,7 +775,8 @@  elf_machine_rel (struct link_map *map, const ElfW(Rel) *reloc,
 		 const ElfW(Sym) *sym, const struct r_found_version *version,
 		 void *const reloc_addr, int skip_ifunc)
 {
-  elf_machine_reloc (map, reloc->r_info, sym, version, reloc_addr, 0, 1);
+  elf_machine_reloc (map, reloc->r_info, sym, version, reloc_addr, 0, 1,
+		     skip_ifunc);
 }
 
 auto inline void
@@ -768,7 +817,7 @@  elf_machine_rela (struct link_map *map, const ElfW(Rela) *reloc,
 		  void *const reloc_addr, int skip_ifunc)
 {
   elf_machine_reloc (map, reloc->r_info, sym, version, reloc_addr,
-		     reloc->r_addend, 0);
+		     reloc->r_addend, 0, skip_ifunc);
 }
 
 auto inline void
@@ -795,8 +844,18 @@  elf_machine_got_rel (struct link_map *map, int lazy)
       const struct r_found_version *version __attribute__ ((unused))	  \
 	= vernum ? &map->l_versions[vernum[sym_index] & 0x7fff] : NULL;	  \
       struct link_map *sym_map;						  \
+      ElfW(Addr) value = 0;						  \
       sym_map = RESOLVE_MAP (&ref, version, reloc);			  \
-      ref ? sym_map->l_addr + ref->st_value : 0;			  \
+      if (__glibc_likely(ref != NULL))					  \
+	{								  \
+	  value = sym_map->l_addr + ref->st_value;			  \
+	  if (__glibc_unlikely (ELFW(ST_TYPE) (ref->st_info)		  \
+				== STT_GNU_IFUNC			  \
+				&& sym_map != map			  \
+				&& sym_map->l_relocated))		  \
+	      value = elf_ifunc_invoke (value);				  \
+	}								  \
+      value;								  \
     })
 
   if (map->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
@@ -810,9 +869,12 @@  elf_machine_got_rel (struct link_map *map, int lazy)
   /* The dynamic linker's local got entries have already been relocated.  */
   if (map != &GL(dl_rtld_map))
     {
-      /* got[0] is reserved. got[1] is also reserved for the dynamic object
-	 generated by gnu ld. Skip these reserved entries from relocation.  */
-      i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;
+      if (__glibc_unlikely(map->l_info[DT_MIPS (GENERAL_GOTNO)] != NULL))
+	i = map->l_info[DT_MIPS (GENERAL_GOTNO)]->d_un.d_val;
+      else
+	/* got[0] is reserved. got[1] is also reserved for the dynamic object
+	   generated by gnu ld. Skip these reserved entries from relocation.  */
+	i = (got[1] & ELF_MIPS_GNU_GOT1_MASK)? 2 : 1;
 
       /* Add the run-time displacement to all local got entries if
 	 needed.  */
@@ -866,7 +928,7 @@  elf_machine_got_rel (struct link_map *map, int lazy)
 	  if (sym->st_other == 0)
 	    *got += map->l_addr;
 	}
-      else
+      else if (ELFW(ST_TYPE) (sym->st_info) != STT_GNU_IFUNC)
 	*got = RESOLVE_GOTSYM (sym, vernum, symidx, R_MIPS_32);
 
       ++got;
diff --git a/sysdeps/mips/dl-trampoline.c b/sysdeps/mips/dl-trampoline.c
index 25b1709..d5f8891 100644
--- a/sysdeps/mips/dl-trampoline.c
+++ b/sysdeps/mips/dl-trampoline.c
@@ -193,6 +193,10 @@  __dl_runtime_resolve (ElfW(Word) sym_index,
       /* Currently value contains the base load address of the object
 	 that defines sym.  Now add in the symbol offset.  */
       value = (sym ? sym_map->l_addr + sym->st_value : 0);
+      if (sym != NULL
+          && __builtin_expect (ELFW(ST_TYPE) (sym->st_info)
+              == STT_GNU_IFUNC, 0))
+        value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
     }
   else
     /* We already found the symbol.  The module (and therefore its load