Blame sysdeps/hppa/dl-fptr.c

Packit 6c4009
/* Manage function descriptors.  Generic version.
Packit 6c4009
   Copyright (C) 1999-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, write to the Free
Packit 6c4009
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
Packit 6c4009
   02111-1307 USA.  */
Packit 6c4009
Packit 6c4009
#include <libintl.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <sys/param.h>
Packit 6c4009
#include <sys/mman.h>
Packit 6c4009
#include <link.h>
Packit 6c4009
#include <ldsodefs.h>
Packit 6c4009
#include <elf/dynamic-link.h>
Packit 6c4009
#include <dl-fptr.h>
Packit 6c4009
#include <dl-unmap-segments.h>
Packit 6c4009
#include <atomic.h>
Packit 6c4009
#include <libc-pointer-arith.h>
Packit 6c4009
Packit 6c4009
#ifndef ELF_MACHINE_BOOT_FPTR_TABLE_LEN
Packit 6c4009
/* ELF_MACHINE_BOOT_FPTR_TABLE_LEN should be greater than the number of
Packit 6c4009
   dynamic symbols in ld.so.  */
Packit 6c4009
# define ELF_MACHINE_BOOT_FPTR_TABLE_LEN 256
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
#ifndef ELF_MACHINE_LOAD_ADDRESS
Packit 6c4009
# error "ELF_MACHINE_LOAD_ADDRESS is not defined."
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
#ifndef COMPARE_AND_SWAP
Packit 6c4009
# define COMPARE_AND_SWAP(ptr, old, new) \
Packit 6c4009
  (catomic_compare_and_exchange_bool_acq (ptr, new, old) == 0)
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
ElfW(Addr) _dl_boot_fptr_table [ELF_MACHINE_BOOT_FPTR_TABLE_LEN];
Packit 6c4009
Packit 6c4009
static struct local
Packit 6c4009
  {
Packit 6c4009
    struct fdesc_table *root;
Packit 6c4009
    struct fdesc *free_list;
Packit 6c4009
    unsigned int npages;		/* # of pages to allocate */
Packit 6c4009
    /* the next to members MUST be consecutive! */
Packit 6c4009
    struct fdesc_table boot_table;
Packit 6c4009
    struct fdesc boot_fdescs[1024];
Packit 6c4009
  }
Packit 6c4009
local =
Packit 6c4009
  {
Packit 6c4009
#ifdef SHARED
Packit 6c4009
    /* Address of .boot_table is not known until runtime.  */
Packit 6c4009
    .root = 0,
Packit 6c4009
#else
Packit 6c4009
    .root = &local.boot_table,
Packit 6c4009
#endif
Packit 6c4009
    .npages = 2,
Packit 6c4009
    .boot_table =
Packit 6c4009
      {
Packit 6c4009
	.len = sizeof (local.boot_fdescs) / sizeof (local.boot_fdescs[0]),
Packit 6c4009
	.first_unused = 0
Packit 6c4009
      }
Packit 6c4009
  };
Packit 6c4009
Packit 6c4009
/* Create a new fdesc table and return a pointer to the first fdesc
Packit 6c4009
   entry.  The fdesc lock must have been acquired already.  */
