Blame elf/dl-fptr.c

Packit Service 82fcde
/* Manage function descriptors.  Generic version.
Packit Service 82fcde
   Copyright (C) 1999-2018 Free Software Foundation, Inc.
Packit Service 82fcde
   This file is part of the GNU C Library.
Packit Service 82fcde
Packit Service 82fcde
   The GNU C Library is free software; you can redistribute it and/or
Packit Service 82fcde
   modify it under the terms of the GNU Lesser General Public
Packit Service 82fcde
   License as published by the Free Software Foundation; either
Packit Service 82fcde
   version 2.1 of the License, or (at your option) any later version.
Packit Service 82fcde
Packit Service 82fcde
   The GNU C Library is distributed in the hope that it will be useful,
Packit Service 82fcde
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 82fcde
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit Service 82fcde
   Lesser General Public License for more details.
Packit Service 82fcde
Packit Service 82fcde
   You should have received a copy of the GNU Lesser General Public
Packit Service 82fcde
   License along with the GNU C Library; if not, see
Packit Service 82fcde
   <http://www.gnu.org/licenses/>.  */
Packit Service 82fcde
Packit Service 82fcde
#include <libintl.h>
Packit Service 82fcde
#include <unistd.h>
Packit Service 82fcde
#include <string.h>
Packit Service 82fcde
#include <sys/param.h>
Packit Service 82fcde
#include <sys/mman.h>
Packit Service 82fcde
#include <link.h>
Packit Service 82fcde
#include <ldsodefs.h>
Packit Service 82fcde
#include <elf/dynamic-link.h>
Packit Service 82fcde
#include <dl-fptr.h>
Packit Service 82fcde
#include <dl-unmap-segments.h>
Packit Service 82fcde
#include <atomic.h>
Packit Service 82fcde
Packit Service 82fcde
#ifndef ELF_MACHINE_BOOT_FPTR_TABLE_LEN
Packit Service 82fcde
/* ELF_MACHINE_BOOT_FPTR_TABLE_LEN should be greater than the number of
Packit Service 82fcde
   dynamic symbols in ld.so.  */
Packit Service 82fcde
# define ELF_MACHINE_BOOT_FPTR_TABLE_LEN 256
Packit Service 82fcde
#endif
Packit Service 82fcde
Packit Service 82fcde
#ifndef ELF_MACHINE_LOAD_ADDRESS
Packit Service 82fcde
# error "ELF_MACHINE_LOAD_ADDRESS is not defined."
Packit Service 82fcde
#endif
Packit Service 82fcde
Packit Service 82fcde
#ifndef COMPARE_AND_SWAP
Packit Service 82fcde
# define COMPARE_AND_SWAP(ptr, old, new) \
Packit Service 82fcde
  (catomic_compare_and_exchange_bool_acq (ptr, new, old) == 0)
Packit Service 82fcde
#endif
Packit Service 82fcde
Packit Service 82fcde
ElfW(Addr) _dl_boot_fptr_table [ELF_MACHINE_BOOT_FPTR_TABLE_LEN];
Packit Service 82fcde
Packit Service 82fcde
static struct local
Packit Service 82fcde
  {
Packit Service 82fcde
    struct fdesc_table *root;
Packit Service 82fcde
    struct fdesc *free_list;
Packit Service 82fcde
    unsigned int npages;		/* # of pages to allocate */
Packit Service 82fcde
    /* the next to members MUST be consecutive! */
Packit Service 82fcde
    struct fdesc_table boot_table;
Packit Service 82fcde
    struct fdesc boot_fdescs[1024];
Packit Service 82fcde
  }
Packit Service 82fcde
local =
Packit Service 82fcde
  {
Packit Service 82fcde
    .root = &local.boot_table,
Packit Service 82fcde
    .npages = 2,
Packit Service 82fcde
    .boot_table =
Packit Service 82fcde
      {
Packit Service 82fcde
	.len = sizeof (local.boot_fdescs) / sizeof (local.boot_fdescs[0]),
Packit Service 82fcde
	.first_unused = 0
Packit Service 82fcde
      }
Packit Service 82fcde
  };
Packit Service 82fcde
Packit Service 82fcde
/* Create a new fdesc table and return a pointer to the first fdesc
Packit Service 82fcde
   entry.  The fdesc lock must have been acquired already.  */
