| /* SEC_MERGE support. |
| Copyright 2001, 2002 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ |
| |
| /* This file contains support for merging duplicate entities within sections, |
| as used in ELF SHF_MERGE. */ |
| |
| #include "bfd.h" |
| #include "sysdep.h" |
| #include "libbfd.h" |
| #include "hashtab.h" |
| #include "libiberty.h" |
| |
| struct sec_merge_sec_info; |
| |
| /* An entry in the section merge hash table. */ |
| |
| struct sec_merge_hash_entry |
| { |
| struct bfd_hash_entry root; |
| /* Length of this entry. */ |
| unsigned int len; |
| /* Start of this string needs to be aligned to |
| alignment octets (not 1 << align). */ |
| unsigned int alignment; |
| union |
| { |
| /* Index within the merged section. */ |
| bfd_size_type index; |
| /* Entity size (if present in suffix hash tables). */ |
| unsigned int entsize; |
| /* Entry this is a suffix of (if alignment is 0). */ |
| struct sec_merge_hash_entry *suffix; |
| } u; |
| /* Which section is it in. */ |
| struct sec_merge_sec_info *secinfo; |
| /* Next entity in the hash table. */ |
| struct sec_merge_hash_entry *next; |
| }; |
| |
| /* The section merge hash table. */ |
| |
| struct sec_merge_hash |
| { |
| struct bfd_hash_table table; |
| /* Next available index. */ |
| bfd_size_type size; |
| /* First entity in the SEC_MERGE sections of this type. */ |
| struct sec_merge_hash_entry *first; |
| /* Last entity in the SEC_MERGE sections of this type. */ |
| struct sec_merge_hash_entry *last; |
| /* Entity size. */ |
| unsigned int entsize; |
| /* Are entries fixed size or zero terminated strings? */ |
| bfd_boolean strings; |
| }; |
| |
| struct sec_merge_info |
| { |
| /* Chain of sec_merge_infos. */ |
| struct sec_merge_info *next; |
| /* Chain of sec_merge_sec_infos. */ |
| struct sec_merge_sec_info *chain; |
| /* A hash table used to hold section content. */ |
| struct sec_merge_hash *htab; |
| }; |
| |
| struct sec_merge_sec_info |
| { |
| /* Chain of sec_merge_sec_infos. */ |
| struct sec_merge_sec_info *next; |
| /* The corresponding section. */ |
| asection *sec; |
| /* Pointer to merge_info pointing to us. */ |
| PTR *psecinfo; |
| /* A hash table used to hold section content. */ |
| struct sec_merge_hash *htab; |
| /* First string in this section. */ |
| struct sec_merge_hash_entry *first; |
| /* Original section content. */ |
| unsigned char contents[1]; |
| }; |
| |
| static struct bfd_hash_entry *sec_merge_hash_newfunc |
| PARAMS ((struct bfd_hash_entry *, struct bfd_hash_table *, const char *)); |
| static struct sec_merge_hash_entry *sec_merge_hash_lookup |
| PARAMS ((struct sec_merge_hash *, const char *, unsigned int, bfd_boolean)); |
| static struct sec_merge_hash *sec_merge_init |
| PARAMS ((unsigned int, bfd_boolean)); |
| static struct sec_merge_hash_entry *sec_merge_add |
| PARAMS ((struct sec_merge_hash *, const char *, unsigned int, |
| struct sec_merge_sec_info *)); |
| static bfd_boolean sec_merge_emit |
| PARAMS ((bfd *, struct sec_merge_hash_entry *)); |
| static int cmplengthentry |
| PARAMS ((const PTR, const PTR)); |
| static int last4_eq |
| PARAMS ((const PTR, const PTR)); |
| static int last_eq |
| PARAMS ((const PTR, const PTR)); |
| static bfd_boolean record_section |
| PARAMS ((struct sec_merge_info *, struct sec_merge_sec_info *)); |
| static void merge_strings |
| PARAMS ((struct sec_merge_info *)); |
| |
| /* Routine to create an entry in a section merge hashtab. */ |
| |
| static struct bfd_hash_entry * |
| sec_merge_hash_newfunc (entry, table, string) |
| struct bfd_hash_entry *entry; |
| struct bfd_hash_table *table; |
| const char *string; |
| { |
| struct sec_merge_hash_entry *ret = (struct sec_merge_hash_entry *) entry; |
| |
| /* Allocate the structure if it has not already been allocated by a |
| subclass. */ |
| if (ret == (struct sec_merge_hash_entry *) NULL) |
| ret = ((struct sec_merge_hash_entry *) |
| bfd_hash_allocate (table, sizeof (struct sec_merge_hash_entry))); |
| if (ret == (struct sec_merge_hash_entry *) NULL) |
| return NULL; |
| |
| /* Call the allocation method of the superclass. */ |
| ret = ((struct sec_merge_hash_entry *) |
| bfd_hash_newfunc ((struct bfd_hash_entry *) ret, table, string)); |
| |
| if (ret) |
| { |
| /* Initialize the local fields. */ |
| ret->u.suffix = NULL; |
| ret->alignment = 0; |
| ret->secinfo = NULL; |
| ret->next = NULL; |
| } |
| |
| return (struct bfd_hash_entry *) ret; |
| } |
| |
| /* Look up an entry in a section merge hash table. */ |
| |
| static struct sec_merge_hash_entry * |
| sec_merge_hash_lookup (table, string, alignment, create) |
| struct sec_merge_hash *table; |
| const char *string; |
| unsigned int alignment; |
| bfd_boolean create; |
| { |
| register const unsigned char *s; |
| register unsigned long hash; |
| register unsigned int c; |
| struct sec_merge_hash_entry *hashp; |
| unsigned int len, i; |
| unsigned int index; |
| |
| hash = 0; |
| len = 0; |
| s = (const unsigned char *) string; |
| if (table->strings) |
| { |
| if (table->entsize == 1) |
| { |
| while ((c = *s++) != '\0') |
| { |
| hash += c + (c << 17); |
| hash ^= hash >> 2; |
| ++len; |
| } |
| hash += len + (len << 17); |
| } |
| else |
| { |
| for (;;) |
| { |
| for (i = 0; i < table->entsize; ++i) |
| if (s[i] != '\0') |
| break; |
| if (i == table->entsize) |
| break; |
| for (i = 0; i < table->entsize; ++i) |
| { |
| c = *s++; |
| hash += c + (c << 17); |
| hash ^= hash >> 2; |
| } |
| ++len; |
| } |
| hash += len + (len << 17); |
| len *= table->entsize; |
| } |
| hash ^= hash >> 2; |
| len += table->entsize; |
| } |
| else |
| { |
| for (i = 0; i < table->entsize; ++i) |
| { |
| c = *s++; |
| hash += c + (c << 17); |
| hash ^= hash >> 2; |
| } |
| len = table->entsize; |
| } |
| |
| index = hash % table->table.size; |
| for (hashp = (struct sec_merge_hash_entry *) table->table.table[index]; |
| hashp != (struct sec_merge_hash_entry *) NULL; |
| hashp = (struct sec_merge_hash_entry *) hashp->root.next) |
| { |
| if (hashp->root.hash == hash |
| && len == hashp->len |
| && memcmp (hashp->root.string, string, len) == 0) |
| { |
| /* If the string we found does not have at least the required |
| alignment, we need to insert another copy. */ |
| if (hashp->alignment < alignment) |
| { |
| /* Mark the less aligned copy as deleted. */ |
| hashp->len = 0; |
| hashp->alignment = 0; |
| break; |
| } |
| return hashp; |
| } |
| } |
| |
| if (! create) |
| return (struct sec_merge_hash_entry *) NULL; |
| |
| hashp = (struct sec_merge_hash_entry *) |
| sec_merge_hash_newfunc ((struct bfd_hash_entry *) NULL, |
| (struct bfd_hash_table *) table, string); |
| if (hashp == (struct sec_merge_hash_entry *) NULL) |
| return (struct sec_merge_hash_entry *) NULL; |
| hashp->root.string = string; |
| hashp->root.hash = hash; |
| hashp->len = len; |
| hashp->alignment = alignment; |
| hashp->root.next = table->table.table[index]; |
| table->table.table[index] = (struct bfd_hash_entry *) hashp; |
| |
| return hashp; |
| } |
| |
| /* Create a new hash table. */ |
| |
| static struct sec_merge_hash * |
| sec_merge_init (entsize, strings) |
| unsigned int entsize; |
| bfd_boolean strings; |
| { |
| struct sec_merge_hash *table; |
| bfd_size_type amt = sizeof (struct sec_merge_hash); |
| |
| table = (struct sec_merge_hash *) bfd_malloc (amt); |
| if (table == NULL) |
| return NULL; |
| |
| if (! bfd_hash_table_init (&table->table, sec_merge_hash_newfunc)) |
| { |
| free (table); |
| return NULL; |
| } |
| |
| table->size = 0; |
| table->first = NULL; |
| table->last = NULL; |
| table->entsize = entsize; |
| table->strings = strings; |
| |
| return table; |
| } |
| |
| /* Get the index of an entity in a hash table, adding it if it is not |
| already present. */ |
| |
| static struct sec_merge_hash_entry * |
| sec_merge_add (tab, str, alignment, secinfo) |
| struct sec_merge_hash *tab; |
| const char *str; |
| unsigned int alignment; |
| struct sec_merge_sec_info *secinfo; |
| { |
| register struct sec_merge_hash_entry *entry; |
| |
| entry = sec_merge_hash_lookup (tab, str, alignment, TRUE); |
| if (entry == NULL) |
| return NULL; |
| |
| if (entry->secinfo == NULL) |
| { |
| tab->size++; |
| entry->secinfo = secinfo; |
| if (tab->first == NULL) |
| tab->first = entry; |
| else |
| tab->last->next = entry; |
| tab->last = entry; |
| } |
| |
| return entry; |
| } |
| |
| static bfd_boolean |
| sec_merge_emit (abfd, entry) |
| register bfd *abfd; |
| struct sec_merge_hash_entry *entry; |
| { |
| struct sec_merge_sec_info *secinfo = entry->secinfo; |
| asection *sec = secinfo->sec; |
| char *pad = ""; |
| bfd_size_type off = 0; |
| int alignment_power = bfd_get_section_alignment (abfd, sec->output_section); |
| |
| if (alignment_power) |
| pad = bfd_zmalloc ((bfd_size_type) 1 << alignment_power); |
| |
| for (; entry != NULL && entry->secinfo == secinfo; entry = entry->next) |
| { |
| register const char *str; |
| register size_t len; |
| |
| len = off & (entry->alignment - 1); |
| if (len) |
| { |
| len = entry->alignment - len; |
| if (bfd_bwrite ((PTR) pad, (bfd_size_type) len, abfd) != len) |
| break; |
| off += len; |
| } |
| |
| str = entry->root.string; |
| len = entry->len; |
| |
| if (bfd_bwrite ((PTR) str, (bfd_size_type) len, abfd) != len) |
| break; |
| |
| off += len; |
| } |
| |
| if (alignment_power) |
| free (pad); |
| |
| return entry == NULL || entry->secinfo != secinfo; |
| } |
| |
| /* This function is called for each input file from the add_symbols |
| pass of the linker. */ |
| |
| bfd_boolean |
| _bfd_merge_section (abfd, psinfo, sec, psecinfo) |
| bfd *abfd; |
| PTR *psinfo; |
| asection *sec; |
| PTR *psecinfo; |
| { |
| struct sec_merge_info *sinfo; |
| struct sec_merge_sec_info *secinfo; |
| unsigned int align; |
| bfd_size_type amt; |
| |
| if (sec->_raw_size == 0 |
| || (sec->flags & SEC_EXCLUDE) |
| || (sec->flags & SEC_MERGE) == 0 |
| || sec->entsize == 0) |
| return TRUE; |
| |
| if ((sec->flags & SEC_RELOC) != 0) |
| { |
| /* We aren't prepared to handle relocations in merged sections. */ |
| return TRUE; |
| } |
| |
| align = bfd_get_section_alignment (sec->owner, sec); |
| if ((sec->entsize < (unsigned int)(1 << align) |
| && ((sec->entsize & (sec->entsize - 1)) |
| || !(sec->flags & SEC_STRINGS))) |
| || (sec->entsize > (unsigned int)(1 << align) |
| && (sec->entsize & ((1 << align) - 1)))) |
| { |
| /* Sanity check. If string character size is smaller than |
| alignment, then we require character size to be a power |
| of 2, otherwise character size must be integer multiple |
| of alignment. For non-string constants, alignment must |
| be smaller than or equal to entity size and entity size |
| must be integer multiple of alignment. */ |
| return TRUE; |
| } |
| |
| for (sinfo = (struct sec_merge_info *) *psinfo; sinfo; sinfo = sinfo->next) |
| if ((secinfo = sinfo->chain) |
| && ! ((secinfo->sec->flags ^ sec->flags) & (SEC_MERGE | SEC_STRINGS)) |
| && secinfo->sec->entsize == sec->entsize |
| && ! strcmp (secinfo->sec->name, sec->name)) |
| break; |
| |
| if (sinfo == NULL) |
| { |
| /* Initialize the information we need to keep track of. */ |
| amt = sizeof (struct sec_merge_info); |
| sinfo = (struct sec_merge_info *) bfd_alloc (abfd, amt); |
| if (sinfo == NULL) |
| goto error_return; |
| sinfo->next = (struct sec_merge_info *) *psinfo; |
| sinfo->chain = NULL; |
| *psinfo = (PTR) sinfo; |
| sinfo->htab = sec_merge_init (sec->entsize, (sec->flags & SEC_STRINGS)); |
| if (sinfo->htab == NULL) |
| goto error_return; |
| } |
| |
| /* Read the section from abfd. */ |
| |
| amt = sizeof (struct sec_merge_sec_info) + sec->_raw_size - 1; |
| *psecinfo = bfd_alloc (abfd, amt); |
| if (*psecinfo == NULL) |
| goto error_return; |
| |
| secinfo = (struct sec_merge_sec_info *)*psecinfo; |
| if (sinfo->chain) |
| { |
| secinfo->next = sinfo->chain->next; |
| sinfo->chain->next = secinfo; |
| } |
| else |
| secinfo->next = secinfo; |
| sinfo->chain = secinfo; |
| secinfo->sec = sec; |
| secinfo->psecinfo = psecinfo; |
| secinfo->htab = sinfo->htab; |
| secinfo->first = NULL; |
| |
| if (! bfd_get_section_contents (sec->owner, sec, secinfo->contents, |
| (bfd_vma) 0, sec->_raw_size)) |
| goto error_return; |
| |
| return TRUE; |
| |
| error_return: |
| *psecinfo = NULL; |
| return FALSE; |
| } |
| |
| /* Compare two sec_merge_hash_entry structures. This is called via qsort. */ |
| |
| static int |
| cmplengthentry (a, b) |
| const PTR a; |
| const PTR b; |
| { |
| struct sec_merge_hash_entry * A = *(struct sec_merge_hash_entry **) a; |
| struct sec_merge_hash_entry * B = *(struct sec_merge_hash_entry **) b; |
| |
| if (A->len < B->len) |
| return 1; |
| else if (A->len > B->len) |
| return -1; |
| |
| return memcmp (A->root.string, B->root.string, A->len); |
| } |
| |
| static int |
| last4_eq (a, b) |
| const PTR a; |
| const PTR b; |
| { |
| struct sec_merge_hash_entry * A = (struct sec_merge_hash_entry *) a; |
| struct sec_merge_hash_entry * B = (struct sec_merge_hash_entry *) b; |
| |
| if (memcmp (A->root.string + A->len - 5 * A->u.entsize, |
| B->root.string + B->len - 5 * A->u.entsize, |
| 4 * A->u.entsize) != 0) |
| /* This was a hashtable collision. */ |
| return 0; |
| |
| 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; |
| |
| if (A->alignment < B->alignment |
| || ((A->len - B->len) & (B->alignment - 1))) |
| /* The suffix is not sufficiently aligned. */ |
| return 0; |
| |
| return memcmp (A->root.string + (A->len - B->len), |
| B->root.string, B->len - 5 * A->u.entsize) == 0; |
| } |
| |
| static int |
| last_eq (a, b) |
| const PTR a; |
| const PTR b; |
| { |
| struct sec_merge_hash_entry * A = (struct sec_merge_hash_entry *) a; |
| struct sec_merge_hash_entry * B = (struct sec_merge_hash_entry *) b; |
| |
| if (B->len >= 5 * A->u.entsize) |
| /* Longer strings are just pushed into the hash table, |
| they'll be used when looking up for very short strings. */ |
| return 0; |
| |
| if (memcmp (A->root.string + A->len - 2 * A->u.entsize, |
| B->root.string + B->len - 2 * A->u.entsize, |
| A->u.entsize) != 0) |
| /* This was a hashtable collision. */ |
| return 0; |
| |
| 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; |
| |
| if (A->alignment < B->alignment |
| || ((A->len - B->len) & (B->alignment - 1))) |
| /* The suffix is not sufficiently aligned. */ |
| return 0; |
| |
| return memcmp (A->root.string + (A->len - B->len), |
| B->root.string, B->len - 2 * A->u.entsize) == 0; |
| } |
| |
| /* Record one section into the hash table. */ |
| static bfd_boolean |
| record_section (sinfo, secinfo) |
| struct sec_merge_info *sinfo; |
| struct sec_merge_sec_info *secinfo; |
| { |
| asection *sec = secinfo->sec; |
| struct sec_merge_hash_entry *entry; |
| bfd_boolean nul; |
| unsigned char *p, *end; |
| bfd_vma mask, eltalign; |
| unsigned int align, i; |
| |
| align = bfd_get_section_alignment (sec->owner, sec); |
| end = secinfo->contents + sec->_raw_size; |
| nul = FALSE; |
| mask = ((bfd_vma) 1 << align) - 1; |
| if (sec->flags & SEC_STRINGS) |
| { |
| for (p = secinfo->contents; p < end; ) |
| { |
| eltalign = p - secinfo->contents; |
| eltalign = ((eltalign ^ (eltalign - 1)) + 1) >> 1; |
| if (!eltalign || eltalign > mask) |
| eltalign = mask + 1; |
| entry = sec_merge_add (sinfo->htab, p, (unsigned) eltalign, secinfo); |
| if (! entry) |
| goto error_return; |
| p += entry->len; |
| if (sec->entsize == 1) |
| { |
| while (p < end && *p == 0) |
| { |
| if (!nul && !((p - secinfo->contents) & mask)) |
| { |
| nul = TRUE; |
| entry = sec_merge_add (sinfo->htab, "", |
| (unsigned) mask + 1, secinfo); |
| if (! entry) |
| goto error_return; |
| } |
| p++; |
| } |
| } |
| else |
| { |
| while (p < end) |
| { |
| for (i = 0; i < sec->entsize; i++) |
| if (p[i] != '\0') |
| break; |
| if (i != sec->entsize) |
| break; |
| if (!nul && !((p - secinfo->contents) & mask)) |
| { |
| nul = TRUE; |
| entry = sec_merge_add (sinfo->htab, p, |
| (unsigned) mask + 1, secinfo); |
| if (! entry) |
| goto error_return; |
| } |
| p += sec->entsize; |
| } |
| } |
| } |
| } |
| else |
| { |
| for (p = secinfo->contents; p < end; p += sec->entsize) |
| { |
| entry = sec_merge_add (sinfo->htab, p, 1, secinfo); |
| if (! entry) |
| goto error_return; |
| } |
| } |
| |
| return TRUE; |
| |
| error_return: |
| for (secinfo = sinfo->chain; secinfo; secinfo = secinfo->next) |
| *secinfo->psecinfo = NULL; |
| return FALSE; |
| } |
| |
| /* This is a helper function for _bfd_merge_sections. It attempts to |
| merge strings matching suffixes of longer strings. */ |
| static void |
| merge_strings (sinfo) |
| struct sec_merge_info *sinfo; |
| { |
| struct sec_merge_hash_entry **array, **a, **end, *e; |
| struct sec_merge_sec_info *secinfo; |
| htab_t lasttab = NULL, last4tab = NULL; |
| bfd_size_type size, amt; |
| |
| /* Now sort the strings by length, longest first. */ |
| array = NULL; |
| amt = sinfo->htab->size * sizeof (struct sec_merge_hash_entry *); |
| array = (struct sec_merge_hash_entry **) bfd_malloc (amt); |
| if (array == NULL) |
| goto alloc_failure; |
| |
| for (e = sinfo->htab->first, a = array; e; e = e->next) |
| if (e->alignment) |
| *a++ = e; |
| |
| sinfo->htab->size = a - array; |
| |
| qsort (array, (size_t) sinfo->htab->size, |
| sizeof (struct sec_merge_hash_entry *), cmplengthentry); |
| |
| last4tab = htab_create_alloc ((size_t) sinfo->htab->size * 4, |
| NULL, last4_eq, NULL, calloc, free); |
| lasttab = htab_create_alloc ((size_t) sinfo->htab->size * 4, |
| NULL, last_eq, NULL, calloc, free); |
| if (lasttab == NULL || last4tab == NULL) |
| goto alloc_failure; |
| |
| /* Now insert the strings into hash tables (strings with last 4 characters |
| and strings with last character equal), look for longer strings which |
| we're suffix of. */ |
| for (a = array, end = array + sinfo->htab->size; a < end; a++) |
| { |
| register hashval_t hash; |
| unsigned int c; |
| unsigned int i; |
| const unsigned char *s; |
| PTR *p; |
| |
| e = *a; |
| e->u.entsize = sinfo->htab->entsize; |
| if (e->len <= e->u.entsize) |
| break; |
| if (e->len > 4 * e->u.entsize) |
| { |
| s = (const unsigned char *) (e->root.string + e->len - e->u.entsize); |
| hash = 0; |
| for (i = 0; i < 4 * e->u.entsize; i++) |
| { |
| c = *--s; |
| hash += c + (c << 17); |
| hash ^= hash >> 2; |
| } |
| p = htab_find_slot_with_hash (last4tab, e, hash, INSERT); |
| if (p == NULL) |
| goto alloc_failure; |
| if (*p) |
| { |
| struct sec_merge_hash_entry *ent; |
| |
| ent = (struct sec_merge_hash_entry *) *p; |
| e->u.suffix = ent; |
| e->alignment = 0; |
| continue; |
| } |
| else |
| *p = (PTR) e; |
| } |
| s = (const unsigned char *) (e->root.string + e->len - e->u.entsize); |
| hash = 0; |
| for (i = 0; i < e->u.entsize; i++) |
| { |
| c = *--s; |
| hash += c + (c << 17); |
| hash ^= hash >> 2; |
| } |
| p = htab_find_slot_with_hash (lasttab, e, hash, INSERT); |
| if (p == NULL) |
| goto alloc_failure; |
| if (*p) |
| { |
| struct sec_merge_hash_entry *ent; |
| |
| ent = (struct sec_merge_hash_entry *) *p; |
| e->u.suffix = ent; |
| e->alignment = 0; |
| } |
| else |
| *p = (PTR) e; |
| } |
| |
| alloc_failure: |
| if (array) |
| free (array); |
| if (lasttab) |
| htab_delete (lasttab); |
| if (last4tab) |
| htab_delete (last4tab); |
| |
| /* Now assign positions to the strings we want to keep. */ |
| size = 0; |
| secinfo = sinfo->htab->first->secinfo; |
| for (e = sinfo->htab->first; e; e = e->next) |
| { |
| if (e->secinfo != secinfo) |
| { |
| secinfo->sec->_cooked_size = size; |
| secinfo = e->secinfo; |
| } |
| if (e->alignment) |
| { |
| if (e->secinfo->first == NULL) |
| { |
| e->secinfo->first = e; |
| size = 0; |
| } |
| size = (size + e->alignment - 1) & ~((bfd_vma) e->alignment - 1); |
| e->u.index = size; |
| size += e->len; |
| } |
| } |
| secinfo->sec->_cooked_size = size; |
| |
| /* And now adjust the rest, removing them from the chain (but not hashtable) |
| at the same time. */ |
| for (a = &sinfo->htab->first, e = *a; e; e = e->next) |
| if (e->alignment) |
| a = &e->next; |
| else |
| { |
| *a = e->next; |
| if (e->len) |
| { |
| e->secinfo = e->u.suffix->secinfo; |
| e->alignment = e->u.suffix->alignment; |
| e->u.index = e->u.suffix->u.index + (e->u.suffix->len - e->len); |
| } |
| } |
| } |
| |
| /* This function is called once after all SEC_MERGE sections are registered |
| with _bfd_merge_section. */ |
| |
| bfd_boolean |
| _bfd_merge_sections (abfd, xsinfo, remove_hook) |
| bfd *abfd ATTRIBUTE_UNUSED; |
| PTR xsinfo; |
| void (*remove_hook) PARAMS((bfd *, asection *)); |
| { |
| struct sec_merge_info *sinfo; |
| |
| for (sinfo = (struct sec_merge_info *) xsinfo; sinfo; sinfo = sinfo->next) |
| { |
| struct sec_merge_sec_info * secinfo; |
| |
| if (! sinfo->chain) |
| continue; |
| |
| /* Move sinfo->chain to head of the chain, terminate it. */ |
| secinfo = sinfo->chain; |
| sinfo->chain = secinfo->next; |
| secinfo->next = NULL; |
| |
| /* Record the sections into the hash table. */ |
| for (secinfo = sinfo->chain; secinfo; secinfo = secinfo->next) |
| if (secinfo->sec->flags & SEC_EXCLUDE) |
| { |
| *secinfo->psecinfo = NULL; |
| if (remove_hook) |
| (*remove_hook) (abfd, secinfo->sec); |
| } |
| else if (! record_section (sinfo, secinfo)) |
| break; |
| |
| if (secinfo) |
| continue; |
| |
| if (sinfo->htab->first == NULL) |
| continue; |
| |
| if (sinfo->htab->strings) |
| merge_strings (sinfo); |
| else |
| { |
| struct sec_merge_hash_entry *e; |
| bfd_size_type size = 0; |
| |
| /* Things are much simpler for non-strings. |
| Just assign them slots in the section. */ |
| secinfo = NULL; |
| for (e = sinfo->htab->first; e; e = e->next) |
| { |
| if (e->secinfo->first == NULL) |
| { |
| if (secinfo) |
| secinfo->sec->_cooked_size = size; |
| e->secinfo->first = e; |
| size = 0; |
| } |
| size = (size + e->alignment - 1) |
| & ~((bfd_vma) e->alignment - 1); |
| e->u.index = size; |
| size += e->len; |
| secinfo = e->secinfo; |
| } |
| secinfo->sec->_cooked_size = size; |
| } |
| |
| /* Finally shrink all input sections which have not made it into |
| the hash table at all. */ |
| for (secinfo = sinfo->chain; secinfo; secinfo = secinfo->next) |
| if (secinfo->first == NULL) |
| secinfo->sec->_cooked_size = 0; |
| } |
| |
| return TRUE; |
| } |
| |
| /* Write out the merged section. */ |
| |
| bfd_boolean |
| _bfd_write_merged_section (output_bfd, sec, psecinfo) |
| bfd *output_bfd; |
| asection *sec; |
| PTR psecinfo; |
| { |
| struct sec_merge_sec_info *secinfo; |
| file_ptr pos; |
| |
| secinfo = (struct sec_merge_sec_info *) psecinfo; |
| |
| if (!secinfo->first) |
| return TRUE; |
| |
| pos = sec->output_section->filepos + sec->output_offset; |
| if (bfd_seek (output_bfd, pos, SEEK_SET) != 0) |
| return FALSE; |
| |
| if (! sec_merge_emit (output_bfd, secinfo->first)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /* Adjust an address in the SEC_MERGE section. Given OFFSET within |
| *PSEC, this returns the new offset in the adjusted SEC_MERGE |
| section and writes the new section back into *PSEC. */ |
| |
| bfd_vma |
| _bfd_merged_section_offset (output_bfd, psec, psecinfo, offset, addend) |
| bfd *output_bfd ATTRIBUTE_UNUSED; |
| asection **psec; |
| PTR psecinfo; |
| bfd_vma offset, addend; |
| { |
| struct sec_merge_sec_info *secinfo; |
| struct sec_merge_hash_entry *entry; |
| unsigned char *p; |
| asection *sec = *psec; |
| |
| secinfo = (struct sec_merge_sec_info *) psecinfo; |
| |
| if (offset + addend >= sec->_raw_size) |
| { |
| if (offset + addend > sec->_raw_size) |
| { |
| (*_bfd_error_handler) |
| (_("%s: access beyond end of merged section (%ld + %ld)"), |
| bfd_get_filename (sec->owner), (long) offset, (long) addend); |
| } |
| return (secinfo->first ? sec->_cooked_size : 0); |
| } |
| |
| if (secinfo->htab->strings) |
| { |
| if (sec->entsize == 1) |
| { |
| p = secinfo->contents + offset + addend - 1; |
| while (p >= secinfo->contents && *p) |
| --p; |
| ++p; |
| } |
| else |
| { |
| p = secinfo->contents |
| + ((offset + addend) / sec->entsize) * sec->entsize; |
| p -= sec->entsize; |
| while (p >= secinfo->contents) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < sec->entsize; ++i) |
| if (p[i] != '\0') |
| break; |
| if (i == sec->entsize) |
| break; |
| p -= sec->entsize; |
| } |
| p += sec->entsize; |
| } |
| } |
| else |
| { |
| p = secinfo->contents |
| + ((offset + addend) / sec->entsize) * sec->entsize; |
| } |
| entry = sec_merge_hash_lookup (secinfo->htab, p, 0, FALSE); |
| if (!entry) |
| { |
| if (! secinfo->htab->strings) |
| abort (); |
| /* This should only happen if somebody points into the padding |
| after a NUL character but before next entity. */ |
| if (*p) |
| abort (); |
| if (! secinfo->htab->first) |
| abort (); |
| entry = secinfo->htab->first; |
| p = secinfo->contents |
| + ((offset + addend) / sec->entsize + 1) * sec->entsize |
| - entry->len; |
| } |
| |
| *psec = entry->secinfo->sec; |
| return entry->u.index + (secinfo->contents + offset - p); |
| } |