Patch to improve linker output format selection

Nick Clifton nickc@cygnus.com
Sat Jul 17 09:20:00 GMT 1999


Hi Ian,

  Below is a revised patch which addresses most of the points you
raised.  I have changed the target finding heuristic to pay more
attention to what the user or linker script has specified as the
output target format, and I have also implemented the new field for
struct bfd_target which can point to an alternative other-endian
target.  The patch also includes an example of how this might work for
elf based targets, although more patches would be needed to implement
the field for all targets known to bfd.  Not implementing the field
will not be a problem however, as the code copes if it is NULL.

  One change I did not make is to use LANG_FOR_EACH_INPUT_STATEMENT in
the function get_first_input_target.  The reason is that this function
is basically a straight copy of the function open_input_bfds() and
like that function, it is recursive.

  The new heuristic to decide the output target format looks like
  this:

   1. If the linker script or user has specified an output target
      format then use that.

   2. Otherwise if current_target has been specifically set then use
      that.

   3. Otherwise use the target format of the first input file, if it
      can be found.

   4. Otherwise use the default target format.

  Then:

   1. If an endianness has been specified on the command line and the
      selected output target format does not match this then:

   2. If the output target has an alternative target specified and the
      alternative is the correct endianness then use that.

   3. Otherwise search through all of the target formats known to bfd
      to find one which is the closest match to the current output
      target format, but which has the correct endianness.

   4. If none can be found then just use the current output target
      format and assume that an error message will be generated by
      later code.

What do you think ?

Cheers
	Nick

1999-07-17  Nick Clifton  <nickc@cygnus.com>

	* targets.c (alternative_target): New field in bfd_target
	structure.
	(bfd_search_for_target): New function:  Find a target that
	satisifies a search function.
	* bfd-in2.h: Regenerate.

	* elfxx-target.h: Initialise the alternative_target field of
	the bfd_target structures to point to the other target (if
	defined). 

Index: bfd/targets.c
===================================================================
RCS file: /cvs/binutils/binutils/bfd/targets.c,v
retrieving revision 1.3
diff -p -r1.3 targets.c
*** targets.c	1999/05/27 21:44:39	1.3
--- targets.c	1999/07/17 15:58:09
*************** Data for use by back-end routines, which
*** 464,469 ****
--- 464,480 ----
  in this structure.
  
  . PTR backend_data;
+ 
+ A pointer to an alternative bfd_target in case the current one is not
+ satisfactory.  This can happen when the target cpu supports both big
+ and little endian code, and target chosen by the linker has the wrong
+ endianness.  The function open_output() in ld/ldlang.c uses this field
+ to find an alternative output format that is suitable.
+ 
+ . 
+ . {* Opposite endian version of this target.  *}  
+ . const struct bfd_target * alternative_target;
+ . 
  .} bfd_target;
  
  */
*************** bfd_target_list ()
*** 1078,1081 ****
--- 1089,1121 ----
      *(name_ptr++) = (*target)->name;
  
    return name_list;
+ }
+ 
+ /*
+ FUNCTION
+ 	bfd_seach_for_target
+ 
+ SYNOPSIS
+ 	const bfd_target * bfd_search_for_target (int (* search_func)(const bfd_target *, void *), void *);
+ 
+ DESCRIPTION
+ 	Return a pointer to the first transfer vector in the list of
+ 	transfer vectors maintained by BFD that produces a non-zero
+ 	result when passed to the function @var{search_func}.  The
+ 	parameter @var{data} is passed, unexamined, to the search
+ 	function.
+ */
+ 
+ const bfd_target *
+ bfd_search_for_target (search_func, data)
+      int (* search_func) PARAMS ((const bfd_target * target, void * data));
+      void * data;
+ {
+   const bfd_target * const * target;
+ 
+   for (target = bfd_target_vector; * target != NULL; target ++)
+     if (search_func (* target, data))
+       return * target;
+ 
+   return NULL;
  }