Packit Service 82fcde
Packit Service 82fcde
static struct fdesc_table *
Packit Service 82fcde
new_fdesc_table (struct local *l, size_t *size)
Packit Service 82fcde
{
Packit Service 82fcde
  size_t old_npages = l->npages;
Packit Service 82fcde
  size_t new_npages = old_npages + old_npages;
Packit Service 82fcde
  struct fdesc_table *new_table;
Packit Service 82fcde
Packit Service 82fcde
  /* If someone has just created a new table, we return NULL to tell
Packit Service 82fcde
     the caller to use the new table.  */
Packit Service 82fcde
  if (! COMPARE_AND_SWAP (&l->npages, old_npages, new_npages))
Packit Service 82fcde
    return (struct fdesc_table *) NULL;
Packit Service 82fcde
Packit Service 82fcde
  *size = old_npages * GLRO(dl_pagesize);
Packit Service 82fcde
  new_table = __mmap (NULL, *size,
Packit Service 82fcde
		      PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
Packit Service 82fcde
  if (new_table == MAP_FAILED)
Packit Service 82fcde
    _dl_signal_error (errno, NULL, NULL,
Packit Service 82fcde
		      N_("cannot map pages for fdesc table"));
Packit Service 82fcde
Packit Service 82fcde
  new_table->len
Packit Service 82fcde
    = (*size - sizeof (*new_table)) / sizeof (struct fdesc);
Packit Service 82fcde
  new_table->first_unused = 1;
Packit Service 82fcde
  return new_table;
Packit Service 82fcde
}
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
static ElfW(Addr)
Packit Service 82fcde
make_fdesc (ElfW(Addr) ip, ElfW(Addr) gp)
Packit Service 82fcde
{
Packit Service 82fcde
  struct fdesc *fdesc = NULL;
Packit Service 82fcde
  struct fdesc_table *root;
Packit Service 82fcde
  unsigned int old;
Packit Service 82fcde
  struct local *l;
Packit Service 82fcde
Packit Service 82fcde
  ELF_MACHINE_LOAD_ADDRESS (l, local);
Packit Service 82fcde
Packit Service 82fcde
 retry:
Packit Service 82fcde
  root = l->root;
Packit Service 82fcde
  while (1)
Packit Service 82fcde
    {
Packit Service 82fcde
      old = root->first_unused;
Packit Service 82fcde
      if (old >= root->len)
Packit Service 82fcde
	break;
Packit Service 82fcde
      else if (COMPARE_AND_SWAP (&root->first_unused, old, old + 1))
Packit Service 82fcde
	{
Packit Service 82fcde
	  fdesc = &root->fdesc[old];
Packit Service 82fcde
	  goto install;
Packit Service 82fcde
	}
Packit Service 82fcde
    }
Packit Service 82fcde
Packit Service 82fcde
  if (l->free_list)
Packit Service 82fcde
    {
Packit Service 82fcde
      /* Get it from free-list.  */
Packit Service 82fcde
      do
Packit Service 82fcde
	{
Packit Service 82fcde
	  fdesc = l->free_list;
Packit Service 82fcde
	  if (fdesc == NULL)
Packit Service 82fcde
	    goto retry;
Packit Service 82fcde
	}
Packit Service 82fcde
      while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->free_list,
Packit Service 82fcde
				 (ElfW(Addr)) fdesc, fdesc->ip));
Packit Service 82fcde
    }
Packit Service 82fcde
  else
Packit Service 82fcde
    {
Packit Service 82fcde
      /* Create a new fdesc table.  */
Packit Service 82fcde
      size_t size;
Packit Service 82fcde
      struct fdesc_table *new_table = new_fdesc_table (l, &size);
Packit Service 82fcde
Packit Service 82fcde
      if (new_table == NULL)
Packit Service 82fcde
	goto retry;
Packit Service 82fcde
Packit Service 82fcde
      new_table->next = root;
Packit Service 82fcde
      if (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->root,
Packit Service 82fcde
			      (ElfW(Addr)) root,
Packit Service 82fcde
			      (ElfW(Addr)) new_table))
Packit Service 82fcde
	{
Packit Service 82fcde
	  /* Someone has just installed a new table. Return NULL to
Packit Service 82fcde
	     tell the caller to use the new table.  */
Packit Service 82fcde
	  __munmap (new_table, size);
Packit Service 82fcde
	  goto retry;
Packit Service 82fcde
	}
Packit Service 82fcde
Packit Service 82fcde
      /* Note that the first entry was reserved while allocating the
Packit Service 82fcde
	 memory for the new page.  */
Packit Service 82fcde
      fdesc = &new_table->fdesc[0];
Packit Service 82fcde
    }
Packit Service 82fcde
Packit Service 82fcde
 install:
Packit Service 82fcde
  fdesc->ip = ip;
Packit Service 82fcde
  fdesc->gp = gp;
