+/* Returns the number of bytes needed to store VAL. */
+
+static inline unsigned int
+num_bytes (unsigned long val)
+{
+ unsigned int count = 0;
+
+ /* FIXME: There must be a faster way to do this. */
+ while (val)
+ {
+ count ++;
+ val >>= 8;
+ }
+ return count;
+}
+
+/* Merge the notes on SEC, removing redundant entries.
+ Returns the new, smaller size of the section upon success. */
+
+static bfd_size_type
+merge_gnu_build_notes (bfd * abfd, asection * sec, bfd_size_type size, bfd_byte * contents)
+{
+ Elf_Internal_Note * pnotes_end;
+ Elf_Internal_Note * pnotes;
+ Elf_Internal_Note * pnote;
+ bfd_size_type remain = size;
+ unsigned version_1_seen = 0;
+ unsigned version_2_seen = 0;
+ bfd_boolean duplicate_found = FALSE;
+ const char * err = NULL;
+ bfd_byte * in = contents;
+ int attribute_type_byte;
+ int val_start;
+
+ /* Make a copy of the notes.
+ Minimum size of a note is 12 bytes. */
+ pnote = pnotes = (Elf_Internal_Note *) xcalloc ((size / 12), sizeof (Elf_Internal_Note));
+ while (remain >= 12)
+ {
+ pnote->namesz = (bfd_get_32 (abfd, in ) + 3) & ~3;
+ pnote->descsz = (bfd_get_32 (abfd, in + 4) + 3) & ~3;
+ pnote->type = bfd_get_32 (abfd, in + 8);
+
+ if (pnote->type != NT_GNU_BUILD_ATTRIBUTE_OPEN
+ && pnote->type != NT_GNU_BUILD_ATTRIBUTE_FUNC)
+ {
+ err = _("corrupt GNU build attribute note: wrong note type");
+ goto done;
+ }
+
+ if (pnote->namesz + pnote->descsz + 12 > remain)
+ {
+ err = _("corrupt GNU build attribute note: note too big");
+ goto done;
+ }
+
+ if (pnote->namesz < 2)
+ {
+ err = _("corrupt GNU build attribute note: name too small");
+ goto done;
+ }
+
+ if (pnote->descsz != 0
+ && pnote->descsz != 4
+ && pnote->descsz != 8)
+ {
+ err = _("corrupt GNU build attribute note: bad description size");
+ goto done;
+ }
+
+ pnote->namedata = (char *)(in + 12);
+ pnote->descdata = (char *)(in + 12 + pnote->namesz);
+
+ remain -= 12 + pnote->namesz + pnote->descsz;
+ in += 12 + pnote->namesz + pnote->descsz;
+
+ if (pnote->namedata[pnote->namesz - 1] != 0)
+ {
+ err = _("corrupt GNU build attribute note: name not NUL terminated");
+ goto done;
+ }
+
+ if (pnote->namesz > 2
+ && pnote->namedata[0] == '$'
+ && pnote->namedata[1] == GNU_BUILD_ATTRIBUTE_VERSION
+ && pnote->namedata[2] == '1')
+ ++ version_1_seen;
+ else if (pnote->namesz > 4
+ && pnote->namedata[0] == 'G'
+ && pnote->namedata[1] == 'A'
+ && pnote->namedata[2] == '$'
+ && pnote->namedata[3] == GNU_BUILD_ATTRIBUTE_VERSION
+ && pnote->namedata[4] == '2')
+ ++ version_2_seen;
+ pnote ++;
+ }
+
+ pnotes_end = pnote;
+
+ /* Check that the notes are valid. */
+ if (remain != 0)
+ {
+ err = _("corrupt GNU build attribute notes: excess data at end");
+ goto done;
+ }
+
+ if (version_1_seen == 0 && version_2_seen == 0)
+ {
+ err = _("bad GNU build attribute notes: no known versions detected");
+ goto done;
+ }
+
+ if (version_1_seen > 0 && version_2_seen > 0)
+ {
+ err = _("bad GNU build attribute notes: multiple different versions");
+ goto done;
+ }
+
+ /* Merging is only needed if there is more than one version note... */
+ if (version_1_seen == 1 || version_2_seen == 1)
+ goto done;
+
+ attribute_type_byte = version_1_seen ? 1 : 3;
+ val_start = attribute_type_byte + 1;
+
+ /* The first note should be the first version note. */
+ if (pnotes[0].namedata[attribute_type_byte] != GNU_BUILD_ATTRIBUTE_VERSION)
+ {
+ err = _("bad GNU build attribute notes: first note not version note");
+ goto done;
+ }
+
+ /* Now merge the notes. The rules are:
+ 1. Preserve the ordering of the notes.
+ 2. Preserve any NT_GNU_BUILD_ATTRIBUTE_FUNC notes.
+ 3. Eliminate any NT_GNU_BUILD_ATTRIBUTE_OPEN notes that have the same
+ full name field as the immediately preceeding note with the same type
+ of name.
+ 4. Combine the numeric value of any NT_GNU_BUILD_ATTRIBUTE_OPEN notes
+ of type GNU_BUILD_ATTRIBUTE_STACK_SIZE.
+ 5. If an NT_GNU_BUILD_ATTRIBUTE_OPEN note is going to be preserved and
+ its description field is empty then the nearest preceeding OPEN note
+ with a non-empty description field must also be preserved *OR* the
+ description field of the note must be changed to contain the starting
+ address to which it refers. */
+ for (pnote = pnotes + 1; pnote < pnotes_end; pnote ++)
+ {
+ Elf_Internal_Note * back;
+ Elf_Internal_Note * prev_open = NULL;
+
+ if (pnote->type == NT_GNU_BUILD_ATTRIBUTE_FUNC)
+ continue;
+
+ /* Scan for duplicates. Clear the type field of any found - but do not
+ delete them just yet. */
+ for (back = pnote - 1; back >= pnotes; back --)
+ {
+ if (back->descsz > 0
+ && back->type != NT_GNU_BUILD_ATTRIBUTE_FUNC
+ && prev_open == NULL)
+ prev_open = back;
+
+ if (back->type == pnote->type
+ && back->namedata[attribute_type_byte] == pnote->namedata[attribute_type_byte])
+ {
+ if (back->namedata[attribute_type_byte] == GNU_BUILD_ATTRIBUTE_STACK_SIZE)
+ {
+ unsigned char * name;
+ unsigned long note_val;
+ unsigned long back_val;
+ unsigned int shift;
+ unsigned int bytes;
+ unsigned long byte;
+
+ for (shift = 0, note_val = 0,
+ bytes = pnote->namesz - val_start,
+ name = (unsigned char *) pnote->namedata + val_start;
+ bytes--;)
+ {
+ byte = (* name ++) & 0xff;
+ note_val |= byte << shift;
+ shift += 8;
+ }
+
+ for (shift = 0, back_val = 0,
+ bytes = back->namesz - val_start,
+ name = (unsigned char *) back->namedata + val_start;
+ bytes--;)
+ {
+ byte = (* name ++) & 0xff;
+ back_val |= byte << shift;
+ shift += 8;
+ }
+
+ back_val += note_val;
+ if (num_bytes (back_val) >= back->namesz - val_start)
+ {
+ /* We have a problem - the new value requires more bytes of
+ storage in the name field than are available. Currently
+ we have no way of fixing this, so we just preserve both
+ notes. */
+ continue;
+ }
+
+ /* Write the new val into back. */
+ name = (unsigned char *) back->namedata + val_start;
+ while (name < (unsigned char *) back->namedata + back->namesz)
+ {
+ byte = back_val & 0xff;
+ * name ++ = byte;
+ if (back_val == 0)
+ break;
+ back_val >>= 8;
+ }
+
+ duplicate_found = TRUE;
+ pnote->type = 0;
+ break;
+ }
+
+ if (back->namesz == pnote->namesz
+ && memcmp (back->namedata, pnote->namedata, back->namesz) == 0)
+ {
+ duplicate_found = TRUE;
+ pnote->type = 0;
+ break;
+ }
+
+ /* If we have found an attribute match then stop searching backwards. */
+ if (! ISPRINT (back->namedata[attribute_type_byte])
+ /* Names are NUL terminated, so this is safe. */
+ || strcmp (back->namedata + val_start, pnote->namedata + val_start) == 0)
+ {
+ /* Since we are keeping this note we must check to see if its
+ description refers back to an earlier OPEN version note. If so
+ then we must make sure that version note is also preserved. */
+ if (pnote->descsz == 0
+ && prev_open != NULL
+ && prev_open->type == 0)
+ prev_open->type = NT_GNU_BUILD_ATTRIBUTE_FUNC;
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (duplicate_found)
+ {
+ bfd_byte * new_contents;
+ bfd_byte * old;
+ bfd_byte * new;
+ bfd_size_type new_size;
+ arelent ** relpp = NULL;
+ long relsize;
+ long relcount = 0;
+
+ relsize = bfd_get_reloc_upper_bound (abfd, sec);
+ if (relsize > 0)
+ {
+ /* If there are relocs associated with this section then we may
+ have to adjust them as well, as we remove notes. */
+ relpp = (arelent **) xmalloc (relsize);
+ relcount = bfd_canonicalize_reloc (abfd, sec, relpp, isympp);
+ if (relcount < 0)
+ /* Do not bother complaining here - copy_relocations_in_section
+ will do that for us. */
+ relcount = 0;
+ }
+
+ /* Eliminate the duplicates. */
+ new = new_contents = xmalloc (size);
+ for (pnote = pnotes, old = contents;
+ pnote < pnotes_end;
+ pnote ++)
+ {
+ bfd_size_type note_size = 12 + pnote->namesz + pnote->descsz;
+
+ if (pnote->type == 0)
+ {
+ if (relcount > 0)
+ {
+ arelent ** rel;
+
+ /* If there is a reloc at the current offset, delete it.
+ Adjust the location of any relocs above the current
+ location downwards by the size of the note being deleted.
+ FIXME: We could optimize this loop by retaining a pointer to
+ the last reloc below the current note. */
+ for (rel = relpp; rel < relpp + relcount; rel ++)
+ {
+ if ((* rel)->howto == NULL)
+ continue;
+ if ((* rel)->address < (bfd_vma) (new - new_contents))
+ continue;
+ if ((* rel)->address >= (bfd_vma) ((new + note_size) - new_contents))
+ (* rel)->address -= note_size;
+ else
+ (* rel)->howto = NULL;
+ }
+ }
+ }
+ else
+ {
+ memcpy (new, old, note_size);
+ new += note_size;
+ }
+
+ old += note_size;
+ }
+
+ new_size = new - new_contents;
+ memcpy (contents, new_contents, new_size);
+ size = new_size;
+ free (new_contents);
+
+ if (relcount > 0)
+ {
+ arelent **rel = relpp;
+
+ while (rel < relpp + relcount)
+ if ((*rel)->howto != NULL)
+ rel++;
+ else
+ {
+ /* Delete eliminated relocs.
+ FIXME: There are better ways to do this. */
+ memmove (rel, rel + 1,
+ ((relcount - (rel - relpp)) - 1) * sizeof (*rel));
+ relcount--;
+ }
+ bfd_set_reloc (abfd, sec, relpp, relcount);
+ }
+ }
+
+ done:
+ if (err)
+ {
+ bfd_set_error (bfd_error_bad_value);
+ bfd_nonfatal_message (NULL, abfd, sec, err);
+ status = 1;
+ }
+
+ free (pnotes);
+ return size;
+}
+