Index: bfd/bfd-in2.h
===================================================================
RCS file: /cvs/binutils/binutils/bfd/bfd-in2.h,v
retrieving revision 1.13
diff -p -r1.13 bfd-in2.h
*** bfd-in2.h	1999/07/12 11:06:03	1.13
--- bfd-in2.h	1999/07/17 15:58:10
*************** CAT(NAME,_canonicalize_dynamic_reloc)
*** 3004,3009 ****
--- 3004,3013 ----
      PARAMS ((bfd *, arelent **, struct symbol_cache_entry **));
  
   PTR backend_data;
+     
+  /* Opposite endian version of this target.  */
+  const struct bfd_target * alternative_target;
+  
  } bfd_target;
  boolean 
  bfd_set_default_target  PARAMS ((const char *name));
*************** bfd_find_target PARAMS ((CONST char *tar
*** 3013,3018 ****
--- 3017,3025 ----
  
  const char **
  bfd_target_list PARAMS ((void));
+ 
+ const bfd_target *
+ bfd_search_for_target PARAMS ((int (* search_func)(const bfd_target *, void *), void *));
  
  boolean 
  bfd_check_format PARAMS ((bfd *abfd, bfd_format format));

Index: bfd/elfxx-target.h
===================================================================
RCS file: /cvs/binutils/binutils/bfd/elfxx-target.h,v
retrieving revision 1.7
diff -p -r1.7 elfxx-target.h
*** elfxx-target.h	1999/07/14 16:14:49	1.7
--- elfxx-target.h	1999/07/17 15:58:10
*************** static CONST struct elf_backend_data elf
*** 388,393 ****
--- 388,398 ----
    elf_backend_want_dynbss
  };
  
+ /* Forward declaration for use when initialising alternative_target field.  */
+ #ifdef TARGET_LITTLE_SYM
+ extern const bfd_target TARGET_LITTLE_SYM;
+ #endif
+ 
  #ifdef TARGET_BIG_SYM
  const bfd_target TARGET_BIG_SYM =
  {
*************** const bfd_target TARGET_BIG_SYM =
*** 473,478 ****
--- 478,490 ----
  
    /* backend_data: */
    (PTR) &elfNN_bed,
+ 
+   /* alternative endian target.  */
+ #ifdef TARGET_LITTLE_SYM
+   & TARGET_LITTLE_SYM,
+ #else
+   NULL,
+ #endif
  };
  #endif
  
*************** const bfd_target TARGET_LITTLE_SYM =
*** 561,565 ****
--- 573,584 ----
  
    /* backend_data: */
    (PTR) &elfNN_bed,
+ 
+   /* alternative endian target.  */
+ #ifdef TARGET_BIG_SYM
+   & TARGET_BIG_SYM,
+ #else
+   NULL,
+ #endif
  };
  #endif


1999-07-17  Nick Clifton  <nickc@cygnus.com>

	* targets.c (get_target): New function: Return true iff the
	given target is the target being sought.
	(stricpy): New function:  Like strcpy but convert to lower
	case as well.
	(strcut): New function:  Like strstr but remove the located
	substring as well.
	(name_compare): New function: Compute a compatability rating
	for two target names.
	(winner): New variable: Best target found by
	closest_target_match() so far.
	(closest_target_match): New function: Find the target which is
	the closest match to the original target.
	(get_first_input_target): New function: Find the target format
	of the first of the linker's input file.
	(open_output): Be more clever about deciding the output target
	format. 


Index: ld/ldlang.c
===================================================================
RCS file: /cvs/binutils/binutils/ld/ldlang.c,v
retrieving revision 1.7
diff -p -r1.7 ldlang.c
*** ldlang.c	1999/07/14 16:45:13	1.7
--- ldlang.c	1999/07/17 15:58:12
*************** wild (s, section, file, target, output)
*** 1449,1477 ****
      }
  }
  
  /* Open the output file.  */
  
  static bfd *
  open_output (name)