Packit Service 82fcde
Packit Service 82fcde
  return (ElfW(Addr)) fdesc;
Packit Service 82fcde
}
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
static inline ElfW(Addr) * __attribute__ ((always_inline))
Packit Service 82fcde
make_fptr_table (struct link_map *map)
Packit Service 82fcde
{
Packit Service 82fcde
  const ElfW(Sym) *symtab
Packit Service 82fcde
    = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
Packit Service 82fcde
  const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
Packit Service 82fcde
  ElfW(Addr) *fptr_table;
Packit Service 82fcde
  size_t size;
Packit Service 82fcde
  size_t len;
Packit Service 82fcde
Packit Service 82fcde
  /* XXX Apparently the only way to find out the size of the dynamic
Packit Service 82fcde
     symbol section is to assume that the string table follows right
Packit Service 82fcde
     afterwards...  */
Packit Service 82fcde
  len = ((strtab - (char *) symtab)
Packit Service 82fcde
	 / map->l_info[DT_SYMENT]->d_un.d_val);
Packit Service 82fcde
  size = ((len * sizeof (fptr_table[0]) + GLRO(dl_pagesize) - 1)
Packit Service 82fcde
	  & -GLRO(dl_pagesize));
Packit Service 82fcde
  /* XXX We don't support here in the moment systems without MAP_ANON.
Packit Service 82fcde
     There probably are none for IA-64.  In case this is proven wrong
Packit Service 82fcde
     we will have to open /dev/null here and use the file descriptor
Packit Service 82fcde
     instead of the hard-coded -1.  */
Packit Service 82fcde
  fptr_table = __mmap (NULL, size,
Packit Service 82fcde
		       PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE,
Packit Service 82fcde
		       -1, 0);
Packit Service 82fcde
  if (fptr_table == MAP_FAILED)
Packit Service 82fcde
    _dl_signal_error (errno, NULL, NULL,
Packit Service 82fcde
		      N_("cannot map pages for fptr table"));
Packit Service 82fcde
Packit Service 82fcde
  if (COMPARE_AND_SWAP ((ElfW(Addr) *) &map->l_mach.fptr_table,
Packit Service 82fcde
			(ElfW(Addr)) NULL, (ElfW(Addr)) fptr_table))
Packit Service 82fcde
    map->l_mach.fptr_table_len = len;
Packit Service 82fcde
  else
Packit Service 82fcde
    __munmap (fptr_table, len * sizeof (fptr_table[0]));
Packit Service 82fcde
Packit Service 82fcde
  return map->l_mach.fptr_table;
Packit Service 82fcde
}
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
ElfW(Addr)
Packit Service 82fcde
_dl_make_fptr (struct link_map *map, const ElfW(Sym) *sym,
Packit Service 82fcde
	       ElfW(Addr) ip)
Packit Service 82fcde
{
Packit Service 82fcde
  ElfW(Addr) *ftab = map->l_mach.fptr_table;
Packit Service 82fcde
  const ElfW(Sym) *symtab;
Packit Service 82fcde
  Elf_Symndx symidx;
Packit Service 82fcde
  struct local *l;
Packit Service 82fcde
Packit Service 82fcde
  if (__glibc_unlikely (ftab == NULL))
Packit Service 82fcde
    ftab = make_fptr_table (map);
Packit Service 82fcde
Packit Service 82fcde
  symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
Packit Service 82fcde
  symidx = sym - symtab;
Packit Service 82fcde
Packit Service 82fcde
  if (symidx >= map->l_mach.fptr_table_len)
Packit Service 82fcde
    _dl_signal_error (0, NULL, NULL,
Packit Service 82fcde
		      N_("internal error: symidx out of range of fptr table"));
Packit Service 82fcde
Packit Service 82fcde
  while (ftab[symidx] == 0)
Packit Service 82fcde
    {
Packit Service 82fcde
      /* GOT has already been relocated in elf_get_dynamic_info -
Packit Service 82fcde
	 don't try to relocate it again.  */
Packit Service 82fcde
      ElfW(Addr) fdesc
Packit Service 82fcde
	= make_fdesc (ip, map->l_info[DT_PLTGOT]->d_un.d_ptr);
Packit Service 82fcde
Packit Service 82fcde
      if (__builtin_expect (COMPARE_AND_SWAP (&ftab[symidx], (ElfW(Addr)) NULL,
Packit Service 82fcde
					      fdesc), 1))
Packit Service 82fcde
	{
Packit Service 82fcde
	  /* Noone has updated the entry and the new function
Packit Service 82fcde
	     descriptor has been installed.  */
Packit Service 82fcde
#if 0
Packit Service 82fcde
	  const char *strtab
Packit Service 82fcde
	    = (const void *) D_PTR (map, l_info[DT_STRTAB]);
Packit Service 82fcde
Packit Service 82fcde
	  ELF_MACHINE_LOAD_ADDRESS (l, local);
Packit Service 82fcde
	  if (l->root != &l->boot_table
Packit Service 82fcde
	      || l->boot_table.first_unused > 20)
Packit Service 82fcde
	    _dl_debug_printf ("created fdesc symbol `%s' at %lx\n",
Packit Service 82fcde
			      strtab + sym->st_name, ftab[symidx]);
Packit Service 82fcde
#endif
Packit Service 82fcde
	  break;
Packit Service 82fcde
	}
Packit Service 82fcde
      else
Packit Service 82fcde
	{
Packit Service 82fcde
	  /* We created a duplicated function descriptor. We put it on
Packit Service 82fcde
	     free-list.  */
Packit Service 82fcde
	  struct fdesc *f = (struct fdesc *) fdesc;
Packit Service 82fcde
Packit Service 82fcde
	  ELF_MACHINE_LOAD_ADDRESS (l, local);
Packit Service 82fcde
Packit Service 82fcde
	  do
Packit Service 82fcde
	    f->ip = (ElfW(Addr)) l->free_list;
Packit Service 82fcde
	  while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->free_list,
Packit Service 82fcde
				     f->ip, fdesc));
Packit Service 82fcde
	}
