* peXXigen.c: Include wchar.h if available.
authorNick Clifton <nickc@redhat.com>
Tue, 17 Dec 2013 15:39:13 +0000 (15:39 +0000)
committerNick Clifton <nickc@redhat.com>
Tue, 17 Dec 2013 15:39:13 +0000 (15:39 +0000)
Include safe-ctype.h.
(HighBitSet, SetHighBit, WithoutHighBit): New macros.
(pe_print_resource_entries): Rename to
rsrc_print_resource_entries.  Handle names that are not RVAs.
(pe_print_resource_directory): Rename to
rsrc_print_resource_directory.
(pe_print_rsrc): Rename to rsrc_print_section.  Corrupt
computation of RVA bias.
(rsrc_count_entries): New function.
(rsrc_count_directory): New function.
(rsrc_parse_entry): New function.
(rsrc_parse_entries): New function.
(rsrc_parse_directory): New function.
(rsrc_write_string): New function.
(rsrc_compute_rva): New function.
(rsrc_write_leaf): New function.
(rsrc_write_entry): New function.
(rsrc_write_directory): New function.
(u16_mbtouc): New function.
(rsrc_cmp): New function.
(rsrc_print_name): New function.
(rsrc_resource_name): New function.
(rsrc_merge_string_entries): New function.
(rsrc_sort_entries): New function.
(rsrc_attach_chain): New function.
(rsrc_merge): New function.
(rsrc_process_section): New function - merges the contents of a
.rsrc section.
(_bfd_XXi_final_link_postscript): Call rsrc_process_section.
* configure.in (AC_CHECK_HEADERS): Add wchar.h
* config.in: Regenerate.
* configure: Regenerate.

bfd/ChangeLog
bfd/config.in
bfd/configure
bfd/configure.in
bfd/peXXigen.c

index 63865f4..4df87fe 100644 (file)
@@ -1,3 +1,39 @@
+2013-12-17  Nick Clifton  <nickc@redhat.com>
+
+       * peXXigen.c: Include wchar.h if available.
+       Include safe-ctype.h.
+       (HighBitSet, SetHighBit, WithoutHighBit): New macros.
+       (pe_print_resource_entries): Rename to
+       rsrc_print_resource_entries.  Handle names that are not RVAs.
+       (pe_print_resource_directory): Rename to
+       rsrc_print_resource_directory.
+       (pe_print_rsrc): Rename to rsrc_print_section.  Corrupt
+       computation of RVA bias.
+       (rsrc_count_entries): New function.
+       (rsrc_count_directory): New function.
+       (rsrc_parse_entry): New function.
+       (rsrc_parse_entries): New function.
+       (rsrc_parse_directory): New function.
+       (rsrc_write_string): New function.
+       (rsrc_compute_rva): New function.
+       (rsrc_write_leaf): New function.
+       (rsrc_write_entry): New function.
+       (rsrc_write_directory): New function.
+       (u16_mbtouc): New function.
+       (rsrc_cmp): New function.
+       (rsrc_print_name): New function.
+       (rsrc_resource_name): New function.
+       (rsrc_merge_string_entries): New function.
+       (rsrc_sort_entries): New function.
+       (rsrc_attach_chain): New function.
+       (rsrc_merge): New function.
+       (rsrc_process_section): New function - merges the contents of a
+       .rsrc section.
+       (_bfd_XXi_final_link_postscript): Call rsrc_process_section.
+       * configure.in (AC_CHECK_HEADERS): Add wchar.h
+       * config.in: Regenerate.
+       * configure: Regenerate.
+
 2013-12-16  Andreas Schwab  <schwab@suse.de>
 
        * elf64-ppc.c (ppc64_elf_relocate_section): Add newline to error
index 65fb044..3106c28 100644 (file)
 /* Define to 1 if you have the <unistd.h> header file. */
 #undef HAVE_UNISTD_H
 
+/* Define to 1 if you have the <wchar.h> header file. */
+#undef HAVE_WCHAR_H
+
 /* Define if <sys/procfs.h> has win32_pstatus_t. */
 #undef HAVE_WIN32_PSTATUS_T
 
index 382dd01..bef7295 100755 (executable)
@@ -12900,7 +12900,7 @@ $as_echo "$bfd_cv_build_exeext" >&6; }
 fi
 
 
-for ac_header in alloca.h stddef.h string.h strings.h stdlib.h time.h unistd.h
+for ac_header in alloca.h stddef.h string.h strings.h stdlib.h time.h unistd.h wchar.h
 do :
   as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
 ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
index 0d41867..6a029f7 100644 (file)
@@ -210,7 +210,7 @@ AC_SUBST(BFD_HOSTPTR_T)
 
 BFD_CC_FOR_BUILD
 
-AC_CHECK_HEADERS(alloca.h stddef.h string.h strings.h stdlib.h time.h unistd.h)
+AC_CHECK_HEADERS(alloca.h stddef.h string.h strings.h stdlib.h time.h unistd.h wchar.h)
 AC_CHECK_HEADERS(fcntl.h sys/file.h sys/time.h sys/stat.h sys/resource.h)
 GCC_HEADER_STDINT(bfd_stdint.h)
 AC_HEADER_TIME
index b720668..cddb38f 100644 (file)
@@ -62,6 +62,9 @@
 #include "libbfd.h"
 #include "coff/internal.h"
 #include "bfdver.h"