!      const char *name;
  {
!   bfd *output;
  
    if (output_target == (char *) NULL)
      {
!       if (current_target != (char *) NULL)
  	output_target = current_target;
        else
! 	output_target = default_target;
      }
    output = bfd_openw (name, output_target);
  
    if (output == (bfd *) NULL)
      {
        if (bfd_get_error () == bfd_error_invalid_target)
! 	{
! 	  einfo (_("%P%F: target %s not found\n"), output_target);
! 	}
        einfo (_("%P%F: cannot open output file %s: %E\n"), name);
      }
  
--- 1445,1696 ----
      }
  }
  
+ /* Return true iff target is the sought target.  */
+ static int
+ get_target (target, data)
+      const bfd_target * target;
+      void * data;
+ {
+   const char * sought = (const char *) data;
+   
+   return strcmp (target->name, sought) == 0;
+ }
+ 
+ /* Like strcpy() but convert to lower case as well.  */
+ static void
+ stricpy (dest, src)
+      char * dest;
+      char * src;
+ {
+   char c;
+   
+   while (c = * src ++)
+     {
+       if (isascii (c) && isupper (c))
+ 	c = tolower (c);
+ 
+       * dest ++ = c;
+     }
+ 
+   * dest = 0;
+ }
+ 
+ /* Remove the first occurance of needle (if any) in haystack
+    from haystack.  */
+ static void
+ strcut (haystack, needle)
+      char * haystack;
+      char * needle;
+ {
+   haystack = strstr (haystack, needle);
+   
+   if (haystack)
+     {
+       char * src;
+ 
+       for (src = haystack + strlen (needle); * src;)
+ 	* haystack ++ = * src ++;
+       
+       * haystack = 0;
+     }
+ }
+ 
+ /* Compare two target format name strings.
+    Return a value indicating how "similar" they are.  */
+ static int
+ name_compare (first, second)
+      char * first;
+      char * second;
+ {
+   char * copy1;
+   char * copy2;
+   int    result;
+   
+   copy1 = xmalloc (strlen (first) + 1);
+   copy2 = xmalloc (strlen (second) + 1);
+ 
+   /* Convert the names to lower case.  */
+   stricpy (copy1, first);
+   stricpy (copy2, second);
+ 
+   /* Remove and endian strings from the name.  */
+   strcut (copy1, "big");
+   strcut (copy1, "little");
+   strcut (copy2, "big");
+   strcut (copy2, "little");
+ 
+   /* Return a value based on how many characters match,
+      starting from the beginning.   If both strings are
+      the same then return 10 * their length.  */
+   for (result = 0; copy1 [result] == copy2 [result]; result ++)
+     if (copy1 [result] == 0)
+       {
+ 	result *= 10;
+ 	break;
+       }
+   
+   free (copy1);
+   free (copy2);
+ 
+   return result;
+ }
+ 
+ /* Set by closest_target_match() below.  */
+ static const bfd_target * winner;
+ 
+ /* Scan all the valid bfd targets looking for one that has the endianness
+    requirement that was specified on the command line, and is the nearest
+    match to the original output target.  */
+ static int
+ closest_target_match (target, data)
+      const bfd_target * target;
+      void * data;
+ {
+   const bfd_target * original = (const bfd_target *) data;
+   
+   if (command_line.endian == ENDIAN_BIG && target->byteorder != BFD_ENDIAN_BIG)
+     return 0;
+   
+   if (command_line.endian == ENDIAN_LITTLE && target->byteorder != BFD_ENDIAN_LITTLE)
+     return 0;
+ 
+   /* Must be the same flavour.  */
+   if (target->flavour != original->flavour)
+     return 0;
+ 
+   /* If we have not found a potential winner yet, then record this one.  */
+   if (winner == NULL)
+     {
+       winner = target;
+       return 0;
+     }
+ 
+   /* Oh dear, we now have two potential candidates for a successful match.
+      Compare their names and choose the better one. */
+   if (name_compare (target->name, original->name) > name_compare (winner->name, original->name))
+     winner = target;
+ 
+   /* Keep on searching until wqe have checked them all.  */
+   return 0;
+ }
+ 
+ 
+ static char *
+ get_first_input_target (s)
+      lang_statement_union_type * s;
+ {
+   char * target = NULL;
+   
+   for (; s != (lang_statement_union_type *) NULL; s = s->next)
+     {
+       switch (s->header.type)
+ 	{
+ 	case lang_constructors_statement_enum:
+ 	  target = get_first_input_target (constructor_list.head);
+ 	  break;
+ 	case lang_output_section_statement_enum:
+ 	  target = get_first_input_target (s->output_section_statement.children.head);
+ 	  break;
+ 	case lang_wild_statement_enum:
+ 	  target = get_first_input_target (s->wild_statement.children.head);
+ 	  break;
+ 	case lang_group_statement_enum:
+ 	  target = get_first_input_target (s->group_statement.children.head);
+ 	  break;
+ 	case lang_target_statement_enum:
+ 	  current_target = s->target_statement.target;
+ 	  break;
+ 	case lang_input_statement_enum:
+ 	  if (s->input_statement.real)
+ 	    {
+ 	      ldfile_open_file (& s->input_statement);
+ 	      if (s->input_statement.the_bfd != NULL)
+ 		if (bfd_check_format (s->input_statement.the_bfd, bfd_object))
+ 		  target = bfd_get_target (s->input_statement.the_bfd);
+ 	    }
+ 	  break;
+ 	default:
+ 	  break;
+ 	}
+       if (target != NULL)
+ 	return target;
+     }
+   
+   return target;
+ }
+ 
  /* Open the output file.  */
  
  static bfd *
  open_output (name)
