| /* ELF strtab with GC and suffix merging support. | |
| Copyright (C) 2001-2024 Free Software Foundation, Inc. | |
| Written by Jakub Jelinek <jakub@redhat.com>. | |
| This file is part of BFD, the Binary File Descriptor library. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3 of the License, or | |
| (at your option) any later version. | |
| This program 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 General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program; if not, write to the Free Software | |
| Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, | |
| MA 02110-1301, USA. */ | |
| #include "sysdep.h" | |
| #include "bfd.h" | |
| #include "libbfd.h" | |
| #include "elf-bfd.h" | |
| #include "hashtab.h" | |
| #include "libiberty.h" | |
| /* An entry in the strtab hash table. */ | |
| struct elf_strtab_hash_entry | |
| { | |
| struct bfd_hash_entry root; | |
| /* Length of this entry. This includes the zero terminator. */ | |
| int len; | |
| unsigned int refcount; | |
| union { | |
| /* Index within the merged section. */ | |
| bfd_size_type index; | |
| /* Entry this is a suffix of (if len < 0). */ | |
| struct elf_strtab_hash_entry *suffix; | |
| } u; | |
| }; | |
| /* The strtab hash table. */ | |
| struct elf_strtab_hash | |
| { | |
| struct bfd_hash_table table; | |
| /* Next available index. */ | |
| size_t size; | |
| /* Number of array entries alloced. */ | |
| size_t alloced; | |
| /* Final strtab size. */ | |
| bfd_size_type sec_size; | |
| /* Array of pointers to strtab entries. */ | |
| struct elf_strtab_hash_entry **array; | |
| }; | |
| /* Routine to create an entry in a section merge hashtab. */ | |
| static struct bfd_hash_entry * | |
| elf_strtab_hash_newfunc (struct bfd_hash_entry *entry, | |
| struct bfd_hash_table *table, | |
| const char *string) | |
| { | |
| /* Allocate the structure if it has not already been allocated by a | |
| subclass. */ | |
| if (entry == NULL) | |
| entry = (struct bfd_hash_entry *) | |
| bfd_hash_allocate (table, sizeof (struct elf_strtab_hash_entry)); | |
| if (entry == NULL) | |
| return NULL; | |
| /* Call the allocation method of the superclass. */ | |
| entry = bfd_hash_newfunc (entry, table, string); | |
| if (entry) | |
| { | |
| /* Initialize the local fields. */ | |
| struct elf_strtab_hash_entry *ret; | |
| ret = (struct elf_strtab_hash_entry *) entry; | |
| ret->u.index = -1; | |
| ret->refcount = 0; | |
| ret->len = 0; | |
| } | |
| return entry; | |
| } | |
| /* Create a new hash table. */ | |
| struct elf_strtab_hash * | |
| _bfd_elf_strtab_init (void) | |
| { | |
| struct elf_strtab_hash *table; | |
| size_t amt = sizeof (struct elf_strtab_hash); | |
| table = (struct elf_strtab_hash *) bfd_malloc (amt); | |
| if (table == NULL) | |
| return NULL; | |
| if (!bfd_hash_table_init (&table->table, elf_strtab_hash_newfunc, | |
| sizeof (struct elf_strtab_hash_entry))) | |
| { | |
| free (table); | |
| return NULL; | |
| } | |
| table->sec_size = 0; | |
| table->size = 1; | |
| table->alloced = 64; | |
| amt = sizeof (struct elf_strtab_hasn_entry *); | |
| table->array = ((struct elf_strtab_hash_entry **) | |
| bfd_malloc (table->alloced * amt)); | |
| if (table->array == NULL) | |
| { | |
| bfd_hash_table_free (&table->table); | |
| free (table); | |
| return NULL; | |
| } | |
| table->array[0] = NULL; | |
| return table; | |
| } | |
| /* Free a strtab. */ | |
| void | |
| _bfd_elf_strtab_free (struct elf_strtab_hash *tab) | |
| { | |
| bfd_hash_table_free (&tab->table); | |
| free (tab->array); | |
| free (tab); | |
| } | |
| /* Get the index of an entity in a hash table, adding it if it is not | |
| already present. */ | |
| size_t | |
| _bfd_elf_strtab_add (struct elf_strtab_hash *tab, | |
| const char *str, | |
| bool copy) | |
| { | |
| register struct elf_strtab_hash_entry *entry; | |
| /* We handle this specially, since we don't want to do refcounting | |
| on it. */ | |
| if (*str == '\0') | |
| return 0; | |
| BFD_ASSERT (tab->sec_size == 0); | |
| entry = (struct elf_strtab_hash_entry *) | |
| bfd_hash_lookup (&tab->table, str, true, copy); | |
| if (entry == NULL) | |
| return (size_t) -1; | |
| entry->refcount++; | |
| if (entry->len == 0) | |
| { | |
| entry->len = strlen (str) + 1; | |
| /* 2G strings lose. */ | |
| BFD_ASSERT (entry->len > 0); | |
| if (tab->size == tab->alloced) | |
| { | |
| bfd_size_type amt = sizeof (struct elf_strtab_hash_entry *); | |
| tab->alloced *= 2; | |
| tab->array = (struct elf_strtab_hash_entry **) | |
| bfd_realloc_or_free (tab->array, tab->alloced * amt); | |
| if (tab->array == NULL) | |
| return (size_t) -1; | |
| } | |
| entry->u.index = tab->size++; | |
| tab->array[entry->u.index] = entry; | |
| } | |
| return entry->u.index; | |
| } | |
| void | |
| _bfd_elf_strtab_addref (struct elf_strtab_hash *tab, size_t idx) | |
| { | |
| if (idx == 0 || idx == (size_t) -1) | |
| return; | |
| BFD_ASSERT (tab->sec_size == 0); | |
| BFD_ASSERT (idx < tab->size); | |
| ++tab->array[idx]->refcount; | |
| } | |
| void | |
| _bfd_elf_strtab_delref (struct elf_strtab_hash *tab, size_t idx) | |
| { | |
| if (idx == 0 || idx == (size_t) -1) | |
| return; | |
| BFD_ASSERT (tab->sec_size == 0); | |
| BFD_ASSERT (idx < tab->size); | |
| BFD_ASSERT (tab->array[idx]->refcount > 0); | |
| --tab->array[idx]->refcount; | |
| } | |
| unsigned int | |
| _bfd_elf_strtab_refcount (struct elf_strtab_hash *tab, size_t idx) | |
| { | |
| return tab->array[idx]->refcount; | |
| } | |
| void | |
| _bfd_elf_strtab_clear_all_refs (struct elf_strtab_hash *tab) | |
| { | |
| size_t idx; | |
| for (idx = 1; idx < tab->size; idx++) | |
| tab->array[idx]->refcount = 0; | |
| } | |
| /* Save strtab refcounts prior to adding --as-needed library. */ | |
| struct strtab_save | |
| { | |
| size_t size; | |
| unsigned int refcount[1]; | |
| }; | |
| void * | |
| _bfd_elf_strtab_save (struct elf_strtab_hash *tab) | |
| { | |
| struct strtab_save *save; | |
| size_t idx, size; | |
| size = sizeof (*save) + (tab->size - 1) * sizeof (save->refcount[0]); | |
| save = bfd_malloc (size); | |
| if (save == NULL) | |
| return save; | |
| save->size = tab->size; | |
| for (idx = 1; idx < tab->size; idx++) | |
| save->refcount[idx] = tab->array[idx]->refcount; | |
| return save; | |
| } | |
| /* Restore strtab refcounts on finding --as-needed library not needed. */ | |
| void | |
| _bfd_elf_strtab_restore (struct elf_strtab_hash *tab, void *buf) | |
| { | |
| size_t idx, curr_size = tab->size, save_size; | |
| struct strtab_save *save = (struct strtab_save *) buf; | |
| BFD_ASSERT (tab->sec_size == 0); | |
| save_size = 1; | |
| if (save != NULL) | |
| save_size = save->size; | |
| BFD_ASSERT (save_size <= curr_size); | |
| tab->size = save_size; | |
| for (idx = 1; idx < save_size; ++idx) | |
| tab->array[idx]->refcount = save->refcount[idx]; | |
| for (; idx < curr_size; ++idx) | |
| { | |
| /* We don't remove entries from the hash table, just set their | |
| REFCOUNT to zero. Setting LEN zero will result in the size | |
| growing if the entry is added again. See _bfd_elf_strtab_add. */ | |
| tab->array[idx]->refcount = 0; | |
| tab->array[idx]->len = 0; | |
| } | |
| } | |
| bfd_size_type | |
| _bfd_elf_strtab_size (struct elf_strtab_hash *tab) | |
| { | |
| return tab->sec_size ? tab->sec_size : tab->size; | |
| } | |
| bfd_size_type | |
| _bfd_elf_strtab_len (struct elf_strtab_hash *tab) | |
| { | |
| return tab->size; | |
| } | |
| bfd_size_type | |
| _bfd_elf_strtab_offset (struct elf_strtab_hash *tab, size_t idx) | |
| { | |
| struct elf_strtab_hash_entry *entry; | |
| if (idx == 0) | |
| return 0; | |
| BFD_ASSERT (idx < tab->size); | |
| BFD_ASSERT (tab->sec_size); | |
| entry = tab->array[idx]; | |
| BFD_ASSERT (entry->refcount > 0); | |
| entry->refcount--; | |
| return tab->array[idx]->u.index; | |
| } | |
| const char * | |
| _bfd_elf_strtab_str (struct elf_strtab_hash *tab, size_t idx, | |
| bfd_size_type *offset) | |
| { | |
| if (idx == 0) | |
| return NULL; | |
| BFD_ASSERT (idx < tab->size); | |
| BFD_ASSERT (tab->sec_size); | |
| if (tab->array[idx]->refcount == 0) | |
| return NULL; | |
| if (offset) | |
| *offset = tab->array[idx]->u.index; | |
| return tab->array[idx]->root.string; | |
| } | |
| bool | |
| _bfd_elf_strtab_emit (register bfd *abfd, struct elf_strtab_hash *tab) | |
| { | |
| bfd_size_type off = 1; | |
| size_t i; | |
| if (bfd_write ("", 1, abfd) != 1) | |
| return false; | |
| for (i = 1; i < tab->size; ++i) | |
| { | |
| register const char *str; | |
| register unsigned int len; | |
| BFD_ASSERT (tab->array[i]->refcount == 0); | |
| len = tab->array[i]->len; | |
| if ((int) len <= 0) | |
| continue; | |
| str = tab->array[i]->root.string; | |
| if (bfd_write (str, len, abfd) != len) | |
| return false; | |
| off += len; | |
| } | |
| BFD_ASSERT (off == tab->sec_size); | |
| return true; | |
| } | |
| /* Compare two elf_strtab_hash_entry structures. Called via qsort. | |
| Won't ever return zero as all entries differ, so there is no issue | |
| with qsort stability here. */ | |
| static int | |
| strrevcmp (const void *a, const void *b) | |
| { | |
| struct elf_strtab_hash_entry *A = *(struct elf_strtab_hash_entry **) a; | |
| struct elf_strtab_hash_entry *B = *(struct elf_strtab_hash_entry **) b; | |
| unsigned int lenA = A->len; | |
| unsigned int lenB = B->len; | |
| const unsigned char *s = (const unsigned char *) A->root.string + lenA - 1; | |
| const unsigned char *t = (const unsigned char *) B->root.string + lenB - 1; | |
| int l = lenA < lenB ? lenA : lenB; | |
| while (l) | |
| { | |
| if (*s != *t) | |
| return (int) *s - (int) *t; | |
| s--; | |
| t--; | |
| l--; | |
| } | |
| return lenA - lenB; | |
| } | |
| static inline int | |
| is_suffix (const struct elf_strtab_hash_entry *A, | |
| const struct elf_strtab_hash_entry *B) | |
| { | |
| if (A->len <= B->len) | |
| /* B cannot be a suffix of A unless A is equal to B, which is guaranteed | |
| not to be equal by the hash table. */ | |
| return 0; | |
| return memcmp (A->root.string + (A->len - B->len), | |
| B->root.string, B->len - 1) == 0; | |
| } | |
| /* This function assigns final string table offsets for used strings, | |
| merging strings matching suffixes of longer strings if possible. */ | |
| void | |
| _bfd_elf_strtab_finalize (struct elf_strtab_hash *tab) | |
| { | |
| struct elf_strtab_hash_entry **array, **a, *e; | |
| bfd_size_type amt, sec_size; | |
| size_t size, i; | |
| /* Sort the strings by suffix and length. */ | |
| amt = tab->size; | |
| amt *= sizeof (struct elf_strtab_hash_entry *); | |
| array = (struct elf_strtab_hash_entry **) bfd_malloc (amt); | |
| if (array == NULL) | |
| goto alloc_failure; | |
| for (i = 1, a = array; i < tab->size; ++i) | |
| { | |
| e = tab->array[i]; | |
| if (e->refcount) | |
| { | |
| *a++ = e; | |
| /* Adjust the length to not include the zero terminator. */ | |
| e->len -= 1; | |
| } | |
| else | |
| e->len = 0; | |
| } | |
| size = a - array; | |
| if (size != 0) | |
| { | |
| qsort (array, size, sizeof (struct elf_strtab_hash_entry *), strrevcmp); | |
| /* Loop over the sorted array and merge suffixes. Start from the | |
| end because we want eg. | |
| s1 -> "d" | |
| s2 -> "bcd" | |
| s3 -> "abcd" | |
| to end up as | |
| s3 -> "abcd" | |
| s2 _____^ | |
| s1 _______^ | |
| ie. we don't want s1 pointing into the old s2. */ | |
| e = *--a; | |
| e->len += 1; | |
| while (--a >= array) | |
| { | |
| struct elf_strtab_hash_entry *cmp = *a; | |
| cmp->len += 1; | |
| if (is_suffix (e, cmp)) | |
| { | |
| cmp->u.suffix = e; | |
| cmp->len = -cmp->len; | |
| } | |
| else | |
| e = cmp; | |
| } | |
| } | |
| alloc_failure: | |
| free (array); | |
| /* Assign positions to the strings we want to keep. */ | |
| sec_size = 1; | |
| for (i = 1; i < tab->size; ++i) | |
| { | |
| e = tab->array[i]; | |
| if (e->refcount && e->len > 0) | |
| { | |
| e->u.index = sec_size; | |
| sec_size += e->len; | |
| } | |
| } | |
| tab->sec_size = sec_size; | |
| /* Adjust the rest. */ | |
| for (i = 1; i < tab->size; ++i) | |
| { | |
| e = tab->array[i]; | |
| if (e->refcount && e->len < 0) | |
| e->u.index = e->u.suffix->u.index + (e->u.suffix->len + e->len); | |
| } | |
| } |