Packit 6c4009
Packit 6c4009
static struct fdesc_table *
Packit 6c4009
new_fdesc_table (struct local *l, size_t *size)
Packit 6c4009
{
Packit 6c4009
  size_t old_npages = l->npages;
Packit 6c4009
  size_t new_npages = old_npages + old_npages;
Packit 6c4009
  struct fdesc_table *new_table;
Packit 6c4009
Packit 6c4009
  /* If someone has just created a new table, we return NULL to tell
Packit 6c4009
     the caller to use the new table.  */
Packit 6c4009
  if (! COMPARE_AND_SWAP (&l->npages, old_npages, new_npages))
Packit 6c4009
    return (struct fdesc_table *) NULL;
Packit 6c4009
Packit 6c4009
  *size = old_npages * GLRO(dl_pagesize);
Packit 6c4009
  new_table = __mmap (NULL, *size,
Packit 6c4009
		      PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
Packit 6c4009
  if (new_table == MAP_FAILED)
Packit 6c4009
    _dl_signal_error (errno, NULL, NULL,
Packit 6c4009
		      N_("cannot map pages for fdesc table"));
Packit 6c4009
Packit 6c4009
  new_table->len
Packit 6c4009
    = (*size - sizeof (*new_table)) / sizeof (struct fdesc);
Packit 6c4009
  new_table->first_unused = 1;
Packit 6c4009
  return new_table;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Must call _dl_fptr_init before using any other function.  */
Packit 6c4009
void
Packit 6c4009
_dl_fptr_init (void)
Packit 6c4009
{
Packit 6c4009
  struct local *l;
Packit 6c4009
Packit 6c4009
  ELF_MACHINE_LOAD_ADDRESS (l, local);
Packit 6c4009
  l->root = &l->boot_table;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static ElfW(Addr)
Packit 6c4009
make_fdesc (ElfW(Addr) ip, ElfW(Addr) gp)
Packit 6c4009
{
Packit 6c4009
  struct fdesc *fdesc = NULL;
Packit 6c4009
  struct fdesc_table *root;
Packit 6c4009
  unsigned int old;
Packit 6c4009
  struct local *l;
Packit 6c4009
Packit 6c4009
  ELF_MACHINE_LOAD_ADDRESS (l, local);
Packit 6c4009
Packit 6c4009
 retry:
Packit 6c4009
  root = l->root;
Packit 6c4009
  while (1)
Packit 6c4009
    {
Packit 6c4009
      old = root->first_unused;
Packit 6c4009
      if (old >= root->len)
Packit 6c4009
	break;
Packit 6c4009
      else if (COMPARE_AND_SWAP (&root->first_unused, old, old + 1))
Packit 6c4009
	{
Packit 6c4009
	  fdesc = &root->fdesc[old];
Packit 6c4009
	  goto install;
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (l->free_list)
Packit 6c4009
    {
Packit 6c4009
      /* Get it from free-list.  */
Packit 6c4009
      do
Packit 6c4009
	{
Packit 6c4009
	  fdesc = l->free_list;
Packit 6c4009
	  if (fdesc == NULL)
Packit 6c4009
	    goto retry;
Packit 6c4009
	}
Packit 6c4009
      while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->free_list,
Packit 6c4009
				 (ElfW(Addr)) fdesc, fdesc->ip));
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      /* Create a new fdesc table.  */
Packit 6c4009
      size_t size;
Packit 6c4009
      struct fdesc_table *new_table = new_fdesc_table (l, &size);
Packit 6c4009
Packit 6c4009
      if (new_table == NULL)
Packit 6c4009
	goto retry;
Packit 6c4009
Packit 6c4009
      new_table->next = root;
Packit 6c4009
      if (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->root,
Packit 6c4009
			      (ElfW(Addr)) root,
Packit 6c4009
			      (ElfW(Addr)) new_table))
Packit 6c4009
	{
Packit 6c4009
	  /* Someone has just installed a new table. Return NULL to
Packit 6c4009
	     tell the caller to use the new table.  */
Packit 6c4009
	  __munmap (new_table, size);
Packit 6c4009
	  goto retry;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      /* Note that the first entry was reserved while allocating the
Packit 6c4009
	 memory for the new page.  */
Packit 6c4009
      fdesc = &new_table->fdesc[0];
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
 install:
Packit 6c4009
  fdesc->ip = ip;
Packit 6c4009
  fdesc->gp = gp;
Packit 6c4009
Packit 6c4009
  return (ElfW(Addr)) fdesc;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
static inline ElfW(Addr) * __attribute__ ((always_inline))
Packit 6c4009
make_fptr_table (struct link_map *map)
Packit 6c4009
{
Packit 6c4009
  const ElfW(Sym) *symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
Packit 6c4009
  const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
Packit 6c4009
  ElfW(Addr) *fptr_table;
Packit 6c4009
  size_t size;
Packit 6c4009
  size_t len;
Packit 6c4009
  const ElfW(Sym) *symtabend;
Packit 6c4009
Packit 6c4009
  /* Determine the end of the dynamic symbol table using the hash.  */
Packit 6c4009
  if (map->l_info[DT_HASH] != NULL)
Packit 6c4009
    symtabend = (symtab + ((Elf_Symndx *) D_PTR (map, l_info[DT_HASH]))[1]);
Packit 6c4009
  else
Packit 6c4009
  /* There is no direct way to determine the number of symbols in the
Packit 6c4009
     dynamic symbol table and no hash table is present.  The ELF
Packit 6c4009
     binary is ill-formed but what shall we do?  Use the beginning of
Packit 6c4009
     the string table which generally follows the symbol table.  */
Packit 6c4009
    symtabend = (const ElfW(Sym) *) strtab;
Packit 6c4009
Packit 6c4009
  len = (((char *) symtabend - (char *) symtab)
Packit 6c4009
	 / map->l_info[DT_SYMENT]->d_un.d_val);
Packit 6c4009
  size = ALIGN_UP (len * sizeof (fptr_table[0]), GLRO(dl_pagesize));
Packit 6c4009
Packit 6c4009
  /* We don't support systems without MAP_ANON.  We avoid using malloc
Packit 6c4009
     because this might get called before malloc is setup.  */
Packit 6c4009
  fptr_table = __mmap (NULL, size,
Packit 6c4009
		       PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE,
Packit 6c4009
		       -1, 0);
Packit 6c4009
  if (fptr_table == MAP_FAILED)
Packit 6c4009
    _dl_signal_error (errno, NULL, NULL,
Packit 6c4009
		      N_("cannot map pages for fptr table"));
Packit 6c4009
Packit 6c4009
  if (COMPARE_AND_SWAP ((ElfW(Addr) *) &map->l_mach.fptr_table,
Packit 6c4009
			(ElfW(Addr)) NULL, (ElfW(Addr)) fptr_table))
Packit 6c4009
    map->l_mach.fptr_table_len = len;
Packit 6c4009
  else
Packit 6c4009
    __munmap (fptr_table, len * sizeof (fptr_table[0]));
Packit 6c4009
Packit 6c4009
  return map->l_mach.fptr_table;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
ElfW(Addr)
Packit 6c4009
_dl_make_fptr (struct link_map *map, const ElfW(Sym) *sym,
Packit 6c4009
	       ElfW(Addr) ip)
Packit 6c4009
{
Packit 6c4009
  ElfW(Addr) *ftab = map->l_mach.fptr_table;
Packit 6c4009
  const ElfW(Sym) *symtab;
Packit 6c4009
  Elf_Symndx symidx;
Packit 6c4009
  struct local *l;
Packit 6c4009
Packit 6c4009
  if (__builtin_expect (ftab == NULL, 0))
Packit 6c4009
    ftab = make_fptr_table (map);
Packit 6c4009
Packit 6c4009
  symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
Packit 6c4009
  symidx = sym - symtab;
Packit 6c4009
Packit 6c4009
  if (symidx >= map->l_mach.fptr_table_len)
Packit 6c4009
    _dl_signal_error (0, NULL, NULL,
Packit 6c4009
		      N_("internal error: symidx out of range of fptr table"));
Packit 6c4009
Packit 6c4009
  while (ftab[symidx] == 0)
Packit 6c4009
    {
Packit 6c4009
      /* GOT has already been relocated in elf_get_dynamic_info -
Packit 6c4009
	 don't try to relocate it again.  */
Packit 6c4009
      ElfW(Addr) fdesc
Packit 6c4009
	= make_fdesc (ip, map->l_info[DT_PLTGOT]->d_un.d_ptr);
Packit 6c4009
Packit 6c4009
      if (__builtin_expect (COMPARE_AND_SWAP (&ftab[symidx], (ElfW(Addr)) NULL,
Packit 6c4009
					      fdesc), 1))
Packit 6c4009
	{
Packit 6c4009
	  /* Noone has updated the entry and the new function
Packit 6c4009
	     descriptor has been installed.  */
Packit 6c4009
#if 0
Packit 6c4009
	  const char *strtab
Packit 6c4009
	    = (const void *) D_PTR (map, l_info[DT_STRTAB]);
Packit 6c4009
Packit 6c4009
	  ELF_MACHINE_LOAD_ADDRESS (l, local);
Packit 6c4009
	  if (l->root != &l->boot_table
Packit 6c4009
	      || l->boot_table.first_unused > 20)
Packit 6c4009
	    _dl_debug_printf ("created fdesc symbol `%s' at %lx\n",
Packit 6c4009
			      strtab + sym->st_name, ftab[symidx]);
Packit 6c4009
#endif
Packit 6c4009
	  break;
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  /* We created a duplicated function descriptor. We put it on
Packit 6c4009
	     free-list.  */
Packit 6c4009
	  struct fdesc *f = (struct fdesc *) fdesc;
Packit 6c4009
Packit 6c4009
	  ELF_MACHINE_LOAD_ADDRESS (l, local);
Packit 6c4009
Packit 6c4009
	  do
Packit 6c4009
	    f->ip = (ElfW(Addr)) l->free_list;
Packit 6c4009
	  while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->free_list,
Packit 6c4009
				     f->ip, fdesc));
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  return ftab[symidx];
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
_dl_unmap (struct link_map *map)
Packit 6c4009
{
Packit 6c4009
  ElfW(Addr) *ftab = map->l_mach.fptr_table;
Packit 6c4009
  struct fdesc *head = NULL, *tail = NULL;
Packit 6c4009
  size_t i;
Packit 6c4009
Packit 6c4009
  _dl_unmap_segments (map);
Packit 6c4009
Packit 6c4009
  if (ftab == NULL)
Packit 6c4009
    return;
Packit 6c4009
Packit 6c4009
  /* String together the fdesc structures that are being freed.  */
Packit 6c4009
  for (i = 0; i < map->l_mach.fptr_table_len; ++i)
Packit 6c4009
    {
Packit 6c4009
      if (ftab[i])
Packit 6c4009
	{
Packit 6c4009
	  *(struct fdesc **) ftab[i] = head;
Packit 6c4009
	  head = (struct fdesc *) ftab[i];
Packit 6c4009
	  if (tail == NULL)
Packit 6c4009
	    tail = head;
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Prepend the new list to the free_list: */
Packit 6c4009
  if (tail)
Packit 6c4009
    do
Packit 6c4009
      tail->ip = (ElfW(Addr)) local.free_list;
Packit 6c4009
    while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &local.free_list,
Packit 6c4009
			       tail->ip, (ElfW(Addr)) head));
Packit 6c4009
Packit 6c4009
  __munmap (ftab, (map->l_mach.fptr_table_len
Packit 6c4009
		   * sizeof (map->l_mach.fptr_table[0])));
Packit 6c4009
Packit 6c4009
  map->l_mach.fptr_table = NULL;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
extern ElfW(Addr) _dl_fixup (struct link_map *, ElfW(Word)) attribute_hidden;
Packit 6c4009
Packit 6c4009
static inline Elf32_Addr
Packit 6c4009
elf_machine_resolve (void)
Packit 6c4009
{
Packit 6c4009
  Elf32_Addr addr;
Packit 6c4009
Packit 6c4009
  asm ("b,l     1f,%0\n"
Packit 6c4009
"	addil	L'_dl_runtime_resolve - ($PIC_pcrel$0 - 1),%0\n"
Packit 6c4009
"1:	ldo	R'_dl_runtime_resolve - ($PIC_pcrel$0 - 5)(%%r1),%0\n"
Packit 6c4009
       : "=r" (addr) : : "r1");
Packit 6c4009
Packit 6c4009
  return addr;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static inline int
Packit 6c4009
_dl_read_access_allowed (unsigned int *addr)
Packit 6c4009
{
Packit 6c4009
  int result;
Packit 6c4009
Packit 6c4009
  asm ("proberi	(%1),3,%0" : "=r" (result) : "r" (addr) : );
Packit 6c4009
Packit 6c4009
  return result;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
ElfW(Addr)
Packit 6c4009
_dl_lookup_address (const void *address)
Packit 6c4009
{
Packit 6c4009
  ElfW(Addr) addr = (ElfW(Addr)) address;
Packit 6c4009
  unsigned int *desc, *gptr;
Packit 6c4009
Packit 6c4009
  /* Return ADDR if the least-significant two bits of ADDR are not consistent
Packit 6c4009
     with ADDR being a linker defined function pointer.  The normal value for
Packit 6c4009
     a code address in a backtrace is 3.  */
Packit 6c4009
  if (((unsigned int) addr & 3) != 2)
Packit 6c4009
    return addr;
Packit 6c4009
Packit 6c4009
  /* Handle special case where ADDR points to page 0.  */
Packit 6c4009
  if ((unsigned int) addr < 4096)
Packit 6c4009
    return addr;
Packit 6c4009
Packit 6c4009
  /* Clear least-significant two bits from descriptor address.  */
Packit 6c4009
  desc = (unsigned int *) ((unsigned int) addr & ~3);
Packit 6c4009
  if (!_dl_read_access_allowed (desc))
Packit 6c4009
    return addr;
Packit 6c4009
Packit 6c4009
  /* Load first word of candidate descriptor.  It should be a pointer
Packit 6c4009
     with word alignment and point to memory that can be read.  */
Packit 6c4009
  gptr = (unsigned int *) desc[0];
Packit 6c4009
  if (((unsigned int) gptr & 3) != 0
Packit 6c4009
      || !_dl_read_access_allowed (gptr))
Packit 6c4009
    return addr;
Packit 6c4009
Packit 6c4009
  /* See if descriptor requires resolution.  The following trampoline is
Packit 6c4009
     used in each global offset table for function resolution:
Packit 6c4009
Packit 6c4009
		ldw 0(r20),r22
Packit 6c4009
		bv r0(r22)
Packit 6c4009
		ldw 4(r20),r21
Packit 6c4009
     tramp:	b,l .-12,r20
Packit 6c4009
		depwi 0,31,2,r20
Packit 6c4009
		.word _dl_runtime_resolve
Packit 6c4009
		.word "_dl_runtime_resolve ltp"
Packit 6c4009
     got:	.word _DYNAMIC
Packit 6c4009
		.word "struct link map address" */
Packit 6c4009
  if (gptr[0] == 0xea9f1fdd			/* b,l .-12,r20     */
Packit 6c4009
      && gptr[1] == 0xd6801c1e			/* depwi 0,31,2,r20 */
Packit 6c4009
      && (ElfW(Addr)) gptr[2] == elf_machine_resolve ())
Packit 6c4009
    _dl_fixup ((struct link_map *) gptr[5], (ElfW(Word)) desc[1]);
Packit 6c4009
Packit 6c4009
  return (ElfW(Addr)) desc[0];
Packit 6c4009
}