!      const char * name;
  {
!   bfd * output;
  
+   /* Has the user told us which output format to use ?  */
    if (output_target == (char *) NULL)
      {
!       /* No - has the current target been set to something other than the default ?  */
!       if (current_target != default_target)
  	output_target = current_target;
+ 
+       /* No - can we determine the format of the first input file ? */
        else
! 	{
! 	  output_target = get_first_input_target (statement_list.head);
! 
! 	  /* Failed - use the default output target.  */
! 	  if (output_target == NULL)
! 	    output_target = default_target;
! 	}
!     }
!   
!   /* Has the user requested a particular endianness on the command line ?  */
!   if (command_line.endian != ENDIAN_UNSET)
!     {
!       const bfd_target * target;
!       int desired_endian;
! 
!       /* Get the chosen target.  */
!       target = bfd_search_for_target (get_target, (void *) output_target);
! 
!       if (command_line.endian == ENDIAN_BIG)
! 	desired_endian = BFD_ENDIAN_BIG;
!       else
! 	desired_endian = BFD_ENDIAN_LITTLE;
!       
!       /* See if the target has the wrong endianness.  This should not happen
! 	 if the linker script has provided big and little endian alternatives,
! 	 but some scrips don't do this.  */
!       if (target->byteorder != desired_endian)
! 	{
! 	  /* If it does, then see if the target provides
! 	     an alternative with the correct endianness.  */
! 	  if (target->alternative_target != NULL
! 	      && (target->alternative_target->byteorder == desired_endian))
! 	    output_target = target->alternative_target->name;
! 	  else
! 	    {
! 	      /* Try to find a target as similar as possible to the default
! 		 target, but which has the desired endian characteristic.  */
! 	      (void) bfd_search_for_target (closest_target_match, (void *) target);
! 	      
! 	      /* Oh dear - we could not find any targets that satisfy our requirements.  */
! 	      if (winner == NULL)
! 		einfo (_("%F%P: could not find any targets that match endianness requirement\n"));
! 	      else
! 		output_target = winner->name;
! 	    }
! 	}
      }
+       
    output = bfd_openw (name, output_target);
  
    if (output == (bfd *) NULL)
      {
        if (bfd_get_error () == bfd_error_invalid_target)
! 	einfo (_("%P%F: target %s not found\n"), output_target);
! 
        einfo (_("%P%F: cannot open output file %s: %E\n"), name);
      }
  


More information about the Binutils mailing list