[PATCH v11 03/25] gas: implement parsing of object attributes v2

Jan Beulich jbeulich@suse.com
Thu Jan 8 13:04:38 GMT 2026


On 31.12.2025 00:05, Matthieu Longo wrote:
> --- a/bfd/elf-attrs.c
> +++ b/bfd/elf-attrs.c
> @@ -258,6 +258,160 @@ bfd_elf_set_obj_attr_contents (bfd *abfd, bfd_byte *buffer, bfd_vma size)
>    write_obj_attr_section_v1 (abfd, buffer, size);
>  }
>  
> +/* The first two tags in gnu-testing namespace are known (from the perspective
> +   of GNU ld), and so have a name and can be initialized to the default value
> +   ('0' or NULL) depending on the encoding specified on the subsection.  Any
> +   tags above 1 will be considered unknown, so will be default initialized in
> +   the same way but its status will be set to obj_attr_subsection_v2_unknown.
> +   If the tag is unknown, ld can drop it if it is inside an optional subsection,
> +   whereas ld will raise an error in a required subsection.
> +   Note: the array below has to be sorted by the tag's integer value.  */
> +static const obj_attr_info_t known_attrs_gnu_testing[] =
> +{
> +  {
> +    .tag = {"GNUTestTag_0", 0},
> +    .default_value = {},
> +  },
> +  {
> +    .tag = {"GNUTestTag_1", 1},
> +    .default_value = {},
> +  },
> +};

As said on the v10 thread, aiui {} is a gcc extension (becoming standard C only
in a version newer than C99). And as before - I don't see what good those two
lines would do anyway. Simply omit them.

> --- a/bfd/elf-attrs.h
> +++ b/bfd/elf-attrs.h
> @@ -21,6 +21,7 @@
>  #pragma once
>  
>  #include <stdint.h>
> +#include "hidden.h"
>  
>  typedef enum obj_attr_version {
>    OBJ_ATTR_VERSION_NONE = 0,
> @@ -47,6 +48,9 @@ typedef enum obj_attr_encoding_v2
>  #define obj_attr_encoding_v2_to_u8(value) \
>    ((uint8_t) ((value) - 1))
>  
> +extern const char *
> +bfd_oav2_encoding_to_string (obj_attr_encoding_v2_t);

Okay, this now identifies itself as exported function.

> @@ -108,6 +112,9 @@ typedef struct obj_attr_subsection_v2 {
>    struct obj_attr_v2 *last;
>  } obj_attr_subsection_v2_t;
>  
> +extern const char *
> +oav2_comprehension_to_string (bool);

What about this? It has no bfd_ prefix and no ATTRIBUTE_HIDDEN.

> @@ -119,3 +126,37 @@ typedef struct obj_attr_subsection_list
>    /* The size of the list.  */
>    unsigned int size;
>  } obj_attr_subsection_list_t;
> +
> +typedef struct {
> +  const char *const name;
> +  obj_attr_tag_t value;
> +} obj_attr_tag_info_t;
> +
> +/* Attribute information.  */
> +typedef struct {
> +  obj_attr_tag_info_t tag;
> +  obj_attr_value_v2_t default_value;
> +} obj_attr_info_t;
> +
> +typedef struct
> +{
> +  const char *const subsec_name;
> +  const obj_attr_info_t *known_attrs;
> +  const bool optional;
> +  const obj_attr_encoding_v2_t encoding;
> +  const size_t len;
> +} known_subsection_v2_t;
> +
> +struct elf_backend_data;
> +
> +extern const known_subsection_v2_t *
> +bfd_obj_attr_v2_identify_subsection (const struct elf_backend_data *,
> +				     const char*);
> +
> +extern const obj_attr_info_t *
> +obj_attr_v2_find_known_by_tag (const struct elf_backend_data *,
> +			       const char*, obj_attr_tag_t)  ATTRIBUTE_HIDDEN;

Nit: One blank too many ahead of ATTRIBUTE_HIDDEN, ...

> +extern const char *
> +obj_attr_v2_tag_to_string (const struct elf_backend_data *, const char*,
> +			   obj_attr_tag_t)  ATTRIBUTE_HIDDEN;

... also here. Also for all three above the pointer-to-const-char all
(still) lack a blank ahead of the *. I'm sure I previously asked that
you please go through the entire series to make respective adjustments.

