/* Delayed IFUNC processing. Copyright (C) 2017 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 . */ #ifdef HAVE_IFUNC # include # include # include # include # include /* Machine-specific definitions. */ # include /* This struct covers a whole page containing individual struct dl_ifunc_relocation elements, which are allocated individually by allocate_relocation below. */ struct dl_ifunc_relocation_array { struct dl_ifunc_relocation_array *next; struct dl_ifunc_relocation data[]; }; /* Global list of allocated arrays of struct dl_ifunc_relocation elements. */ static struct dl_ifunc_relocation_array *array_list; /* Position of the next allocation in the current array used as the source of struct dl_ifunc_relocation allocations. */ static struct dl_ifunc_relocation *next_alloc; static unsigned int remaining_alloc; /* Allocate a new struct dl_ifunc_relocation_array object. Return the first data object therein. Update array_list, next_alloc, remaining_alloc. This function assumes taht remaining_alloc is zero. */ static struct dl_ifunc_relocation * allocate_array (void) { size_t page_size = GLRO(dl_pagesize); void *ptr = __mmap (NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (ptr == MAP_FAILED) _dl_signal_error (ENOMEM, NULL, NULL, "cannot allocate IFUNC resolver information"); struct dl_ifunc_relocation_array *new_head = ptr; new_head->next = array_list; array_list = new_head; next_alloc = &new_head->data[1]; /* The function returns the first element of the new array. */ remaining_alloc = (page_size - offsetof (struct dl_ifunc_relocation_array, data)) / sizeof (new_head->data[0]) - 1; return &new_head->data[0]; } /* Allocate whone struct dl_ifunc_relocation element from the active array. Updated next_alloc, remaining_alloc, and potentially array_list. */ static struct dl_ifunc_relocation * allocate_ifunc (void) { if (remaining_alloc == 0) return allocate_array (); --remaining_alloc; return next_alloc++; } /* Deallocate the list of array allocations starting at array_list. */ static void free_allocations (void) { size_t page_size = GLRO(dl_pagesize); struct dl_ifunc_relocation_array *p = array_list; while (p != NULL) { struct dl_ifunc_relocation_array *next = p->next; munmap (p, page_size); p = next; } /* Restart from scratch if _dl_ifunc_record_reloc is called again. */ array_list = NULL; next_alloc = NULL; remaining_alloc = 0; } /* Process all delayed IFUNC resolutions for IFUNC_MAP alone. */ static void apply_relocations (struct link_map *ifunc_map) { if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS) && ifunc_map->l_name != NULL) _dl_debug_printf ("applying delayed IFUNC relocations against %s\n", ifunc_map->l_name); struct dl_ifunc_relocation *p = ifunc_map->l_ifunc_relocations; unsigned int count = 0; while (p != NULL) { if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS) && ifunc_map->l_name != NULL) { if (p->ifunc_sym == NULL) _dl_debug_printf ("processing relative IFUNC relocation for %s\n", ifunc_map->l_name); else { const char *strtab = (const char *) D_PTR (ifunc_map, l_info[DT_STRTAB]); _dl_debug_printf ("binding IFUNC symbol %s in %s against %s\n", strtab + p->ifunc_sym->st_name, p->reloc_map->l_name, ifunc_map->l_name); } } _dl_ifunc_process_relocation (p, ifunc_map); p = p->next; ++count; } if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS) && ifunc_map->l_name != NULL) _dl_debug_printf ("%u delayed IFUNC relocations performed against %s\n", count, ifunc_map->l_name); /* No deallocation takes place here. All allocated objects are freed in bulk by a later call to free_allocations. */ ifunc_map->l_ifunc_relocations = NULL; } void internal_function _dl_ifunc_record_reloc (struct link_map *reloc_map, const ElfW(Rela) *reloc, ElfW(Addr) *reloc_addr, struct link_map *ifunc_map, const ElfW(Sym) *ifunc_sym) { /* Add the delayed IFUNC relocation to the list for ifunc_map. */ struct dl_ifunc_relocation *ifunc = allocate_ifunc (); *ifunc = (struct dl_ifunc_relocation) { .reloc_map = reloc_map, .reloc = reloc, .reloc_addr = reloc_addr, .ifunc_sym = ifunc_sym, .next = ifunc_map->l_ifunc_relocations, }; ifunc_map->l_ifunc_relocations = ifunc; } void internal_function _dl_ifunc_apply_relocations (struct link_map *new) { /* Traverse the search list in reverse order, in case an IFUNC resolver calls into one its dependencies, and those dependency needs IFUNC resolution as well. */ struct link_map **r_list = new->l_searchlist.r_list; for (unsigned int i = new->l_searchlist.r_nlist; i-- > 0; ) { struct link_map *l = r_list[i]; if (l->l_ifunc_relocations != NULL) apply_relocations (l); } free_allocations (); } void internal_function _dl_ifunc_clear_relocations (struct link_map *map) { Lmid_t nsid = map->l_ns; struct link_namespaces *ns = &GL(dl_ns)[nsid]; for (struct link_map *l = ns->_ns_loaded; l != NULL; l = l->l_next) l->l_ifunc_relocations = NULL; free_allocations (); } #endif /* HAVE_IFUNC */