Packit Service 82fcde
    }
Packit Service 82fcde
Packit Service 82fcde
  return ftab[symidx];
Packit Service 82fcde
}
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
void
Packit Service 82fcde
_dl_unmap (struct link_map *map)
Packit Service 82fcde
{
Packit Service 82fcde
  ElfW(Addr) *ftab = map->l_mach.fptr_table;
Packit Service 82fcde
  struct fdesc *head = NULL, *tail = NULL;
Packit Service 82fcde
  size_t i;
Packit Service 82fcde
Packit Service 82fcde
  _dl_unmap_segments (map);
Packit Service 82fcde
Packit Service 82fcde
  if (ftab == NULL)
Packit Service 82fcde
    return;
Packit Service 82fcde
Packit Service 82fcde
  /* String together the fdesc structures that are being freed.  */
Packit Service 82fcde
  for (i = 0; i < map->l_mach.fptr_table_len; ++i)
Packit Service 82fcde
    {
Packit Service 82fcde
      if (ftab[i])
Packit Service 82fcde
	{
Packit Service 82fcde
	  *(struct fdesc **) ftab[i] = head;
Packit Service 82fcde
	  head = (struct fdesc *) ftab[i];
Packit Service 82fcde
	  if (tail == NULL)
Packit Service 82fcde
	    tail = head;
Packit Service 82fcde
	}
Packit Service 82fcde
    }
Packit Service 82fcde
Packit Service 82fcde
  /* Prepend the new list to the free_list: */
Packit Service 82fcde
  if (tail)
Packit Service 82fcde
    do
Packit Service 82fcde
      tail->ip = (ElfW(Addr)) local.free_list;
Packit Service 82fcde
    while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &local.free_list,
Packit Service 82fcde
			       tail->ip, (ElfW(Addr)) head));
Packit Service 82fcde
Packit Service 82fcde
  __munmap (ftab, (map->l_mach.fptr_table_len
Packit Service 82fcde
		   * sizeof (map->l_mach.fptr_table[0])));
Packit Service 82fcde
Packit Service 82fcde
  map->l_mach.fptr_table = NULL;
Packit Service 82fcde
}
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
ElfW(Addr)
Packit Service 82fcde
_dl_lookup_address (const void *address)
Packit Service 82fcde
{
Packit Service 82fcde
  ElfW(Addr) addr = (ElfW(Addr)) address;
Packit Service 82fcde
  struct fdesc_table *t;
Packit Service 82fcde
  unsigned long int i;
Packit Service 82fcde
Packit Service 82fcde
  for (t = local.root; t != NULL; t = t->next)
Packit Service 82fcde
    {
Packit Service 82fcde
      i = (struct fdesc *) addr - &t->fdesc[0];
Packit Service 82fcde
      if (i < t->first_unused && addr == (ElfW(Addr)) &t->fdesc[i])
Packit Service 82fcde
	{
Packit Service 82fcde
	  addr = t->fdesc[i].ip;
Packit Service 82fcde
	  break;
Packit Service 82fcde
	}
Packit Service 82fcde
    }
Packit Service 82fcde
Packit Service 82fcde
  return addr;
Packit Service 82fcde
}