> --- a/bfd/elfxx-aarch64.c
> +++ b/bfd/elfxx-aarch64.c
> @@ -21,8 +21,10 @@
>  #include "sysdep.h"
>  #include "bfd.h"
>  #include "elf-bfd.h"
> +#include "elf/aarch64.h"
>  #include "elfxx-aarch64.h"
>  #include "libbfd.h"
> +#include "libiberty.h"
>  #include <stdarg.h>
>  #include <string.h>
>  
> @@ -887,6 +889,68 @@ _bfd_aarch64_obj_attrs_version_enc (obj_attr_version_t version)
>    abort ();
>  }
>  
> +/* List of known attributes in the subsection "aeabi_feature_and_bits".
> +   Note: the array below has to be sorted by the tag's integer value that can
> +   be found in the document "Build Attributes for the Arm® 64-bit Architecture
> +   (AArch64)".  */
> +static const obj_attr_info_t known_attrs_aeabi_feature_and_bits[] =
> +{
> +  {
> +    .tag = {"Tag_Feature_BTI", Tag_Feature_BTI},
> +    .default_value.uint = 0,
> +  },
> +  {
> +    .tag = {"Tag_Feature_PAC", Tag_Feature_PAC},
> +    .default_value.uint = 0,
> +  },
> +  {
> +    .tag = {"Tag_Feature_GCS", Tag_Feature_GCS},
> +    .default_value.uint = 0,
> +  },
> +};

As per the earlier comment, no need for the .default_value lines.

> +/* List of known attributes in the subsection "aeabi_pauthabi".
> +   Notes:
> +   - "aeabi_pauthabi" is a required subsection to use PAuthABI (which is
> +     today only supported by LLVM, unsupported by GCC 15 and lower. There is no
> +     plan to add support for it in the future). A value of 0 for any the tags
> +     below means that the user did not permit this entity to use the PAuthABI.
> +   - the array below has to be sorted by the tag's integer value that can be
> +     found in the document "Build Attributes for the Arm® 64-bit Architecture
> +     (AArch64)".  */
> +static const obj_attr_info_t known_attrs_aeabi_pauthabi[] =
> +{
> +  {
> +    .tag = {"Tag_PAuth_Platform", Tag_PAuth_Platform},
> +    .default_value.uint = 0,
> +  },
> +  {
> +    .tag = {"Tag_PAuth_Schema", Tag_PAuth_Schema},
> +    .default_value.uint = 0,
> +  },
> +};

Again.

> +/* Extract a string literal ("[^.]+") from the input.  */

What is ("[^.]+") about? ISTR you mentioning . to be illegal in certain places,
so that may mean to be a regular expression. Except that ...

> +static bool
> +extract_string_literal (arg_t *arg_out)
> +{
> +  int len;
> +  char *obstack_buf = demand_copy_C_string (&len);
> +  if (obstack_buf != NULL)
> +    {
> +      arg_out->val.string = xstrdup (obstack_buf);
> +      obstack_free (&notes, obstack_buf);
> +      arg_out->vtype = VALUE_STRING;
> +      return true;
> +    }
> +
> +  arg_out->val.string = NULL;
> +  return false;
> +}

... no exclusion of . is done anywhere here.