+#ifdef HAVE_WCHAR_H
+#include <wchar.h>
+#endif
 
 /* NOTE: it's strange to be including an architecture specific header
    in what's supposed to be general (to PE/PEI) code.  However, that's
@@ -80,6 +83,7 @@
 #include "coff/pe.h"
 #include "libcoff.h"
 #include "libpei.h"
+#include "safe-ctype.h"
 
 #if defined COFF_WITH_pep || defined COFF_WITH_pex64
 # undef AOUTSZ
 # define PEAOUTHDR     PEPAOUTHDR
 #endif
 
+#define HighBitSet(val)      ((val) & 0x80000000)
+#define SetHighBit(val)      ((val) | 0x80000000)
+#define WithoutHighBit(val)  ((val) & 0x7fffffff)
+
 /* FIXME: This file has various tests of POWERPC_LE_PE.  Those tests
    worked when the code was in peicode.h, but no longer work now that
    the code is in peigen.c.  PowerPC NT is said to be dead.  If
@@ -1285,9 +1293,10 @@ pe_print_idata (bfd * abfd, void * vfile)
              if (!member && !member_high)
                break;
 
-             if (member_high & 0x80000000)
+             if (HighBitSet (member_high))
                fprintf (file, "\t%lx%08lx\t %4lx%08lx  <none>",
-                        member_high,member, member_high & 0x7fffffff, member);
+                        member_high, member,
+                        WithoutHighBit (member_high), member);
              else
                {
                  int ordinal;
@@ -1317,9 +1326,9 @@ pe_print_idata (bfd * abfd, void * vfile)
              if (member == 0)
                break;
 
-             if (member & 0x80000000)
+             if (HighBitSet (member))
                fprintf (file, "\t%04lx\t %4lu  <none>",
-                        member, member & 0x7fffffff);
+                        member, WithoutHighBit (member));
              else
                {
                  int ordinal;
@@ -1990,20 +1999,21 @@ pe_print_reloc (bfd * abfd, void * vfile)
 
   return TRUE;
 }
+\f
 
 static bfd_byte *
-pe_print_resource_directory (FILE * , bfd *, unsigned int,
-                            bfd_byte *, bfd_byte *, bfd_byte *, bfd_vma);
+rsrc_print_resource_directory (FILE * , bfd *, unsigned int,
+                              bfd_byte *, bfd_byte *, bfd_byte *, bfd_vma);
 
 static bfd_byte *
-pe_print_resource_entries (FILE *        file,
-                          bfd *         abfd,
-                          unsigned int  indent,
-                          bfd_boolean   is_name,
-                          bfd_byte *    data,
-                          bfd_byte *    datastart,
-                          bfd_byte *    dataend,
-                          bfd_vma       rva_bias)
+rsrc_print_resource_entries (FILE *        file,
+                            bfd *         abfd,
+                            unsigned int  indent,
+                            bfd_boolean   is_name,
+                            bfd_byte *    datastart,
+                            bfd_byte *    data,
+                            bfd_byte *    dataend,
+                            bfd_vma       rva_bias)
 {
   unsigned long entry, addr, size;
 
@@ -2015,65 +2025,81 @@ pe_print_resource_entries (FILE *        file,
   entry = (long) bfd_get_32 (abfd, data);
   if (is_name)
     {
-      if (datastart + entry < dataend)
+      bfd_byte * name;
+
+      /* Note - the documenation says that this field is an RVA value
+        but windres appears to produce a section relative offset with
+        the top bit set.  Support both styles for now.  */
+      if (HighBitSet (entry))
+       name = datastart + WithoutHighBit (entry);
+      else
+       name = datastart + entry - rva_bias;
+
+      if (name + 2 < dataend)
        {
          unsigned int len;
-         len = bfd_get_16 (abfd, datastart + entry);
+         len = bfd_get_16 (abfd, name);
       
-         fprintf (file, _("name: %08lx [%d:]"), entry, len);
-         if (datastart + entry - rva_bias + 2 + len < dataend)
-           fprintf (file, "%.*s", len, (char *) (datastart + entry - rva_bias + 2));
+         fprintf (file, _("name: [val: %08lx len %d]: "), entry, len);
+         if (name + 2 + len * 2 < dataend)
+           {
+             /* This strange loop is to cope with multibyte characters.  */
+             while (len --)
+               {
+                 name += 2;
+                 fprintf (file, "%.1s", name);
+               }
+           }
          else
-           fprintf (file, _("<corrupt>"));
+           fprintf (file, _("<corrupt string length: %#x>"), len);
        }
       else
-       fprintf (file, _("<corrupt>"));
+       fprintf (file, _("<corrupt string offset: %#lx>"), entry);
     }
   else
     fprintf (file, _("ID: %#08lx"), entry);
             
   entry = (long) bfd_get_32 (abfd, data + 4);
-  fprintf (file, _(", Value: %#08lx"), entry);
+  fprintf (file, _(", Value: %#08lx\n"), entry);
 
-  if (entry & 0x80000000)
-    {
-      fprintf (file, _(" sub-table:\n"));
-      return pe_print_resource_directory (file, abfd, indent + 1,
-                                         datastart + (entry & 0x7fffffff),
-                                         datastart, dataend, rva_bias);
-    }
+  if (HighBitSet  (entry))
+    return rsrc_print_resource_directory (file, abfd, indent + 1,
+                                         datastart,
+                                         datastart + WithoutHighBit (entry),
+                                         dataend, rva_bias);
 
   if (datastart + entry + 16 >= dataend)
-    {
-      fprintf (file, "\n");      
-      return dataend + 1;
-    }
+    return dataend + 1;
 
-  fprintf (file, _(" leaf: Addr: %#08lx, Size: %#08lx, Codepage: %d\n"),
+  fprintf (file, _("%*.s  Leaf: Addr: %#08lx, Size: %#08lx, Codepage: %d\n"),
+          indent, " ",
           addr = (long) bfd_get_32 (abfd, datastart + entry),
           size = (long) bfd_get_32 (abfd, datastart + entry + 4),
           (int) bfd_get_32 (abfd, datastart + entry + 8));
-
+            
   /* Check that the reserved entry is 0.  */
   if (bfd_get_32 (abfd, datastart + entry + 12) != 0
       /* And that the data address/size is valid too.  */
-      || (datastart + addr - rva_bias + size > dataend))
+      || (datastart + (addr - rva_bias) + size > dataend))
     return dataend + 1;
 
-  return datastart + addr - rva_bias + size;
+  return datastart + (addr - rva_bias) + size;
 }
 
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#define min(a,b) ((a) < (b) ? (a) : (b))
+
 static bfd_byte *
-pe_print_resource_directory (FILE *        file,
-                            bfd *         abfd,
-                            unsigned int  indent,
-                            bfd_byte *    data,
-                            bfd_byte *    datastart,
-                            bfd_byte *    dataend,
-                            bfd_vma       rva_bias)
+rsrc_print_resource_directory (FILE *        file,
+                              bfd *         abfd,
+                              unsigned int  indent,
+                              bfd_byte *    datastart,
+                              bfd_byte *    data,
+                              bfd_byte *    dataend,
+                              bfd_vma       rva_bias)
 {
   unsigned int num_names, num_ids;
-  bfd_byte * enddata = data;
+  bfd_byte * highest_data = data;
 
   if (data + 16 >= dataend)
     return dataend + 1;
@@ -2096,37 +2122,31 @@ pe_print_resource_directory (FILE *        file,
           num_ids =   (int) bfd_get_16 (abfd, data + 14));
   data += 16;
 
-  if (num_names)
+  while (num_names --)
     {
-      while (num_names --)
-       {
-         bfd_byte * ndata;
-         ndata = pe_print_resource_entries (file, abfd, indent + 1, TRUE,
-                                            data, datastart, dataend, rva_bias);
-         data += 8;
-         if (ndata > enddata)
-           enddata = ndata;
-         if (ndata >= dataend)
-           break;
-       }
+      bfd_byte * entry_end;
+
+      entry_end = rsrc_print_resource_entries (file, abfd, indent + 1, TRUE,
+                                              datastart, data, dataend, rva_bias);
+      data += 8;
+      highest_data = max (highest_data, entry_end);
+      if (entry_end >= dataend)
+       return entry_end;
     }
 
-  if (num_ids)
+  while (num_ids --)
     {
-      while (num_ids --)
-       {
-         bfd_byte * ndata;
-         ndata = pe_print_resource_entries (file, abfd, indent + 1, FALSE,
-                                            data, datastart, dataend, rva_bias);
-         data += 8;
-         if (ndata > enddata)
-           enddata = ndata;
-         if (ndata >= dataend)
-           break;
-       }
+      bfd_byte * entry_end;
+
+      entry_end = rsrc_print_resource_entries (file, abfd, indent + 1, FALSE,
+                                              datastart, data, dataend, rva_bias);
+      data += 8;
+      highest_data = max (highest_data, entry_end);
+      if (entry_end >= dataend)
+       return entry_end;
     }
 
-  return enddata > data ? enddata : data;
+  return max (highest_data, data);
 }
 
 /* Display the contents of a .rsrc section.  We do not try to
@@ -2134,7 +2154,7 @@ pe_print_resource_directory (FILE *        file,
    the tables in a human readable format.  */
 
 static bfd_boolean
