| /* simple-object-coff.c -- routines to manipulate COFF object files. | 
 |    Copyright (C) 2010-2023 Free Software Foundation, Inc. | 
 |    Written by Ian Lance Taylor, Google. | 
 |  | 
 | 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, 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, 51 Franklin Street - Fifth Floor, | 
 | Boston, MA 02110-1301, USA.  */ | 
 |  | 
 | #include "config.h" | 
 | #include "libiberty.h" | 
 | #include "simple-object.h" | 
 |  | 
 | #include <errno.h> | 
 | #include <stddef.h> | 
 |  | 
 | #ifdef HAVE_STDLIB_H | 
 | #include <stdlib.h> | 
 | #endif | 
 |  | 
 | #ifdef HAVE_STDINT_H | 
 | #include <stdint.h> | 
 | #endif | 
 |  | 
 | #ifdef HAVE_STRING_H | 
 | #include <string.h> | 
 | #endif | 
 |  | 
 | #ifdef HAVE_INTTYPES_H | 
 | #include <inttypes.h> | 
 | #endif | 
 |  | 
 | #include "simple-object-common.h" | 
 |  | 
 | /* COFF structures and constants.  */ | 
 |  | 
 | /* COFF file header.  */ | 
 |  | 
 | struct external_filehdr | 
 | { | 
 |   unsigned char f_magic[2];	/* magic number			*/ | 
 |   unsigned char f_nscns[2];	/* number of sections		*/ | 
 |   unsigned char f_timdat[4];	/* time & date stamp		*/ | 
 |   unsigned char f_symptr[4];	/* file pointer to symtab	*/ | 
 |   unsigned char f_nsyms[4];	/* number of symtab entries	*/ | 
 |   unsigned char f_opthdr[2];	/* sizeof(optional hdr)		*/ | 
 |   unsigned char f_flags[2];	/* flags			*/ | 
 | }; | 
 |  | 
 | /* Bits for filehdr f_flags field.  */ | 
 |  | 
 | #define F_EXEC			(0x0002) | 
 | #define IMAGE_FILE_SYSTEM	(0x1000) | 
 | #define IMAGE_FILE_DLL		(0x2000) | 
 |  | 
 | /* COFF section header.  */ | 
 |  | 
 | struct external_scnhdr | 
 | { | 
 |   unsigned char s_name[8];	/* section name				*/ | 
 |   unsigned char s_paddr[4];	/* physical address, aliased s_nlib 	*/ | 
 |   unsigned char s_vaddr[4];	/* virtual address			*/ | 
 |   unsigned char s_size[4];	/* section size				*/ | 
 |   unsigned char s_scnptr[4];	/* file ptr to raw data for section 	*/ | 
 |   unsigned char s_relptr[4];	/* file ptr to relocation		*/ | 
 |   unsigned char s_lnnoptr[4];	/* file ptr to line numbers		*/ | 
 |   unsigned char s_nreloc[2];	/* number of relocation entries		*/ | 
 |   unsigned char s_nlnno[2];	/* number of line number entries	*/ | 
 |   unsigned char s_flags[4];	/* flags				*/ | 
 | }; | 
 |  | 
 | /* The length of the s_name field in struct external_scnhdr.  */ | 
 |  | 
 | #define SCNNMLEN (8) | 
 |  | 
 | /* Bits for scnhdr s_flags field.  This includes some bits defined | 
 |    only for PE.  This may need to be moved into coff_magic.  */ | 
 |  | 
 | #define STYP_DATA			(1 << 6) | 
 | #define IMAGE_SCN_MEM_DISCARDABLE	(1 << 25) | 
 | #define IMAGE_SCN_MEM_SHARED		(1 << 28) | 
 | #define IMAGE_SCN_MEM_READ		(1 << 30) | 
 |  | 
 | #define IMAGE_SCN_ALIGN_POWER_BIT_POS	     20 | 
 | #define IMAGE_SCN_ALIGN_POWER_CONST(val)     \ | 
 |   (((val) + 1) << IMAGE_SCN_ALIGN_POWER_BIT_POS) | 
 |  | 
 | /* COFF symbol table entry.  */ | 
 |  | 
 | #define E_SYMNMLEN	8	/* # characters in a symbol name	*/ | 
 |  | 
 | struct external_syment | 
 | { | 
 |   union | 
 |   { | 
 |     unsigned char e_name[E_SYMNMLEN]; | 
 |  | 
 |     struct | 
 |     { | 
 |       unsigned char e_zeroes[4]; | 
 |       unsigned char e_offset[4]; | 
 |     } e; | 
 |   } e; | 
 |  | 
 |   unsigned char e_value[4]; | 
 |   unsigned char e_scnum[2]; | 
 |   unsigned char e_type[2]; | 
 |   unsigned char e_sclass[1]; | 
 |   unsigned char e_numaux[1]; | 
 | }; | 
 |  | 
 | /* Length allowed for filename in aux sym format 4.  */ | 
 |  | 
 | #define E_FILNMLEN	18 | 
 |  | 
 | /* Omits x_sym and other unused variants.  */ | 
 |  | 
 | union external_auxent | 
 | { | 
 |   /* Aux sym format 4: file.  */ | 
 |   union | 
 |   { | 
 |     char x_fname[E_FILNMLEN]; | 
 |     struct | 
 |     { | 
 |       unsigned char x_zeroes[4]; | 
 |       unsigned char x_offset[4]; | 
 |     } x_n; | 
 |   } x_file; | 
 |   /* Aux sym format 5: section.  */ | 
 |   struct | 
 |   { | 
 |     unsigned char x_scnlen[4];		/* section length		*/ | 
 |     unsigned char x_nreloc[2];		/* # relocation entries		*/ | 
 |     unsigned char x_nlinno[2];		/* # line numbers		*/ | 
 |     unsigned char x_checksum[4];	/* section COMDAT checksum	*/ | 
 |     unsigned char x_associated[2];	/* COMDAT assoc section index	*/ | 
 |     unsigned char x_comdat[1];		/* COMDAT selection number	*/ | 
 |   } x_scn; | 
 | }; | 
 |  | 
 | /* Symbol-related constants.  */ | 
 |  | 
 | #define IMAGE_SYM_DEBUG		(-2) | 
 | #define IMAGE_SYM_TYPE_NULL	(0) | 
 | #define IMAGE_SYM_DTYPE_NULL	(0) | 
 | #define IMAGE_SYM_CLASS_STATIC	(3) | 
 | #define IMAGE_SYM_CLASS_FILE	(103) | 
 |  | 
 | #define IMAGE_SYM_TYPE \ | 
 |   ((IMAGE_SYM_DTYPE_NULL << 4) | IMAGE_SYM_TYPE_NULL) | 
 |  | 
 | /* Private data for an simple_object_read.  */ | 
 |  | 
 | struct simple_object_coff_read | 
 | { | 
 |   /* Magic number.  */ | 
 |   unsigned short magic; | 
 |   /* Whether the file is big-endian.  */ | 
 |   unsigned char is_big_endian; | 
 |   /* Number of sections.  */ | 
 |   unsigned short nscns; | 
 |   /* File offset of symbol table.  */ | 
 |   off_t symptr; | 
 |   /* Number of symbol table entries.  */ | 
 |   unsigned int nsyms; | 
 |   /* Flags.  */ | 
 |   unsigned short flags; | 
 |   /* Offset of section headers in file.  */ | 
 |   off_t scnhdr_offset; | 
 | }; | 
 |  | 
 | /* Private data for an simple_object_attributes.  */ | 
 |  | 
 | struct simple_object_coff_attributes | 
 | { | 
 |   /* Magic number.  */ | 
 |   unsigned short magic; | 
 |   /* Whether the file is big-endian.  */ | 
 |   unsigned char is_big_endian; | 
 |   /* Flags.  */ | 
 |   unsigned short flags; | 
 | }; | 
 |  | 
 | /* There is no magic number which indicates a COFF file as opposed to | 
 |    any other sort of file.  Instead, each COFF file starts with a | 
 |    two-byte magic number which also indicates the type of the target. | 
 |    This struct holds a magic number as well as characteristics of that | 
 |    COFF format.  */ | 
 |  | 
 | struct coff_magic_struct | 
 | { | 
 |   /* Magic number.  */ | 
 |   unsigned short magic; | 
 |   /* Whether this magic number is for a big-endian file.  */ | 
 |   unsigned char is_big_endian; | 
 |   /* Flag bits, in the f_flags fields, which indicates that this file | 
 |      is not a relocatable object file.  There is no flag which | 
 |      specifically indicates a relocatable object file, it is only | 
 |      implied by the absence of these flags.  */ | 
 |   unsigned short non_object_flags; | 
 | }; | 
 |  | 
 | /* This is a list of the COFF magic numbers which we recognize, namely | 
 |    the ones used on Windows.  More can be added as needed.  */ | 
 |  | 
 | static const struct coff_magic_struct coff_magic[] = | 
 | { | 
 |   /* i386.  */ | 
 |   { 0x14c, 0, F_EXEC | IMAGE_FILE_SYSTEM | IMAGE_FILE_DLL }, | 
 |   /* x86_64.  */ | 
 |   { 0x8664, 0, F_EXEC | IMAGE_FILE_SYSTEM | IMAGE_FILE_DLL } | 
 | }; | 
 |  | 
 | /* See if we have a COFF file.  */ | 
 |  | 
 | static void * | 
 | simple_object_coff_match (unsigned char header[SIMPLE_OBJECT_MATCH_HEADER_LEN], | 
 | 			  int descriptor, off_t offset, | 
 | 			  const char *segment_name ATTRIBUTE_UNUSED, | 
 | 			  const char **errmsg, int *err) | 
 | { | 
 |   size_t c; | 
 |   unsigned short magic_big; | 
 |   unsigned short magic_little; | 
 |   unsigned short magic; | 
 |   size_t i; | 
 |   int is_big_endian; | 
 |   unsigned short (*fetch_16) (const unsigned char *); | 
 |   unsigned int (*fetch_32) (const unsigned char *); | 
 |   unsigned char hdrbuf[sizeof (struct external_filehdr)]; | 
 |   unsigned short flags; | 
 |   struct simple_object_coff_read *ocr; | 
 |  | 
 |   c = sizeof (coff_magic) / sizeof (coff_magic[0]); | 
 |   magic_big = simple_object_fetch_big_16 (header); | 
 |   magic_little = simple_object_fetch_little_16 (header); | 
 |   for (i = 0; i < c; ++i) | 
 |     { | 
 |       if (coff_magic[i].is_big_endian | 
 | 	  ? coff_magic[i].magic == magic_big | 
 | 	  : coff_magic[i].magic == magic_little) | 
 | 	break; | 
 |     } | 
 |   if (i >= c) | 
 |     { | 
 |       *errmsg = NULL; | 
 |       *err = 0; | 
 |       return NULL; | 
 |     } | 
 |   is_big_endian = coff_magic[i].is_big_endian; | 
 |  | 
 |   magic = is_big_endian ? magic_big : magic_little; | 
 |   fetch_16 = (is_big_endian | 
 | 	      ? simple_object_fetch_big_16 | 
 | 	      : simple_object_fetch_little_16); | 
 |   fetch_32 = (is_big_endian | 
 | 	      ? simple_object_fetch_big_32 | 
 | 	      : simple_object_fetch_little_32); | 
 |  | 
 |   if (!simple_object_internal_read (descriptor, offset, hdrbuf, sizeof hdrbuf, | 
 | 				    errmsg, err)) | 
 |     return NULL; | 
 |  | 
 |   flags = fetch_16 (hdrbuf + offsetof (struct external_filehdr, f_flags)); | 
 |   if ((flags & coff_magic[i].non_object_flags) != 0) | 
 |     { | 
 |       *errmsg = "not relocatable object file"; | 
 |       *err = 0; | 
 |       return NULL; | 
 |     } | 
 |  | 
 |   ocr = XNEW (struct simple_object_coff_read); | 
 |   ocr->magic = magic; | 
 |   ocr->is_big_endian = is_big_endian; | 
 |   ocr->nscns = fetch_16 (hdrbuf + offsetof (struct external_filehdr, f_nscns)); | 
 |   ocr->symptr = fetch_32 (hdrbuf | 
 | 			  + offsetof (struct external_filehdr, f_symptr)); | 
 |   ocr->nsyms = fetch_32 (hdrbuf + offsetof (struct external_filehdr, f_nsyms)); | 
 |   ocr->flags = flags; | 
 |   ocr->scnhdr_offset = (sizeof (struct external_filehdr) | 
 | 			+ fetch_16 (hdrbuf + offsetof (struct external_filehdr, | 
 | 						       f_opthdr))); | 
 |  | 
 |   return (void *) ocr; | 
 | } | 
 |  | 
 | /* Read the string table in a COFF file.  */ | 
 |  | 
 | static char * | 
 | simple_object_coff_read_strtab (simple_object_read *sobj, size_t *strtab_size, | 
 | 				const char **errmsg, int *err) | 
 | { | 
 |   struct simple_object_coff_read *ocr = | 
 |     (struct simple_object_coff_read *) sobj->data; | 
 |   off_t strtab_offset; | 
 |   unsigned char strsizebuf[4]; | 
 |   size_t strsize; | 
 |   char *strtab; | 
 |  | 
 |   strtab_offset = sobj->offset + ocr->symptr | 
 | 		  + ocr->nsyms * sizeof (struct external_syment); | 
 |   if (!simple_object_internal_read (sobj->descriptor, strtab_offset, | 
 | 				    strsizebuf, 4, errmsg, err)) | 
 |     return NULL; | 
 |   strsize = (ocr->is_big_endian | 
 | 	     ? simple_object_fetch_big_32 (strsizebuf) | 
 | 	     : simple_object_fetch_little_32 (strsizebuf)); | 
 |   strtab = XNEWVEC (char, strsize); | 
 |   if (!simple_object_internal_read (sobj->descriptor, strtab_offset, | 
 | 				    (unsigned char *) strtab, strsize, errmsg, | 
 | 				    err)) | 
 |     { | 
 |       XDELETEVEC (strtab); | 
 |       return NULL; | 
 |     } | 
 |   *strtab_size = strsize; | 
 |   return strtab; | 
 | } | 
 |  | 
 | /* Find all sections in a COFF file.  */ | 
 |  | 
 | static const char * | 
 | simple_object_coff_find_sections (simple_object_read *sobj, | 
 | 				  int (*pfn) (void *, const char *, | 
 | 					      off_t offset, off_t length), | 
 | 				  void *data, | 
 | 				  int *err) | 
 | { | 
 |   struct simple_object_coff_read *ocr = | 
 |     (struct simple_object_coff_read *) sobj->data; | 
 |   size_t scnhdr_size; | 
 |   unsigned char *scnbuf; | 
 |   const char *errmsg; | 
 |   unsigned int (*fetch_32) (const unsigned char *); | 
 |   unsigned int nscns; | 
 |   char *strtab; | 
 |   size_t strtab_size; | 
 |   unsigned int i; | 
 |  | 
 |   scnhdr_size = sizeof (struct external_scnhdr); | 
 |   scnbuf = XNEWVEC (unsigned char, scnhdr_size * ocr->nscns); | 
 |   if (!simple_object_internal_read (sobj->descriptor, | 
 | 				    sobj->offset + ocr->scnhdr_offset, | 
 | 				    scnbuf, scnhdr_size * ocr->nscns, &errmsg, | 
 | 				    err)) | 
 |     { | 
 |       XDELETEVEC (scnbuf); | 
 |       return errmsg; | 
 |     } | 
 |  | 
 |   fetch_32 = (ocr->is_big_endian | 
 | 	      ? simple_object_fetch_big_32 | 
 | 	      : simple_object_fetch_little_32); | 
 |  | 
 |   nscns = ocr->nscns; | 
 |   strtab = NULL; | 
 |   strtab_size = 0; | 
 |   for (i = 0; i < nscns; ++i) | 
 |     { | 
 |       unsigned char *scnhdr; | 
 |       unsigned char *scnname; | 
 |       char namebuf[SCNNMLEN + 1]; | 
 |       char *name; | 
 |       off_t scnptr; | 
 |       unsigned int size; | 
 |  | 
 |       scnhdr = scnbuf + i * scnhdr_size; | 
 |       scnname = scnhdr + offsetof (struct external_scnhdr, s_name); | 
 |       memcpy (namebuf, scnname, SCNNMLEN); | 
 |       namebuf[SCNNMLEN] = '\0'; | 
 |       name = &namebuf[0]; | 
 |       if (namebuf[0] == '/') | 
 | 	{ | 
 | 	  size_t strindex; | 
 | 	  char *end; | 
 |  | 
 | 	  strindex = strtol (namebuf + 1, &end, 10); | 
 | 	  if (*end == '\0') | 
 | 	    { | 
 | 	      /* The real section name is found in the string | 
 | 		 table.  */ | 
 | 	      if (strtab == NULL) | 
 | 		{ | 
 | 		  strtab = simple_object_coff_read_strtab (sobj, | 
 | 							   &strtab_size, | 
 | 							   &errmsg, err); | 
 | 		  if (strtab == NULL) | 
 | 		    { | 
 | 		      XDELETEVEC (scnbuf); | 
 | 		      return errmsg; | 
 | 		    } | 
 | 		} | 
 |  | 
 | 	      if (strindex < 4 || strindex >= strtab_size) | 
 | 		{ | 
 | 		  XDELETEVEC (strtab); | 
 | 		  XDELETEVEC (scnbuf); | 
 | 		  *err = 0; | 
 | 		  return "section string index out of range"; | 
 | 		} | 
 |  | 
 | 	      name = strtab + strindex; | 
 | 	    } | 
 | 	} | 
 |  | 
 |       scnptr = fetch_32 (scnhdr + offsetof (struct external_scnhdr, s_scnptr)); | 
 |       size = fetch_32 (scnhdr + offsetof (struct external_scnhdr, s_size)); | 
 |  | 
 |       if (!(*pfn) (data, name, scnptr, size)) | 
 | 	break; | 
 |     } | 
 |  | 
 |   if (strtab != NULL) | 
 |     XDELETEVEC (strtab); | 
 |   XDELETEVEC (scnbuf); | 
 |  | 
 |   return NULL; | 
 | } | 
 |  | 
 | /* Fetch the attributes for an simple_object_read.  */ | 
 |  | 
 | static void * | 
 | simple_object_coff_fetch_attributes (simple_object_read *sobj, | 
 | 				     const char **errmsg ATTRIBUTE_UNUSED, | 
 | 				     int *err ATTRIBUTE_UNUSED) | 
 | { | 
 |   struct simple_object_coff_read *ocr = | 
 |     (struct simple_object_coff_read *) sobj->data; | 
 |   struct simple_object_coff_attributes *ret; | 
 |  | 
 |   ret = XNEW (struct simple_object_coff_attributes); | 
 |   ret->magic = ocr->magic; | 
 |   ret->is_big_endian = ocr->is_big_endian; | 
 |   ret->flags = ocr->flags; | 
 |   return ret; | 
 | } | 
 |  | 
 | /* Release the private data for an simple_object_read.  */ | 
 |  | 
 | static void | 
 | simple_object_coff_release_read (void *data) | 
 | { | 
 |   XDELETE (data); | 
 | } | 
 |  | 
 | /* Compare two attributes structures.  */ | 
 |  | 
 | static const char * | 
 | simple_object_coff_attributes_merge (void *todata, void *fromdata, int *err) | 
 | { | 
 |   struct simple_object_coff_attributes *to = | 
 |     (struct simple_object_coff_attributes *) todata; | 
 |   struct simple_object_coff_attributes *from = | 
 |     (struct simple_object_coff_attributes *) fromdata; | 
 |  | 
 |   if (to->magic != from->magic || to->is_big_endian != from->is_big_endian) | 
 |     { | 
 |       *err = 0; | 
 |       return "COFF object format mismatch"; | 
 |     } | 
 |   return NULL; | 
 | } | 
 |  | 
 | /* Release the private data for an attributes structure.  */ | 
 |  | 
 | static void | 
 | simple_object_coff_release_attributes (void *data) | 
 | { | 
 |   XDELETE (data); | 
 | } | 
 |  | 
 | /* Prepare to write out a file.  */ | 
 |  | 
 | static void * | 
 | simple_object_coff_start_write (void *attributes_data, | 
 | 				const char **errmsg ATTRIBUTE_UNUSED, | 
 | 				int *err ATTRIBUTE_UNUSED) | 
 | { | 
 |   struct simple_object_coff_attributes *attrs = | 
 |     (struct simple_object_coff_attributes *) attributes_data; | 
 |   struct simple_object_coff_attributes *ret; | 
 |  | 
 |   /* We're just going to record the attributes, but we need to make a | 
 |      copy because the user may delete them.  */ | 
 |   ret = XNEW (struct simple_object_coff_attributes); | 
 |   *ret = *attrs; | 
 |   return ret; | 
 | } | 
 |  | 
 | /* Write out a COFF filehdr.  */ | 
 |  | 
 | static int | 
 | simple_object_coff_write_filehdr (simple_object_write *sobj, int descriptor, | 
 | 				  unsigned int nscns, size_t symtab_offset, | 
 | 				  unsigned int nsyms, const char **errmsg, | 
 | 				  int *err) | 
 | { | 
 |   struct simple_object_coff_attributes *attrs = | 
 |     (struct simple_object_coff_attributes *) sobj->data; | 
 |   unsigned char hdrbuf[sizeof (struct external_filehdr)]; | 
 |   unsigned char *hdr; | 
 |   void (*set_16) (unsigned char *, unsigned short); | 
 |   void (*set_32) (unsigned char *, unsigned int); | 
 |  | 
 |   hdr = &hdrbuf[0]; | 
 |  | 
 |   set_16 = (attrs->is_big_endian | 
 | 	    ? simple_object_set_big_16 | 
 | 	    : simple_object_set_little_16); | 
 |   set_32 = (attrs->is_big_endian | 
 | 	    ? simple_object_set_big_32 | 
 | 	    : simple_object_set_little_32); | 
 |  | 
 |   memset (hdr, 0, sizeof (struct external_filehdr)); | 
 |  | 
 |   set_16 (hdr + offsetof (struct external_filehdr, f_magic), attrs->magic); | 
 |   set_16 (hdr + offsetof (struct external_filehdr, f_nscns), nscns); | 
 |   /* f_timdat left as zero.  */ | 
 |   set_32 (hdr + offsetof (struct external_filehdr, f_symptr), symtab_offset); | 
 |   set_32 (hdr + offsetof (struct external_filehdr, f_nsyms), nsyms); | 
 |   /* f_opthdr left as zero.  */ | 
 |   set_16 (hdr + offsetof (struct external_filehdr, f_flags), attrs->flags); | 
 |  | 
 |   return simple_object_internal_write (descriptor, 0, hdrbuf, | 
 | 				       sizeof (struct external_filehdr), | 
 | 				       errmsg, err); | 
 | } | 
 |  | 
 | /* Write out a COFF section header.  */ | 
 |  | 
 | static int | 
 | simple_object_coff_write_scnhdr (simple_object_write *sobj, int descriptor, | 
 | 				 const char *name, size_t *name_offset, | 
 | 				 off_t scnhdr_offset, size_t scnsize, | 
 | 				 off_t offset, unsigned int align, | 
 | 				 const char **errmsg, int *err) | 
 | { | 
 |   struct simple_object_coff_attributes *attrs = | 
 |     (struct simple_object_coff_attributes *) sobj->data; | 
 |   void (*set_32) (unsigned char *, unsigned int); | 
 |   unsigned char hdrbuf[sizeof (struct external_scnhdr)]; | 
 |   unsigned char *hdr; | 
 |   size_t namelen; | 
 |   unsigned int flags; | 
 |  | 
 |   set_32 = (attrs->is_big_endian | 
 | 	    ? simple_object_set_big_32 | 
 | 	    : simple_object_set_little_32); | 
 |  | 
 |   memset (hdrbuf, 0, sizeof hdrbuf); | 
 |   hdr = &hdrbuf[0]; | 
 |  | 
 |   namelen = strlen (name); | 
 |   if (namelen <= SCNNMLEN) | 
 |     strncpy ((char *) hdr + offsetof (struct external_scnhdr, s_name), name, | 
 | 	     SCNNMLEN); | 
 |   else | 
 |     { | 
 |       snprintf ((char *) hdr + offsetof (struct external_scnhdr, s_name), | 
 | 		SCNNMLEN, "/%lu", (unsigned long) *name_offset); | 
 |       *name_offset += namelen + 1; | 
 |     } | 
 |  | 
 |   /* s_paddr left as zero.  */ | 
 |   /* s_vaddr left as zero.  */ | 
 |   set_32 (hdr + offsetof (struct external_scnhdr, s_size), scnsize); | 
 |   set_32 (hdr + offsetof (struct external_scnhdr, s_scnptr), offset); | 
 |   /* s_relptr left as zero.  */ | 
 |   /* s_lnnoptr left as zero.  */ | 
 |   /* s_nreloc left as zero.  */ | 
 |   /* s_nlnno left as zero.  */ | 
 |   flags = (STYP_DATA | IMAGE_SCN_MEM_DISCARDABLE | IMAGE_SCN_MEM_SHARED | 
 | 	   | IMAGE_SCN_MEM_READ); | 
 |   /* PE can represent alignment up to 13.  */ | 
 |   if (align > 13) | 
 |     align = 13; | 
 |   flags |= IMAGE_SCN_ALIGN_POWER_CONST(align); | 
 |   set_32 (hdr + offsetof (struct external_scnhdr, s_flags), flags); | 
 |  | 
 |   return simple_object_internal_write (descriptor, scnhdr_offset, hdrbuf, | 
 | 				       sizeof (struct external_scnhdr), | 
 | 				       errmsg, err); | 
 | } | 
 |  | 
 | /* Write out a complete COFF file.  */ | 
 |  | 
 | static const char * | 
 | simple_object_coff_write_to_file (simple_object_write *sobj, int descriptor, | 
 | 				  int *err) | 
 | { | 
 |   struct simple_object_coff_attributes *attrs = | 
 |     (struct simple_object_coff_attributes *) sobj->data; | 
 |   unsigned int nscns, secnum; | 
 |   simple_object_write_section *section; | 
 |   off_t scnhdr_offset; | 
 |   size_t symtab_offset; | 
 |   off_t secsym_offset; | 
 |   unsigned int nsyms; | 
 |   size_t offset; | 
 |   size_t name_offset; | 
 |   const char *errmsg; | 
 |   unsigned char strsizebuf[4]; | 
 |   /* The interface doesn't give us access to the name of the input file | 
 |      yet.  We want to use its basename for the FILE symbol.  This is | 
 |      what 'gas' uses when told to assemble from stdin.  */ | 
 |   const char *source_filename = "fake"; | 
 |   size_t sflen; | 
 |   union | 
 |   { | 
 |     struct external_syment sym; | 
 |     union external_auxent aux; | 
 |   } syms[2]; | 
 |   void (*set_16) (unsigned char *, unsigned short); | 
 |   void (*set_32) (unsigned char *, unsigned int); | 
 |  | 
 |   set_16 = (attrs->is_big_endian | 
 | 	    ? simple_object_set_big_16 | 
 | 	    : simple_object_set_little_16); | 
 |   set_32 = (attrs->is_big_endian | 
 | 	    ? simple_object_set_big_32 | 
 | 	    : simple_object_set_little_32); | 
 |  | 
 |   nscns = 0; | 
 |   for (section = sobj->sections; section != NULL; section = section->next) | 
 |     ++nscns; | 
 |  | 
 |   scnhdr_offset = sizeof (struct external_filehdr); | 
 |   offset = scnhdr_offset + nscns * sizeof (struct external_scnhdr); | 
 |   name_offset = 4; | 
 |   for (section = sobj->sections; section != NULL; section = section->next) | 
 |     { | 
 |       size_t mask; | 
 |       size_t new_offset; | 
 |       size_t scnsize; | 
 |       struct simple_object_write_section_buffer *buffer; | 
 |  | 
 |       mask = (1U << section->align) - 1; | 
 |       new_offset = offset & mask; | 
 |       new_offset &= ~ mask; | 
 |       while (new_offset > offset) | 
 | 	{ | 
 | 	  unsigned char zeroes[16]; | 
 | 	  size_t write; | 
 |  | 
 | 	  memset (zeroes, 0, sizeof zeroes); | 
 | 	  write = new_offset - offset; | 
 | 	  if (write > sizeof zeroes) | 
 | 	    write = sizeof zeroes; | 
 | 	  if (!simple_object_internal_write (descriptor, offset, zeroes, write, | 
 | 					     &errmsg, err)) | 
 | 	    return errmsg; | 
 | 	} | 
 |  | 
 |       scnsize = 0; | 
 |       for (buffer = section->buffers; buffer != NULL; buffer = buffer->next) | 
 | 	{ | 
 | 	  if (!simple_object_internal_write (descriptor, offset + scnsize, | 
 | 					     ((const unsigned char *) | 
 | 					      buffer->buffer), | 
 | 					     buffer->size, &errmsg, err)) | 
 | 	    return errmsg; | 
 | 	  scnsize += buffer->size; | 
 | 	} | 
 |  | 
 |       if (!simple_object_coff_write_scnhdr (sobj, descriptor, section->name, | 
 | 					    &name_offset, scnhdr_offset, | 
 | 					    scnsize, offset, section->align, | 
 | 					    &errmsg, err)) | 
 | 	return errmsg; | 
 |  | 
 |       scnhdr_offset += sizeof (struct external_scnhdr); | 
 |       offset += scnsize; | 
 |     } | 
 |  | 
 |   /* Symbol table is always half-word aligned.  */ | 
 |   offset += (offset & 1); | 
 |   /* There is a file symbol and a section symbol per section, | 
 |      and each of these has a single auxiliary symbol following.  */ | 
 |   nsyms = 2 * (nscns + 1); | 
 |   symtab_offset = offset; | 
 |   /* Advance across space reserved for symbol table to locate | 
 |      start of string table.  */ | 
 |   offset += nsyms * sizeof (struct external_syment); | 
 |  | 
 |   /* Write out file symbol.  */ | 
 |   memset (&syms[0], 0, sizeof (syms)); | 
 |   strcpy ((char *)&syms[0].sym.e.e_name[0], ".file"); | 
 |   set_16 (&syms[0].sym.e_scnum[0], IMAGE_SYM_DEBUG); | 
 |   set_16 (&syms[0].sym.e_type[0], IMAGE_SYM_TYPE); | 
 |   syms[0].sym.e_sclass[0] = IMAGE_SYM_CLASS_FILE; | 
 |   syms[0].sym.e_numaux[0] = 1; | 
 |   /* The name need not be nul-terminated if it fits into the x_fname field | 
 |      directly, but must be if it has to be placed into the string table.  */ | 
 |   sflen = strlen (source_filename); | 
 |   if (sflen <= E_FILNMLEN) | 
 |     memcpy (&syms[1].aux.x_file.x_fname[0], source_filename, sflen); | 
 |   else | 
 |     { | 
 |       set_32 (&syms[1].aux.x_file.x_n.x_offset[0], name_offset); | 
 |       if (!simple_object_internal_write (descriptor, offset + name_offset, | 
 | 					 ((const unsigned char *) | 
 | 					  source_filename), | 
 | 					 sflen + 1, &errmsg, err)) | 
 | 	return errmsg; | 
 |       name_offset += strlen (source_filename) + 1; | 
 |     } | 
 |   if (!simple_object_internal_write (descriptor, symtab_offset, | 
 | 				     (const unsigned char *) &syms[0], | 
 | 				     sizeof (syms), &errmsg, err)) | 
 |     return errmsg; | 
 |  | 
 |   /* Write the string table length, followed by the strings and section | 
 |      symbols in step with each other.  */ | 
 |   set_32 (strsizebuf, name_offset); | 
 |   if (!simple_object_internal_write (descriptor, offset, strsizebuf, 4, | 
 | 				     &errmsg, err)) | 
 |     return errmsg; | 
 |  | 
 |   name_offset = 4; | 
 |   secsym_offset = symtab_offset + sizeof (syms); | 
 |   memset (&syms[0], 0, sizeof (syms)); | 
 |   set_16 (&syms[0].sym.e_type[0], IMAGE_SYM_TYPE); | 
 |   syms[0].sym.e_sclass[0] = IMAGE_SYM_CLASS_STATIC; | 
 |   syms[0].sym.e_numaux[0] = 1; | 
 |   secnum = 1; | 
 |  | 
 |   for (section = sobj->sections; section != NULL; section = section->next) | 
 |     { | 
 |       size_t namelen; | 
 |       size_t scnsize; | 
 |       struct simple_object_write_section_buffer *buffer; | 
 |  | 
 |       namelen = strlen (section->name); | 
 |       set_16 (&syms[0].sym.e_scnum[0], secnum++); | 
 |       scnsize = 0; | 
 |       for (buffer = section->buffers; buffer != NULL; buffer = buffer->next) | 
 | 	scnsize += buffer->size; | 
 |       set_32 (&syms[1].aux.x_scn.x_scnlen[0], scnsize); | 
 |       if (namelen > SCNNMLEN) | 
 | 	{ | 
 | 	  set_32 (&syms[0].sym.e.e.e_zeroes[0], 0); | 
 | 	  set_32 (&syms[0].sym.e.e.e_offset[0], name_offset); | 
 | 	  if (!simple_object_internal_write (descriptor, offset + name_offset, | 
 | 					     ((const unsigned char *) | 
 | 					      section->name), | 
 | 					     namelen + 1, &errmsg, err)) | 
 | 	    return errmsg; | 
 | 	  name_offset += namelen + 1; | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  memcpy (&syms[0].sym.e.e_name[0], section->name, | 
 | 		  strlen (section->name)); | 
 | 	  memset (&syms[0].sym.e.e_name[strlen (section->name)], 0, | 
 | 		  E_SYMNMLEN - strlen (section->name)); | 
 | 	} | 
 |  | 
 |       if (!simple_object_internal_write (descriptor, secsym_offset, | 
 | 					 (const unsigned char *) &syms[0], | 
 | 					 sizeof (syms), &errmsg, err)) | 
 | 	return errmsg; | 
 |       secsym_offset += sizeof (syms); | 
 |     } | 
 |  | 
 |   if (!simple_object_coff_write_filehdr (sobj, descriptor, nscns, | 
 | 					 symtab_offset, nsyms, &errmsg, err)) | 
 |     return errmsg; | 
 |  | 
 |   return NULL; | 
 | } | 
 |  | 
 | /* Release the private data for an simple_object_write structure.  */ | 
 |  | 
 | static void | 
 | simple_object_coff_release_write (void *data) | 
 | { | 
 |   XDELETE (data); | 
 | } | 
 |  | 
 | /* The COFF functions.  */ | 
 |  | 
 | const struct simple_object_functions simple_object_coff_functions = | 
 | { | 
 |   simple_object_coff_match, | 
 |   simple_object_coff_find_sections, | 
 |   simple_object_coff_fetch_attributes, | 
 |   simple_object_coff_release_read, | 
 |   simple_object_coff_attributes_merge, | 
 |   simple_object_coff_release_attributes, | 
 |   simple_object_coff_start_write, | 
 |   simple_object_coff_write_to_file, | 
 |   simple_object_coff_release_write, | 
 |   NULL | 
 | }; |