> +/* Extract an integer literal from the input.
> +   Anything matched by O_constant is considered an integer literal (see the
> +   usage of O_constant in expr.c to see all the matches.
> +   Return true on success, false otherwise. If a signedness issue is detected,
> +   'signedness_issue' is also set to true.  */
> +static bool
> +extract_integer_literal (arg_t *arg_out,
> +			 bool want_unsigned,
> +			 bool *signedness_issue)
> +{
> +  const char *cursor_begin = input_line_pointer;
> +  expressionS exp;
> +  expression_and_evaluate (&exp);
> +  if (exp.X_op != O_constant)
> +    {
> +      char backup_c = *input_line_pointer;
> +      *input_line_pointer = '\0';
> +      as_bad (_("expression '%s' does not resolve to an integer literal"),

"integer literal" is a plain number in whatever source. The result of evaluating
an expression isn't a "literal" anymore. Just drop the word.

> +	      cursor_begin);
> +      /* Restore the character pointed by the current cursor position,
> +	 otherwise '\0' misleads ignore_rest_of_line().  */
> +      *input_line_pointer = backup_c;
> +      return false;
> +    }
> +
> +  int64_t val = (int64_t) exp.X_add_number;
> +  if (want_unsigned)
> +    {
> +      if (! exp.X_unsigned && val < 0)
> +	{
> +	  as_bad (_("unexpected value %" PRId64 ", expected `unsigned integer'"
> +		    " instead"), val);

Even if this goes beyond 80 chars, please don't wrap format string unless
they're really long. Also please recall what I said about not putting further
arguments after an already wrapped one (of course right here this won't be an
issue anymore once the wrapping disappears).

> +/* Parse an argument, and set its type accordingly depending on the input
> +   value, and the constraints on the expected argument.  */
> +static bool
> +obj_attr_parse_arg (arg_token_t expected_ttype,
> +		    bool resolve_identifier,
> +		    bool optional,
> +		    arg_t *arg_out)
> +{
> +  const arg_token_t low_ttype = (expected_ttype & LT_MASK);
> +
> +  if (optional && is_end_of_stmt (*input_line_pointer))
> +    {
> +      arg_out->vtype = VALUE_OPTIONAL_ABSENT;
> +      return true;
> +    }
> +
> +  /* Check whether this looks like a string literal
> +     Note: symbol look-up for string literals is not available.  */
> +  if (*input_line_pointer == '"')
> +    {
> +      bool status = extract_string_literal (arg_out);
> +      if (status && (low_ttype & STRING))
> +	return true;
> +
> +      if (status)
> +	{
> +	  char sbuffer[100];
> +	  as_bad (_("unexpected `string' \"%s\", expected %s instead"),
> +		  arg_out->val.string,
> +		  expectations_to_string (low_ttype, sbuffer, sizeof(sbuffer)));
> +	  free ((char *) arg_out->val.string);
> +	  arg_out->val.string = NULL;
> +	  arg_out->vtype = VALUE_UNDEFINED;
> +	}
> +      return false;
> +    }
> +
> +  /* Check whether this looks like an identifier.  */
> +  if (is_identifier_beginner (*input_line_pointer))
> +    {
> +      bool status = extract_identifier (is_part_of_identifier, arg_out);
> +      /* match_identifier() confirmed that it was the beginning of an
> +	 identifier, so we don't expect the extraction to fail.  */
> +      gas_assert (status);
> +      gas_assert (arg_out->vtype == VALUE_STRING);
> +
> +      if (! (low_ttype & IDENTIFIER))
> +	{
> +	  char sbuffer[100];
> +	  as_bad (_("unexpected `identifier' \"%s\", expected %s instead"),
> +		  arg_out->val.string,
> +		  expectations_to_string (low_ttype, sbuffer, sizeof(sbuffer)));
> +	  free ((char *) arg_out->val.string);
> +	  arg_out->val.string = NULL;
> +	  arg_out->vtype = VALUE_UNDEFINED;
> +	  return false;
> +	}
> +
> +      /* In some cases, we don't want to resolve the identifier because it is the
> +	 actual value.  */
> +      if (! resolve_identifier)
> +	return true;
> +
> +      /* Move the identifier out of arg_out.  */
> +      const char *identifier = arg_out->val.string;
> +      arg_out->val.string = NULL;
> +      bool resolved = true;
> +
> +      /* The identifier is a symbol, let's try to resolve it by:
> +	 1. using the provided list of known symbols.
> +	   a) backend-independent
> +	   b) backend-specific.  */
> +      if (lookup_known_symbols (identifier, expected_ttype, arg_out))
> +	goto free_identifier;
> +
> +      /* 2. using the symbol table for this compilation unit.
> +	 Note: this is the last attempt before failure.  */
> +      if (lookup_symbol_table (identifier, low_ttype, arg_out))
> +	goto free_identifier;
> +
> +      as_bad (_("unknown identifier '%s' in this context"), identifier);
> +      arg_out->val.string = NULL;
> +      arg_out->vtype = VALUE_UNDEFINED;
> +      resolved = false;
> +
> + free_identifier:
> +      free ((char *) identifier);
> +      return resolved;
> +    }
> +
> +  /* If it is neither a string nor an identifier, it must be an expression.  */
> +  bool signedness_issue = false;
> +  bool status = extract_integer_literal (arg_out,
> +					 (low_ttype & UNSIGNED_INTEGER),
> +					 &signedness_issue);
> +  if (status && (low_ttype & (UNSIGNED_INTEGER | SIGNED_INTEGER)))
> +    return true;
> +
> +  char sbuffer[100];
> +  if (status)

The use of "status" here is suboptimal, as - without looking at
extract_integer_literal() itself - it's not clear whether "true" means
success or failure. Not the least because it's "success" yet ...

> +    as_bad (_("unexpected integer %lu, expected %s instead"),
> +	    arg_out->val.u64,
> +	    expectations_to_string (low_ttype, sbuffer, sizeof(sbuffer)));

... it results in an error here. Would you mind renaming to "success" or
"okay"?

> +  else if ((low_ttype & UNSIGNED_INTEGER) && signedness_issue) {}
> +    /* Already handled by extract_integer_literal(), nothing to do.  */

Having the (empty) braces on the same line as the if() may be okay when the
body is really empty. With the comment, however, please place the braces as
usual.

> +/* Trim white spaces before a parameter.
> +   Error if it meets a parameter separator before a parameter.  */
> +static bool
> +trim_whitespace_before_param (unsigned int n)

You dropped the use of plural in the function name, but for some reason it
stayed in the comment. (Again for th following function.)

> +{
> +  bool error = false;
> +  do
> +    {
> +      skip_whitespace (input_line_pointer);
> +      if (*input_line_pointer == ',')
> +	{
> +	  error = true;
> +	  if (n == 0)
> +	    as_bad (_("unexpected comma before value"));
> +	  else
> +	    as_bad (_("unexpected comma before parameter %u"), n);
> +	  skip_past_comma (&input_line_pointer);
> +	}
> +    }
> +  while (is_whitespace (*input_line_pointer));

Why does this need to be a loop? Can't you bail after encountering the first
error, rather than perhaps printing many (unhelpful, simply by their amount)
diagnostics?

> +/* Skip white spaces + parameter separator after a parameter.
> +   Error if it does not meet a parameter separator after a parameter.  */
> +static bool
> +skip_whitespace_past_comma (unsigned int n)
> +{
> +  skip_whitespace (input_line_pointer);
> +  if (! skip_past_comma (&input_line_pointer))
> +    {
> +      if (n == 0)
> +	as_bad (_("unexpected comma after value"));
> +      else
> +	as_bad (_("missing comma after parameter %u"), n);

Something's wrong with the error messages here: skip_past_comma() returning
"false" can't mean there was a comma in one case and there was none in the
other. I wonder if the function can be called at all when n == 0. Hmm, ...

> +/* Can parse a list of arguments with variable length.  */
> +static bool
> +obj_attr_parse_args (arg_token_t expected_ttype,
> +		     bool resolve_identifier,
> +		     arg_t *arg_out)
> +{
> +  if ((expected_ttype & LIST) == 0)
> +    return obj_attr_parse_arg (expected_ttype, resolve_identifier, false,
> +			       arg_out);
> +
> +  static const size_t LIST_MAX_SIZE = 2;
> +  arg_t *arg_list = xcalloc (LIST_MAX_SIZE, sizeof (*arg_list));
> +
> +  /* We don't want to support recursive lists.  */
> +  expected_ttype &= ~LIST;
> +
> +  size_t n = 0;
> +  do {
> +    if (! trim_whitespace_before_param (0))
> +      goto bad;
> +
> +    if (! obj_attr_parse_arg (expected_ttype, resolve_identifier, false,
> +			      &arg_list[n]))
> +      goto bad;
> +
> +    ++n;
> +    skip_whitespace (input_line_pointer);
> +    if (is_end_of_stmt (*input_line_pointer))
> +      break;
> +
> +    if (! skip_whitespace_past_comma (0))
> +      goto bad;

... yes, it can, but why? Don't you mean to pass n here (which already is non-
zero)? And don't you also mean to pass n to trim_whitespace_before_param()?

> +#if (TC_OBJ_ATTR_v1)
> +/* Determine the expected argument type based on the tag ID.  */
> +static arg_token_t
> +obj_attr_v1_get_arg_type (bfd *abfd,
> +			  obj_attr_vendor_t vendor,
> +			  obj_attr_tag_t tag)
> +{
> +  int attr_type = bfd_elf_obj_attrs_arg_type (abfd, vendor, tag);
> +  arg_token_t arg_type;
> +  if (attr_type == (ATTR_TYPE_FLAG_STR_VAL | ATTR_TYPE_FLAG_INT_VAL))
> +    arg_type = LIST | UNSIGNED_INTEGER | STRING;
> +  else if (attr_type == ATTR_TYPE_FLAG_STR_VAL)
> +    arg_type = STRING;
> +  else
> +    /* Covers the remaning cases:
> +       - ATTR_TYPE_FLAG_INT_VAL.
> +       - ATTR_TYPE_FLAG_INT_VAL | ATTR_TYPE_FLAG_NO_DEFAULT.  */
> +    arg_type = UNSIGNED_INTEGER;
> +  return arg_type;
> +}
> +#endif /* TC_OBJ_ATTR_v1 */
> +
> +#if (TC_OBJ_ATTR_v2)
> +/* Determine the expected argument type based on the subsection encoding.  */
> +static arg_token_t
> +obj_attr_v2_get_arg_type (obj_attr_encoding_v2_t subsec_encoding)
> +{
> +  arg_token_t arg_type;
> +  switch (subsec_encoding)
> +    {
> +    case OA_ENC_ULEB128:
> +      arg_type = UNSIGNED_INTEGER;
> +      break;
> +    case OA_ENC_NTBS:
> +      arg_type = STRING;
> +      break;
> +    case OA_ENC_UNSET:
> +    default:
> +      abort ();
> +    }
> +  return arg_type;
> +}
> +#endif /* TC_OBJ_ATTR_v2 */
> +
> +/* Parse the arguments of [vendor]_attribute directive.  */
> +static arg_t *
> +vendor_attribute_parse_args (obj_attr_vendor_t vendor ATTRIBUTE_UNUSED,
> +			     const obj_attr_subsection_v2_t *subsec ATTRIBUTE_UNUSED,
> +			     unsigned int nargs, ...)
> +{
> +  bool parsing_err = false;
> +  va_list args;
> +  va_start (args, nargs);
> +
> +  arg_t *args_out = xcalloc (nargs, sizeof (arg_t));
> +
> +  for (unsigned int n = 0; n < nargs; ++n)
> +    {
> +      if (! trim_whitespace_before_param (n + 1))
> +	{
> +	  ignore_rest_of_line ();
> +	  goto bad;
> +	}
> +
> +      arg_t *arg_out = &args_out[n];
> +
> +      arg_token_t expected_ttype = va_arg (args, arg_token_t);
> +      arg_token_t high_ttype = (expected_ttype & HT_MASK);
> +      /* Make sure that we called the right parse_args().  */
> +      gas_assert (high_ttype == ATTRIBUTE_KEY
> +	       || high_ttype == ATTRIBUTE_VALUE);
> +
> +      if (high_ttype == ATTRIBUTE_VALUE)
> +	{
> +	  arg_token_t type_attr_value
> +#if (TC_OBJ_ATTR_v1 && TC_OBJ_ATTR_v2)
> +	    = (subsec != NULL)
> +	    ? obj_attr_v2_get_arg_type (subsec->encoding)
> +	    : obj_attr_v1_get_arg_type (stdoutput, vendor,

These two lines want indenting by two more positions.

> +					args_out[n-1].val.u64);
> +#elif (TC_OBJ_ATTR_v1)
> +	    = obj_attr_v1_get_arg_type (stdoutput, vendor,
> +					args_out[n-1].val.u64);
> +#else /* TC_OBJ_ATTR_v2 */
> +	    = obj_attr_v2_get_arg_type (subsec->encoding);
> +#endif

The #ifdef-ary here isn't very nice. Couldn't you drop all of it here,
leaving just the #if part, and have obj_attr_v{1,2}_get_arg_type()
return suitable "nothing" values when TC_OBJ_ATTR_v{1,2} isn't set?

> +	  expected_ttype |= type_attr_value;
> +	}
> +
> +      if (! obj_attr_parse_args (expected_ttype, true, arg_out))
> +	{
> +	  if (high_ttype == ATTRIBUTE_KEY)
> +	    {
> +	      as_bad (_("could not parse attribute tag"));
> +	      parsing_err = true;
> +	    }
> +	  else
> +	    {
> +	      as_bad (_("could not parse attribute value"));
> +	      ignore_rest_of_line ();
> +	      goto bad;

Why would you bail here, but not for the ATTRIBUTE_KEY case? As said
in an earlier comment, once there was an error it's hard to see why
you would want to continue parsing.

> +	    }
> +	}
> +
> +      if (n + 1 < nargs && !skip_whitespace_past_comma (n + 1))
> +	goto bad;
> +    }
> +
> +  if (! parsing_err)
> +    {
> +      demand_empty_rest_of_line ();
> +      va_end (args);
> +      return args_out;
> +    }
> +
> + bad:
> +  args_list_free (args_out, nargs);
> +  va_end (args);
> +  return NULL;

The reason you don't have ignore_rest_of_line() here is that
skip_whitespace_past_comma() already invokes it. Imo that's wrong, though
demonstrated also by vendor_subsection_parse_args() getting it wrong (in
invoking ignore_rest_of_line() twice in that case). obj_attr_parse_args()
also is exiting with inconsistent state because of this - it may or may
not have invoked ignore_rest_of_line().

> +#if (TC_OBJ_ATTR_v1)
> +/* Record an attribute (object attribute v1 only).  */
> +static obj_attribute *
> +obj_attr_v1_record (bfd *abfd,
> +		    const obj_attr_vendor_t vendor,
> +		    const obj_attr_tag_t tag,
> +		    arg_t *parsed_arg)
> +{
> +  obj_attribute *attr = bfd_elf_new_obj_attr (abfd, vendor, tag);
> +  if (attr != NULL)
> +    {
> +      int tag_type = bfd_elf_obj_attrs_arg_type (abfd, vendor, tag);
> +      if (parsed_arg->vtype == VALUE_LIST)
> +	{
> +	  arg_variant_list_t *plist = &parsed_arg->val.list;
> +	  gas_assert (plist->len == 2
> +		      && plist->elts[0].vtype == VALUE_UNSIGNED_INTEGER
> +		      && plist->elts[1].vtype == VALUE_STRING);
> +	  attr->type = tag_type;
> +	  attr->i = plist->elts[0].val.u64;
> +	  attr->s = (char *) plist->elts[1].val.string;
> +	  plist->elts[1].val.string = NULL;
> +	}
> +      else if (parsed_arg->vtype == VALUE_STRING)
> +	{
> +	  attr->type = tag_type;
> +	  attr->s = (char *) parsed_arg->val.string;
> +	  parsed_arg->val.string = NULL;
> +	}
> +      else
> +	{
> +	  attr->type = tag_type;
> +	  attr->i = parsed_arg->val.u64;
> +	}

Why is the setting of attr->type not done once, outside the if/else chain?
With that arguably the need for the local "tag_type" would disappear. (My
attention was mainly caught by the suspicious use of plain int there. The
magic values 1, 2, and 3 really would better be enumerators, I guess. Not
something you want or need to address here, of course.)

> +/* Record an attribute (object attribute v2 only).  */
> +static void
> +obj_attr_v2_record (obj_attr_tag_t key, arg_t *arg_val)
> +{
> +  /* An OAv2 cannot be recorded unless a subsection has been recorded.  */
> +  gas_assert (elf_obj_attr_subsections (stdoutput).last != NULL);
> +
> +  union obj_attr_value_v2 obj_attr_val;
> +  if (arg_val->vtype == VALUE_UNSIGNED_INTEGER)
> +    obj_attr_val.uint = arg_val->val.u64;
> +  else
> +    {
> +      /* Move the string.  */
> +      obj_attr_val.string = arg_val->val.string;
> +      arg_val->val.string = NULL;
> +    }
> +
> +  obj_attr_v2_t *obj_attr = bfd_elf_obj_attr_v2_init (key, obj_attr_val);
> +  gas_assert (obj_attr != NULL);
> +
> +  /* Go over the list of already recorded attributes and check for
> +     redefinitions (which are forbidden).  */
> +  bool skip_recording = false;
> +  obj_attr_v2_t *recorded_attr = obj_attr_v2_find_by_tag
> +    (elf_obj_attr_subsections (stdoutput).last, obj_attr->tag, false);
> +  if (recorded_attr != NULL)
> +    {
> +      if ((arg_val->vtype == VALUE_UNSIGNED_INTEGER
> +	   && recorded_attr->val.uint != obj_attr->val.uint)
> +	  || (arg_val->vtype == VALUE_STRING
> +	      && strcmp (recorded_attr->val.string, obj_attr->val.string) != 0))
> +	as_bad (_("attribute %lu cannot be redefined"), recorded_attr->tag);
> +      skip_recording = true;
> +    }
> +
> +  if (skip_recording)
> +    {
> +      if (arg_val->vtype == VALUE_STRING)
> +	free ((void *) obj_attr->val.string);
> +      free (obj_attr);
> +      return;
> +    }
> +
> +  LINKED_LIST_APPEND(obj_attr_v2_t) (

Nit (style): Blank before opening paren. (More of these elsewhere with the
use of linked list macros.)

> +/* Parse an attribute directive (supports both v1 & v2).  */
> +obj_attr_tag_t
> +obj_attr_process_attribute (obj_attr_vendor_t vendor)
> +{
> +  obj_attr_version_t version = elf_obj_attr_version (stdoutput);
> +  obj_attr_subsection_v2_t *subsec = NULL;
> +
> +#if (TC_OBJ_ATTR_v2)
> +  if (version == OBJ_ATTR_V2)
> +    {
> +      subsec = elf_obj_attr_subsections (stdoutput).last;
> +      if (subsec == NULL)
> +	{
> +	  as_bad (_("declaration of an attribute outside the scope of an "
> +		    "attribute subsection"));
> +	  ignore_rest_of_line ();
> +	  return 0;
> +	}
> +    }
> +#endif /* TC_OBJ_ATTR_v2 */
> +
> +  const size_t N_ARGS = 2;
> +  arg_t *args = vendor_attribute_parse_args (
> +    vendor, subsec, N_ARGS,
> +    ATTRIBUTE_KEY | IDENTIFIER | UNSIGNED_INTEGER,
> +    ATTRIBUTE_VALUE);
> +
> +  if (args == NULL)
> +    return 0;
> +
> +  obj_attr_tag_t tag = args[0].val.u64;
> +#if (TC_OBJ_ATTR_v1)
> +  if (version == OBJ_ATTR_V1)
> +    {
> +      oav1_attr_record_seen (vendor, tag);
> +      obj_attr_v1_record (stdoutput, vendor, tag, &args[1]);
> +    }
> +#endif /* TC_OBJ_ATTR_v1 */
> +#if (TC_OBJ_ATTR_v2)
> +  #if (TC_OBJ_ATTR_v1)
> +  else
> +  #endif /* TC_OBJ_ATTR_v1 */
> +  if (version == OBJ_ATTR_V2)
> +    obj_attr_v2_record (tag, &args[1]);
> +#endif /* TC_OBJ_ATTR_v2 */
> +  else
> +    abort ();

Wouldn't using switch() make this at least a little more readable? In any
event, please keep #-es in the first column (inserting indenting blanks
afterwards as desired).

> +#if (TC_OBJ_ATTR_v2)
> +/* Parse an object attribute v2's subsection directive.  */
> +void
> +obj_attr_process_subsection ()

void please as (fake) function parameter.

> @@ -234,13 +1378,19 @@ obj_attr_v1_process_attribute (obj_attr_vendor_t vendor)
>    ignore_rest_of_line ();
>    return 0;
>  }
> +#endif /* TC_OBJ_ATTR_v1 */
>  
>  /* Parse a .gnu_attribute directive.  */
>  
>  void
>  obj_elf_gnu_attribute (int ignored ATTRIBUTE_UNUSED)
>  {
> +#if (TC_OBJ_ATTR_v1)
>    obj_attr_v1_process_attribute (OBJ_ATTR_GNU);
> +#endif /* TC_OBJ_ATTR_v1 */
> +#if (TC_OBJ_ATTR_v2)
> +  obj_attr_process_attribute (OBJ_ATTR_GNU);
> +#endif /* TC_OBJ_ATTR_v2 */
>  }

Further up you also allow for TC_OBJ_ATTR_v1 and TC_OBJ_ATTR_v2 both to
be set at the same time. While the code here "looks" fine, it really
isn't, as in that case obj_attr_v1_process_attribute() will already have
consumed a line, and obj_attr_process_attribute() will consume the next
one. (Btw, why not obj_attr_v2_process_attribute()?)

Jan


More information about the Binutils mailing list