-pe_print_rsrc (bfd * abfd, void * vfile)
+rsrc_print_section (bfd * abfd, void * vfile)
 {
   bfd_vma rva_bias;
   pe_data_type * pe;
@@ -2147,20 +2167,20 @@ pe_print_rsrc (bfd * abfd, void * vfile)
 
 
   pe = pe_data (abfd);
- if (pe == NULL)
 if (pe == NULL)
     return TRUE;
 
- section = bfd_get_section_by_name (abfd, ".rsrc");
- if (section == NULL)
 section = bfd_get_section_by_name (abfd, ".rsrc");
 if (section == NULL)
     return TRUE;
 
   rva_bias = section->vma - pe->pe_opthdr.ImageBase;
+
   datasize = section->size;
   if (datasize == 0)
     return TRUE;
 
-  if (! bfd_malloc_and_get_section (abfd, section, &data))
+  if (! bfd_malloc_and_get_section (abfd, section, & data))
     {
       if (data != NULL)
        free (data);
@@ -2174,7 +2194,9 @@ pe_print_rsrc (bfd * abfd, void * vfile)
 
   while (data < dataend)
     {
-      data = pe_print_resource_directory (file, abfd, 0, data, data, dataend, rva_bias);
+      bfd_byte * p = data;
+
+      data = rsrc_print_resource_directory (file, abfd, 0, data, data, dataend, rva_bias);
 
       if (data == dataend + 1)
        fprintf (file, _("Corrupt .rsrc section detected!\n"));
@@ -2182,8 +2204,9 @@ pe_print_rsrc (bfd * abfd, void * vfile)
        {
          /* Align data before continuing.  */
          int align = (1 << section->alignment_power) - 1;
+
          data = (bfd_byte *) (((long) (data + align)) & ~ align);
-         rva_bias += data - datastart;
+         rva_bias += data - p;
 
          /* For reasons that are unclear .rsrc sections are sometimes created
             aligned to a 1^3 boundary even when their alignment is set at
@@ -2196,6 +2219,7 @@ pe_print_rsrc (bfd * abfd, void * vfile)
        }
     }
 
+  free (datastart);
   return TRUE;
 }
 
@@ -2373,7 +2397,7 @@ _bfd_XX_print_private_bfd_data_common (bfd * abfd, void * vfile)
     pe_print_pdata (abfd, vfile);
   pe_print_reloc (abfd, vfile);
 
-  pe_print_rsrc (abfd, vfile);
+  rsrc_print_section (abfd, vfile);
   
   return TRUE;
 }
@@ -2479,6 +2503,1096 @@ sort_x64_pdata (const void *l, const void *r)
   return 0;
 }
 #endif
+\f
+/* Functions to process a .rsrc section.  */
+
+static unsigned int sizeof_leaves;
+static unsigned int sizeof_strings;
+static unsigned int sizeof_tables_and_entries;
+
+static bfd_byte *
+rsrc_count_directory (bfd *, bfd_byte *, bfd_byte *, bfd_byte *, bfd_vma);
+
+static bfd_byte *
+rsrc_count_entries (bfd *          abfd,
+                   bfd_boolean    is_name,
+                   bfd_byte *     datastart,
+                   bfd_byte *     data,
+                   bfd_byte *     dataend,
+                   bfd_vma        rva_bias)
+{
+  unsigned long entry, addr, size;
+
+  if (data + 8 >= dataend)
+    return dataend + 1;
+
+  if (is_name)
+    {
+      bfd_byte * name;
+
+      entry = (long) bfd_get_32 (abfd, data);
+
+      if (HighBitSet (entry))
+       name = datastart + WithoutHighBit (entry);
+      else
+       name = datastart + entry - rva_bias;
+
+      if (name + 2 >= dataend)
+       return dataend + 1;
+
+      unsigned int len = bfd_get_16 (abfd, name);
+      if (len == 0 || len > 256)
+       return dataend + 1;
+
+      sizeof_strings += (len + 1) * 2;
+    }
+
+  entry = (long) bfd_get_32 (abfd, data + 4);
+
+  if (HighBitSet (entry))
+    return rsrc_count_directory (abfd,
+                                datastart,
+                                datastart + WithoutHighBit (entry),
+                                dataend, rva_bias);
+
+  if (datastart + entry + 16 >= dataend)
+    return dataend + 1;
+
+  addr = (long) bfd_get_32 (abfd, datastart + entry);
+  size = (long) bfd_get_32 (abfd, datastart + entry + 4);
+
+  sizeof_leaves += 16;
+
+  return datastart + addr - rva_bias + size;
+}
+static bfd_byte *
+rsrc_count_directory (bfd *          abfd,
+                     bfd_byte *     datastart,
+                     bfd_byte *     data,
+                     bfd_byte *     dataend,
+                     bfd_vma        rva_bias)
+{
+  unsigned int  num_entries, num_ids;
+  bfd_byte *    highest_data = data;
+
+  if (data + 16 >= dataend)
+    return dataend + 1;
+
+  num_entries  = (int) bfd_get_16 (abfd, data + 12);
+  num_ids      = (int) bfd_get_16 (abfd, data + 14);
+
+  num_entries += num_ids;
+
+  data += 16;
+  sizeof_tables_and_entries += 16;
+
+  while (num_entries --)
+    {
+      bfd_byte * entry_end;
+
+      entry_end = rsrc_count_entries (abfd, num_entries >= num_ids,
+                                     datastart, data, dataend, rva_bias);
+      data += 8;
+      sizeof_tables_and_entries += 8;
+      highest_data = max (highest_data, entry_end);
+      if (entry_end >= dataend)
+       break;
+    }
+
+  return max (highest_data, data);
+}
+
+typedef struct rsrc_dir_chain
+{
+  unsigned int         num_entries;
+  struct rsrc_entry *  first_entry;
+  struct rsrc_entry *  last_entry;
+} rsrc_dir_chain;
+
+typedef struct rsrc_directory
+{
+  unsigned int characteristics;
+  unsigned int time;
+  unsigned int major;
+  unsigned int minor;
+
+  rsrc_dir_chain names;
+  rsrc_dir_chain ids;
+
+  struct rsrc_entry * entry;
+} rsrc_directory;
+
+typedef struct rsrc_string
+{
+  unsigned int  len;
+  bfd_byte *    string;
+} rsrc_string;
+  
+typedef struct rsrc_leaf
+{
+  unsigned int  size;
+  unsigned int  codepage;
+  bfd_byte *    data;
+} rsrc_leaf;
+
+typedef struct rsrc_entry
+{
+  bfd_boolean is_name;
+  union
+  {
+    unsigned int          id;
+    struct rsrc_string    name;
+  } name_id;
+
+  bfd_boolean is_dir;
+  union
+  {
+    struct rsrc_directory * directory;
+    struct rsrc_leaf *      leaf;
+  } value;
+
+  struct rsrc_entry *     next_entry;
+  struct rsrc_directory * parent;
+} rsrc_entry;
+
+static bfd_byte *
+rsrc_parse_directory (bfd *, rsrc_directory *, bfd_byte *,
+                     bfd_byte *, bfd_byte *, bfd_vma, rsrc_entry *);
+
+static bfd_byte *
+rsrc_parse_entry (bfd *            abfd,
+                 bfd_boolean      is_name,
+                 rsrc_entry *     entry,
+                 bfd_byte *       datastart,
+                 bfd_byte *       data,
+                 bfd_byte *       dataend,
+                 bfd_vma          rva_bias,
+                 rsrc_directory * parent)
+{
+  unsigned long val, addr, size;
+
+  val = bfd_get_32 (abfd, data);
+
+  entry->parent = parent;
+  entry->is_name = is_name;
+
+  if (is_name)
+    {
+      /* FIXME: Add range checking ?  */
+      if (HighBitSet (val))
+       {
+         val = WithoutHighBit (val);
+
+         entry->name_id.name.len    = bfd_get_16 (abfd, datastart + val);
+         entry->name_id.name.string = datastart + val + 2;
+       }
+      else
+       {
+         entry->name_id.name.len    = bfd_get_16 (abfd, datastart + val - rva_bias);
+         entry->name_id.name.string = datastart + val - rva_bias + 2;
+       }
+    }
+  else
+    entry->name_id.id = val;
+
+  val = bfd_get_32 (abfd, data + 4);
+
+  if (HighBitSet (val))
+    {
+      entry->is_dir = TRUE;
+      entry->value.directory = bfd_malloc (sizeof * entry->value.directory);
+      if (entry->value.directory == NULL)
+       return dataend;
+
+      return rsrc_parse_directory (abfd, entry->value.directory,
+                                  datastart,
+                                  datastart + WithoutHighBit (val),
+                                  dataend, rva_bias, entry);
+    }
+
+  entry->is_dir = FALSE;
+  entry->value.leaf = bfd_malloc (sizeof * entry->value.leaf);
+  if (entry->value.leaf == NULL)
+    return dataend;
+
+  addr = bfd_get_32 (abfd, datastart + val);
+  size = entry->value.leaf->size = bfd_get_32 (abfd, datastart + val + 4);
+  entry->value.leaf->codepage = bfd_get_32 (abfd, datastart + val + 8);
+
+  entry->value.leaf->data = bfd_malloc (size);
+  if (entry->value.leaf->data == NULL)
+    return dataend;
+
+  memcpy (entry->value.leaf->data, datastart + addr - rva_bias, size);
+  return datastart + (addr - rva_bias) + size;
+}
+
+static bfd_byte *
+rsrc_parse_entries (bfd *            abfd,
+                   rsrc_dir_chain * chain,
+                   bfd_boolean      is_name,
+                   bfd_byte *       highest_data,
+                   bfd_byte *       datastart,
+                   bfd_byte *       data,
+                   bfd_byte *       dataend,
+                   bfd_vma          rva_bias,
+                   rsrc_directory * parent)
+{
+  rsrc_entry * entry;
+
+  if (chain->num_entries == 0)
+    {
+      chain->first_entry = chain->last_entry = NULL;
+      return highest_data;
+    }
+
+  entry = bfd_malloc (sizeof * entry);
+  if (entry == NULL)
+    return dataend;
+
+  chain->first_entry = entry;
+
+  unsigned int i;
+  for (i = chain->num_entries; i--;)
+    {
+      bfd_byte * entry_end;
+
+      entry_end = rsrc_parse_entry (abfd, is_name, entry, datastart,
+                                   data, dataend, rva_bias, parent);
+      data += 8;
+      highest_data = max (entry_end, highest_data);
+      if (entry_end > dataend)
+       return dataend;
+
+      if (i)
+       {
+         entry->next_entry = bfd_malloc (sizeof * entry);
+         entry = entry->next_entry;
+         if (entry == NULL)
+           return dataend;
+       }
+      else
+       entry->next_entry = NULL;
+    }
+
+  chain->last_entry = entry;
+
+  return highest_data;
+}
+
+static bfd_byte *
+rsrc_parse_directory (bfd *            abfd,
+                     rsrc_directory * table,
+                     bfd_byte *       datastart,
+                     bfd_byte *       data,
+                     bfd_byte *       dataend,
+                     bfd_vma          rva_bias,
+                     rsrc_entry *     entry)
+{
+  bfd_byte * highest_data = data;
+
+  if (table == NULL)
+    return dataend;
+
+  table->characteristics = bfd_get_32 (abfd, data);
+  table->time = bfd_get_32 (abfd, data + 4);
+  table->major = bfd_get_16 (abfd, data + 8);
+  table->minor = bfd_get_16 (abfd, data + 10);
+  table->names.num_entries = bfd_get_16 (abfd, data + 12);
+  table->ids.num_entries = bfd_get_16 (abfd, data + 14);
+  table->entry = entry;
+
+  data += 16;
+
+  highest_data = rsrc_parse_entries (abfd, & table->names, TRUE, data,
+                                    datastart, data, dataend, rva_bias, table);
+  data += table->names.num_entries * 8;
+
+  highest_data = rsrc_parse_entries (abfd, & table->ids, FALSE, highest_data,
+                                    datastart, data, dataend, rva_bias, table);
+  data += table->ids.num_entries * 8;
+
+  return max (highest_data, data);
+}
+
+typedef struct rsrc_write_data
+{
+  bfd *      abfd;
+  bfd_byte * datastart;
+  bfd_byte * next_table;
+  bfd_byte * next_leaf;
+  bfd_byte * next_string;
+  bfd_byte * next_data;
+  bfd_vma    rva_bias;
+} rsrc_write_data; 
+  
+static void
+rsrc_write_string (rsrc_write_data * data,
+                  rsrc_string *     string)
+{
+  bfd_put_16 (data->abfd, string->len, data->next_string);
+  memcpy (data->next_string + 2, string->string, string->len * 2);
+  data->next_string += (string->len + 1) * 2;
+}
+
+static inline unsigned int
+rsrc_compute_rva (rsrc_write_data * data,
+                 bfd_byte *        addr)
+{
+  return (addr - data->datastart) + data->rva_bias;
+}
+
+static void
+rsrc_write_leaf (rsrc_write_data * data,
+                rsrc_leaf *       leaf)
+{
+  bfd_put_32 (data->abfd, rsrc_compute_rva (data, data->next_data), data->next_leaf);
+  bfd_put_32 (data->abfd, leaf->size,     data->next_leaf + 4);
+  bfd_put_32 (data->abfd, leaf->codepage, data->next_leaf + 8);
+  bfd_put_32 (data->abfd, 0 /*reserved*/, data->next_leaf + 12);
+  data->next_leaf += 16;
+
+  memcpy (data->next_data, leaf->data, leaf->size);
+  data->next_data += leaf->size;
+}
+
+static void rsrc_write_directory (rsrc_write_data *, rsrc_directory *);
+
+static void
+rsrc_write_entry (rsrc_write_data *  data,
+                 bfd_byte *         where,
+                 rsrc_entry *       entry)
+{
+  if (entry->is_name)
+    {
+      bfd_put_32 (data->abfd,
+                 SetHighBit (data->next_string - data->datastart),
+                 where);
+      rsrc_write_string (data, & entry->name_id.name);
+    }
+  else
+    bfd_put_32 (data->abfd, entry->name_id.id, where);
+
+  if (entry->is_dir)
+    {
+      bfd_put_32 (data->abfd,
+                 SetHighBit (data->next_table - data->datastart),
+                 where + 4);
+      rsrc_write_directory (data, entry->value.directory);
+    }
+  else
+    {
+      bfd_put_32 (data->abfd, data->next_leaf - data->datastart, where + 4);
+      rsrc_write_leaf (data, entry->value.leaf);
+    }
+}
+
+static void
+rsrc_write_directory (rsrc_write_data * data,
+                     rsrc_directory *  dir)
+{
+  rsrc_entry * entry;
+  unsigned int i;
+
+  bfd_put_32 (data->abfd, dir->characteristics, data->next_table);
+  bfd_put_32 (data->abfd, 0 /*dir->time*/, data->next_table + 4);
+  bfd_put_16 (data->abfd, dir->major, data->next_table + 8);
+  bfd_put_16 (data->abfd, dir->minor, data->next_table + 10);
+  bfd_put_16 (data->abfd, dir->names.num_entries, data->next_table + 12);
+  bfd_put_16 (data->abfd, dir->ids.num_entries, data->next_table + 14);
+
+  /* Compute where the entries and the next table will be placed.  */
+  bfd_byte * next_entry = data->next_table + 16;
+  data->next_table = next_entry + (dir->names.num_entries * 8) + (dir->ids.num_entries * 8);
+  bfd_byte * nt = data->next_table;
+  
+  /* Write the entries.  */
+  for (i = dir->names.num_entries, entry = dir->names.first_entry;
+       i > 0 && entry != NULL;
+       i--, entry = entry->next_entry)
+    {
+      rsrc_write_entry (data, next_entry, entry);
+      next_entry += 8;
+    }
+  BFD_ASSERT (i == 0);
+  BFD_ASSERT (entry == NULL);
+
+  for (i = dir->ids.num_entries, entry = dir->ids.first_entry;
+       i > 0 && entry != NULL;
+       i--, entry = entry->next_entry)
+    {
+      rsrc_write_entry (data, next_entry, entry);
+      next_entry += 8;
+    }
+  BFD_ASSERT (i == 0);
+  BFD_ASSERT (entry == NULL);
+  BFD_ASSERT (nt == next_entry);
+}
+
+#ifdef HAVE_WCHAR_H
+/* Return the length (number of units) of the first character in S,
+   putting its 'ucs4_t' representation in *PUC.  */
+
+static unsigned int
+u16_mbtouc (wchar_t * puc, const unsigned short * s, unsigned int n)
+{
+  unsigned short c = * s;
+
+  if (c < 0xd800 || c >= 0xe000)
+    {
+      *puc = c;
+      return 1;
+    }
+
+  if (c < 0xdc00)
+    {
+      if (n >= 2)
+        {
+          if (s[1] >= 0xdc00 && s[1] < 0xe000)
+            {
+              *puc = 0x10000 + ((c - 0xd800) << 10) + (s[1] - 0xdc00);
+              return 2;
+            }
+        }
+      else
+        {
+          /* Incomplete multibyte character.  */
+          *puc = 0xfffd;
+          return n;
+        }
+    }
+
+  /* Invalid multibyte character.  */
+  *puc = 0xfffd;
+  return 1;
+}
+#endif /* HAVE_WCHAR_H */
+
+/* Perform a comparison of two entries.  */
+static signed int
+rsrc_cmp (bfd_boolean is_name, rsrc_entry * a, rsrc_entry * b)
+{
+  if (! is_name)
+    return a->name_id.id - b->name_id.id; 
+
+  /* We have to perform a case insenstive, unicode string comparison...  */
+  int res;
+
+#ifdef __CYGWIN__
+  /* Under Cygwin unicode == UTF-16 == wchar_t.
+     FIXME: The same is true for MingGW - we should test for that too.  */
+  res = wcsncasecmp ((const wchar_t *) astring + 2, (const wchar_t *) bstring + 2, min (alen, blen));
+#elif defined HAVE_WCHAR_H
+  unsigned int  i;
+  bfd_byte *    astring = a->name_id.name.string;
+  unsigned int  alen    = a->name_id.name.len;
+  bfd_byte *    bstring = b->name_id.name.string;
+  unsigned int  blen    = b->name_id.name.len;
+
+  res = 0;
+  for (i = min (alen, blen); i--; astring += 2, bstring += 2)
+    {
+      wchar_t awc;
+      wchar_t bwc;
+
+      /* Convert UTF-16 unicode characters into wchar_t characters so
+        that we can then perform a case insensitive comparison.  */
+      int Alen = u16_mbtouc (& awc, (const unsigned short *) astring, 2);
+      int Blen = u16_mbtouc (& bwc, (const unsigned short *) bstring, 2);
+
+      if (Alen != Blen)
+       return Alen - Blen;
+      res = wcsncasecmp (& awc, & bwc, 1);
+      if (res)
+       break;
+    }
+#else
+  res = memcmp (astring + 2, bstring + 2, min (alen, blen) * 2);
+#endif
+
+  if (res == 0)
+    res = alen - blen;
+
+  return res;
+}
+
+static void
+rsrc_print_name (char * buffer, rsrc_string string)
+{
+  unsigned int  i;
+  bfd_byte *    name = string.string;
+
+  for (i = string.len; i--; name += 2)
+    sprintf (buffer + strlen (buffer), "%.1s", name);
+}
+
+static const char *
+rsrc_resource_name (rsrc_entry * entry, rsrc_directory * dir)
+{
+  static char buffer [256];
+  bfd_boolean is_string = FALSE;
+
+  buffer[0] = 0;
+
+  if (dir != NULL && dir->entry != NULL && dir->entry->parent != NULL && dir->entry->parent->entry != NULL)
+    {
+      strcpy (buffer, "type: ");
+      if (dir->entry->parent->entry->is_name)
+       rsrc_print_name (buffer + strlen (buffer), dir->entry->parent->entry->name_id.name);
+      else
+       {
+         unsigned int id = dir->entry->parent->entry->name_id.id;
+
+         sprintf (buffer + strlen (buffer), "%x", id);
+         switch (id)
+           {
+           case 1: strcat (buffer, " (CURSOR)"); break;
+           case 2: strcat (buffer, " (BITMAP)"); break;
+           case 3: strcat (buffer, " (ICON)"); break;
+            case 4: strcat (buffer, " (MENU)"); break;
+           case 5: strcat (buffer, " (DIALOG)"); break;
+           case 6: strcat (buffer, " (STRING)"); is_string = TRUE; break;
+           case 7: strcat (buffer, " (FONTDIR)"); break;
+           case 8: strcat (buffer, " (FONT)"); break;
+           case 9: strcat (buffer, " (ACCELERATOR)"); break;
+           case 10: strcat (buffer, " (RCDATA)"); break;
+           case 11: strcat (buffer, " (MESSAGETABLE)"); break;
+           case 12: strcat (buffer, " (GROUP_CURSOR)"); break;
+           case 14: strcat (buffer, " (GROUP_ICON)"); break;
+           case 16: strcat (buffer, " (VERSION)"); break;
+           case 17: strcat (buffer, " (DLGINCLUDE)"); break;
+           case 19: strcat (buffer, " (PLUGPLAY)"); break;
+           case 20: strcat (buffer, " (VXD)"); break;
+           case 21: strcat (buffer, " (ANICURSOR)"); break;
+           case 22: strcat (buffer, " (ANIICON)"); break;
+           case 23: strcat (buffer, " (HTML)"); break;
+           case 24: strcat (buffer, " (MANIFEST)"); break;
+           case 240: strcat (buffer, " (DLGINIT)"); break;
+           case 241: strcat (buffer, " (TOOLBAR)"); break;
+           }
+       }
+    }
+
+  if (dir != NULL && dir->entry != NULL)
+    {
+      strcat (buffer, " name: ");
+      if (dir->entry->is_name)
+       rsrc_print_name (buffer + strlen (buffer), dir->entry->name_id.name);
+      else
+       {
+         unsigned int id = dir->entry->name_id.id;
+
+         sprintf (buffer + strlen (buffer), "%x", id);
+
+         if (is_string)
+           sprintf (buffer + strlen (buffer), " (resource id range: %d - %d)",
+                    (id - 1) << 4, (id << 4) - 1);
+       }
+    }
+
+  if (entry != NULL)
+    {
+      strcat (buffer, " lang: ");
+
+      if (entry->is_name)
+       rsrc_print_name (buffer + strlen (buffer), entry->name_id.name);
+      else
+       sprintf (buffer + strlen (buffer), "%x", entry->name_id.id);
+    }
+
+  return buffer;
+}
+
+/* *sigh* Windows resource strings are special.  Only the top 28-bits of
+   their ID is stored in the NAME entry.  The bottom four bits are used as
+   an index into unicode string table that makes up the data of the leaf.
+   So identical type-name-lang string resources may not actually be
+   identical at all.
+
+   This function is called when we have detected two string resources with
+   match top-28-bit IDs.  We have to scan the string tables inside the leaves
+   and discover if there are any real collisions.  If there are then we report
+   them and return FALSE.  Otherwise we copy any strings from B into A and then
+   return TRUE.  */
+
+static bfd_boolean
+rsrc_merge_string_entries (rsrc_entry * a ATTRIBUTE_UNUSED,
+                          rsrc_entry * b ATTRIBUTE_UNUSED)
+{
+  unsigned int copy_needed = 0;
+  unsigned int i;
+
+  /* Step one: Find out what we have to do.  */
+  BFD_ASSERT (! a->is_dir);
+  bfd_byte * astring = a->value.leaf->data;
+
+  BFD_ASSERT (! b->is_dir);
+  bfd_byte * bstring = b->value.leaf->data;
+
+  for (i = 0; i < 16; i++)
+    {
+      unsigned int alen = astring[0] + (astring[1] << 8);
+      unsigned int blen = bstring[0] + (bstring[1] << 8);
+
+      if (alen == 0)
+       {
+         copy_needed += blen * 2;
+       }
+      else if (blen == 0)
+       ;
+      else if (alen != blen)
+       /* FIXME: Should we continue the loop in order to report other duplicates ?  */
+       break;
+      /* alen == blen != 0.  We might have two identical strings.  If so we
+        can ignore the second one.  There is no need for wchar_t vs UTF-16
+        theatrics here - we are only interested in (case sensitive) equality.  */
+      else if (memcmp (astring + 2, bstring + 2, alen * 2) != 0)
+       break;
+
+      astring += (alen + 1) * 2;
+      bstring += (blen + 1) * 2;
+    }
+
+  if (i != 16)
+    {
+      if (a->parent != NULL
+         && a->parent->entry != NULL
+         && a->parent->entry->is_name == FALSE)
+       _bfd_error_handler (_(".rsrc merge failure: duplicate string resource: %d"),
+                           ((a->parent->entry->name_id.id - 1) << 4) + i);
+      return FALSE;
+    }
+
+  if (copy_needed == 0)
+    return TRUE;
+
+  /* If we reach here then A and B must both have non-colliding strings.
+     (We never get string resources with fully empty string tables).
+     We need to allocate an extra COPY_NEEDED bytes in A and then bring
+     in B's strings.  */
+  bfd_byte * new_data = bfd_malloc (a->value.leaf->size + copy_needed);
+  if (new_data == NULL)
+    return FALSE;
+
+  bfd_byte * nstring = new_data;
+  astring = a->value.leaf->data;
+  bstring = b->value.leaf->data;
+
+  for (i = 0; i < 16; i++)
+    {
+      unsigned int alen = astring[0] + (astring[1] << 8);
+      unsigned int blen = bstring[0] + (bstring[1] << 8);
+
+      if (alen != 0)
+       {
+         memcpy (nstring, astring, (alen + 1) * 2);
+         nstring += (alen + 1) * 2;
+       }
+      else if (blen != 0)
+       {
+         memcpy (nstring, bstring, (blen + 1) * 2);
+         nstring += (blen + 1) * 2;
+       }
+      else
+       {
+         * nstring++ = 0;
+         * nstring++ = 0;
+       }
+      
+      astring += (alen + 1) * 2;
+      bstring += (blen + 1) * 2;
+    }
+
+  BFD_ASSERT (nstring - new_data == (signed) (a->value.leaf->size + copy_needed));
+  
+  free (a->value.leaf->data);
+  a->value.leaf->data = new_data;
+  a->value.leaf->size += copy_needed;
+
+  return TRUE;
+}
+
+static void rsrc_merge (rsrc_entry *, rsrc_entry *);
+
+/* Sort the entries in given part of the directory.
+   We use an old fashioned bubble sort because we are dealing
+   with lists and we want to handle matches specially.  */      
+
+static void
+rsrc_sort_entries (rsrc_dir_chain *  chain,
+                  bfd_boolean       is_name,
+                  rsrc_directory *  dir)
+{
+  rsrc_entry * entry;
+  rsrc_entry * next;
+  rsrc_entry ** points_to_entry;
+  bfd_boolean swapped;
+
+  if (chain->num_entries < 2)
+    return;
+
+  do
+    {
+      swapped = FALSE;
+      points_to_entry = & chain->first_entry;
+      entry = * points_to_entry;
+      next  = entry->next_entry;
+
+      do
+       {
+         signed int cmp = rsrc_cmp (is_name, entry, next);
+
+         if (cmp > 0)
+           {
+             entry->next_entry = next->next_entry;
+             next->next_entry = entry;
+             * points_to_entry = next;
+             points_to_entry = & next->next_entry;
+             next = entry->next_entry;
+             swapped = TRUE;
+           }
+         else if (cmp == 0)
+           {
+             if (entry->is_dir && next->is_dir)
+               {
+                 /* When we encounter identical directory entries we have to
+                    merge them together.  The exception to this rule is for
+                    resource manifests - there can only be one of these,
+                    even if they differ in language.  Zero-language manifests
+                    are assumed to be default manifests (provided by the
+                    cygwin build system) and these can be silently dropped,
+                    unless that would reduce the number of manifests to zero.
+                    There should only ever be one non-zero lang manifest -
+                    if there are more it is an error.  A non-zero lang
+                    manifest takes precedence over a default manifest.  */
+                 if (entry->is_name == FALSE
+                     && entry->name_id.id == 1
+                     && dir != NULL
+                     && dir->entry != NULL
+                     && dir->entry->is_name == FALSE
+                     && dir->entry->name_id.id == 0x18)
+                   {
+                     if (next->value.directory->names.num_entries == 0
+                         && next->value.directory->ids.num_entries == 1
+                         && next->value.directory->ids.first_entry->is_name == FALSE
+                         && next->value.directory->ids.first_entry->name_id.id == 0)
+                       /* Fall through so that NEXT is dropped.  */
+                       ;
+                     else if (entry->value.directory->names.num_entries == 0
+                              && entry->value.directory->ids.num_entries == 1
+                              && entry->value.directory->ids.first_entry->is_name == FALSE
+                              && entry->value.directory->ids.first_entry->name_id.id == 0)
+                       {
+                         /* Swap ENTRY and NEXT.  Then fall through so that the old ENTRY is dropped.  */
+                         entry->next_entry = next->next_entry;
+                         next->next_entry = entry;
+                         * points_to_entry = next;
+                         points_to_entry = & next->next_entry;
+                         next = entry->next_entry;
+                         swapped = TRUE;
+                       }
+                     else
+                       {
+                         _bfd_error_handler (_(".rsrc merge failure: multiple non-default manifests"));
+                         bfd_set_error (bfd_error_file_truncated);
+                         return;
+                       }
+                     
+                     /* Unhook NEXT from the chain.  */
+                     /* FIXME: memory loss here.  */
+                     entry->next_entry = next->next_entry;
+                     chain->num_entries --;
+                     if (chain->num_entries < 2)
+                       return;
+                     next = next->next_entry;
+                   }
+                 else
+                   rsrc_merge (entry, next);
+               }
+             else if (entry->is_dir != next->is_dir)
+               {
+                 _bfd_error_handler (_(".rsrc merge failure: a directory matches a leaf"));
+                 bfd_set_error (bfd_error_file_truncated);
+                 return;
+               }
+             else
+               {
+                 /* Otherwise with identical leaves we issue an error
+                    message - because there should never be duplicates.
+                    The exception is Type 18/Name 1/Lang 0 which is the
+                    defaul manifest - this can just be dropped.  */
+                 if (entry->is_name == FALSE
+                     && entry->name_id.id == 0
+                     && dir != NULL
+                     && dir->entry != NULL
+                     && dir->entry->is_name == FALSE
+                     && dir->entry->name_id.id == 1
+                     && dir->entry->parent != NULL
+                     && dir->entry->parent->entry != NULL
+                     && dir->entry->parent->entry->is_name == FALSE
+                     && dir->entry->parent->entry->name_id.id == 0x18 /* RT_MANIFEST */)
+                   ;
+                 else if (dir != NULL
+                          && dir->entry != NULL
+                          && dir->entry->parent != NULL
+                          && dir->entry->parent->entry != NULL
+                          && dir->entry->parent->entry->is_name == FALSE
+                          && dir->entry->parent->entry->name_id.id == 0x6 /* RT_STRING */)
+                   {
+                     /* Strings need special handling.  */
+                     if (! rsrc_merge_string_entries (entry, next))
+                       {
+                         /* _bfd_error_handler should have been called inside merge_strings.  */
+                         bfd_set_error (bfd_error_file_truncated);
+                         return;
+                       }
+                   }
+                 else
+                   {
+                     if (dir == NULL
+                         || dir->entry == NULL
+                         || dir->entry->parent == NULL
+                         || dir->entry->parent->entry == NULL)
+                       _bfd_error_handler (_(".rsrc merge failure: duplicate leaf"));
+                     else
+                       _bfd_error_handler (_(".rsrc merge failure: duplicate leaf: %s"),
+                                           rsrc_resource_name (entry, dir));
+                     bfd_set_error (bfd_error_file_truncated);
+                     return;
+                   }
+               }
+
+             /* Unhook NEXT from the chain.  */
+             entry->next_entry = next->next_entry;
+             chain->num_entries --;
+             if (chain->num_entries < 2)
+               return;
+             next = next->next_entry;
+           }
+         else
+           {
+             points_to_entry = & entry->next_entry;
+             entry = next;
+             next = next->next_entry;
+           }
+       }
+      while (next);
+
+      chain->last_entry = entry;
+    }
+  while (swapped);
+}
+
+/* Attach B's chain onto A.  */
+static void
+rsrc_attach_chain (struct rsrc_dir_chain * achain, struct rsrc_dir_chain * bchain)
+{
+  if (bchain->num_entries == 0)
+    return;
+
+  achain->num_entries += bchain->num_entries;
+
+  if (achain->first_entry == NULL)
+    {
+      achain->first_entry = bchain->first_entry;
+      achain->last_entry  = bchain->last_entry;
+    }
+  else
+    {
+      achain->last_entry->next_entry = bchain->first_entry;
+      achain->last_entry = bchain->last_entry;
+    }
+  
+  bchain->num_entries = 0;
+  bchain->first_entry = bchain->last_entry = NULL;
+}
+
+static void
+rsrc_merge (struct rsrc_entry * a, struct rsrc_entry * b)
+{
+  BFD_ASSERT (a->is_dir);
+  BFD_ASSERT (b->is_dir);
+
+  rsrc_directory * adir = a->value.directory;
+  rsrc_directory * bdir = b->value.directory;
+  
+  if (adir->characteristics != bdir->characteristics)
+    {
+      _bfd_error_handler (_(".rsrc merge failure: dirs with differing characteristics\n"));
+      bfd_set_error (bfd_error_file_truncated);
+      return;
+    }
+  
+  if (adir->major != bdir->major || adir->minor != bdir->minor)
+    {
+      _bfd_error_handler (_(".rsrc merge failure: differing directory versions\n"));
+      bfd_set_error (bfd_error_file_truncated);
+      return;
+    }
+
+  /* Attach B's name chain to A.  */
+  rsrc_attach_chain (& adir->names, & bdir->names);
+
+  /* Attach B's ID chain to A.  */
+  rsrc_attach_chain (& adir->ids, & bdir->ids);
+
+  /* Now sort A's entries.  */
+  rsrc_sort_entries (& adir->names, TRUE, adir);
+  rsrc_sort_entries (& adir->ids, FALSE, adir);
+}
+
+/* Check the .rsrc section.  If it contains multiple concatenated
+   resources then we must merge them properly.  Otherwise Windows
+   will ignore all but the first set.  */
+
+static void
+rsrc_process_section (bfd * abfd,
+                     struct coff_final_link_info * pfinfo)
+{
+  rsrc_directory new_table;
+  bfd_size_type  size;
+  asection *     sec;
+
+  new_table.names.num_entries = 0;
+  new_table.ids.num_entries = 0;
+  
+  sec = bfd_get_section_by_name (abfd, ".rsrc");
+  if (sec == NULL || (size = sec->rawsize) == 0)
+    return;
+
+  pe_data_type * pe = pe_data (abfd);
+  if (pe == NULL)
+    return;
+
+  bfd_vma rva_bias;
+  rva_bias = sec->vma - pe->pe_opthdr.ImageBase;
+
+  bfd_byte * data = bfd_malloc (size);
+  if (data == NULL)
+    return;
+
+  bfd_byte * datastart = data;
+
+  if (! bfd_get_section_contents (abfd, sec, data, 0, size))
+    goto end;
+
+  /* Step one: Walk the section, computing the size of the tables,
+     leaves and data and decide if we need to do anything.  */
+  bfd_byte *    dataend   = data + size;
+  unsigned int  num_resource_sets = 0;
+  sizeof_leaves = sizeof_strings = sizeof_tables_and_entries = 0;
+
+  while (data < dataend)
+    {
+      bfd_byte * p = data;
+
+      data = rsrc_count_directory (abfd, data, data, dataend, rva_bias);
+      if (data > dataend)
+       {
+         /* Corrupted .rsrc section - cannot merge.  */
+         _bfd_error_handler (_("%s: .rsrc merge failure: corrupt .rsrc section"),
+                             bfd_get_filename (abfd));
+         bfd_set_error (bfd_error_file_truncated);
+         goto end;
+       }
+
+      /* Align the data pointer - we assume 1^2 alignment.  */
+      data = (bfd_byte *) (((long) (data + 3)) & ~ 3);
+      rva_bias += data - p;
+
+      if (data == (dataend - 4))
+       data = dataend;
+
+      ++ num_resource_sets;
+    }
+
+  if (num_resource_sets < 2)
+    /* No merging necessary.  */
+    goto end;
+
+  /* Step two: Walk the data again, building trees of the resources.  */
+  data = datastart;
+  rva_bias = sec->vma - pe->pe_opthdr.ImageBase;
+
+  rsrc_directory * type_tables = bfd_malloc (num_resource_sets * sizeof * type_tables);
+  if (type_tables == NULL)
+    goto end;
+
+  unsigned int index = 0;
+  while (data < dataend)
+    {
+      bfd_byte * p = data;
+
+      data = rsrc_parse_directory (abfd, type_tables + index, data, data, dataend,
+                                  rva_bias, NULL);
+      data = (bfd_byte *) (((long) (data + 3)) & ~ 3);
+      rva_bias += data - p;
+      if (data == (dataend - 4))
+       data = dataend;
+      index ++;
+    }
+  BFD_ASSERT (index == num_resource_sets);
+  
+  /* Step three: Merge the top level tables (there can be only one).
+     
+     We must ensure that the merged entries are in ascending order.
+     
+     We also thread the top level table entries from the old tree onto
+     the new table, so that they can be pulled off later.  */
+
+  /* FIXME: Should we verify that all type tables are the same ?  */
+  new_table.characteristics = type_tables[0].characteristics;
+  new_table.time            = type_tables[0].time;
+  new_table.major           = type_tables[0].major;
+  new_table.minor           = type_tables[0].minor;
+
+  /* Chain the NAME entries onto the table.  */
+  new_table.names.first_entry = NULL;
+  new_table.names.last_entry = NULL;
+
+  for (index = 0; index < num_resource_sets; index++)
+    rsrc_attach_chain (& new_table.names, & type_tables[index].names);
+
+  rsrc_sort_entries (& new_table.names, TRUE, & new_table);
+  
+  /* Chain the ID entries onto the table.  */
+  new_table.ids.first_entry = NULL;
+  new_table.ids.last_entry = NULL;
+
+  for (index = 0; index < num_resource_sets; index++)
+    rsrc_attach_chain (& new_table.ids, & type_tables[index].ids);
+
+  rsrc_sort_entries (& new_table.ids, FALSE, & new_table);
+
+  /* Step four: Create new contents for the .rsrc section.  */
+  bfd_byte * new_data = bfd_malloc (size);
+  if (new_data == NULL)
+    goto end;
+
+  rsrc_write_data  write_data;
+
+  write_data.abfd        = abfd;
+  write_data.datastart   = new_data;
+  write_data.next_table  = new_data;
+  write_data.next_leaf   = new_data + sizeof_tables_and_entries;
+  write_data.next_string = write_data.next_leaf + sizeof_leaves;
+  write_data.next_data   = write_data.next_string + sizeof_strings;
+
+  write_data.rva_bias = sec->vma - pe->pe_opthdr.ImageBase;
+
+  rsrc_write_directory (& write_data, & new_table);
+
+  /* Step five: Replace the old contents with the new.
+     We recompute the size as we may have lost entries due to mergeing.  */
+  size = ((write_data.next_data - new_data) + 3) & ~3;
+  bfd_set_section_contents (pfinfo->output_bfd, sec, new_data, 0, size);
+  sec->size = sec->rawsize = size;
+  
+ end:
+  /* FIXME: Free the resource tree, if we have one.  */
+  free (datastart);
+}
 
 /* Handle the .idata section and other things that need symbol table
    access.  */
@@ -2689,6 +3803,8 @@ _bfd_XXi_final_link_postscript (bfd * abfd, struct coff_final_link_info *pfinfo)
   }
 #endif
 
+  rsrc_process_section (abfd, pfinfo);
+
   /* If we couldn't find idata$2, we either have an excessively
      trivial program or are in DEEP trouble; we have to assume trivial
      program....  */