Imported Upstream version 0.165
[platform/upstream/elfutils.git] / libdw / dwarf_getmacros.c
index a3d2c81..eb50508 100644 (file)
@@ -1,5 +1,5 @@
 /* Get macro information.
-   Copyright (C) 2002-2009 Red Hat, Inc.
+   Copyright (C) 2002-2009, 2014 Red Hat, Inc.
    This file is part of elfutils.
    Written by Ulrich Drepper <drepper@redhat.com>, 2002.
 
 # include <config.h>
 #endif
 
+#include <assert.h>
 #include <dwarf.h>
+#include <search.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include <libdwP.h>
 
+static int
+get_offset_from (Dwarf_Die *die, int name, Dwarf_Word *retp)
+{
+  /* Get the appropriate attribute.  */
+  Dwarf_Attribute attr;
+  if (INTUSE(dwarf_attr) (die, name, &attr) == NULL)
+    return -1;
 
-ptrdiff_t
-dwarf_getmacros (die, callback, arg, offset)
-     Dwarf_Die *die;
-     int (*callback) (Dwarf_Macro *, void *);
-     void *arg;
-     ptrdiff_t offset;
+  /* Offset into the corresponding section.  */
+  return INTUSE(dwarf_formudata) (&attr, retp);
+}
+
+static int
+macro_op_compare (const void *p1, const void *p2)
 {
-  if (die == NULL)
+  const Dwarf_Macro_Op_Table *t1 = (const Dwarf_Macro_Op_Table *) p1;
+  const Dwarf_Macro_Op_Table *t2 = (const Dwarf_Macro_Op_Table *) p2;
+
+  if (t1->offset < t2->offset)
+    return -1;
+  if (t1->offset > t2->offset)
+    return 1;
+
+  if (t1->sec_index < t2->sec_index)
     return -1;
+  if (t1->sec_index > t2->sec_index)
+    return 1;
+
+  return 0;
+}
 
-  Elf_Data *d = die->cu->dbg->sectiondata[IDX_debug_macinfo];
-  if (unlikely (d == NULL) || unlikely (d->d_buf == NULL))
+static void
+build_table (Dwarf_Macro_Op_Table *table,
+            Dwarf_Macro_Op_Proto op_protos[static 255])
+{
+  unsigned ct = 0;
+  for (unsigned i = 1; i < 256; ++i)
+    if (op_protos[i - 1].forms != NULL)
+      table->table[table->opcodes[i - 1] = ct++] = op_protos[i - 1];
+    else
+      table->opcodes[i - 1] = 0xff;
+}
+
+#define MACRO_PROTO(NAME, ...)                                 \
+  Dwarf_Macro_Op_Proto NAME = ({                               \
+      static const uint8_t proto[] = {__VA_ARGS__};            \
+      (Dwarf_Macro_Op_Proto) {sizeof proto, proto};            \
+    })
+
+enum { macinfo_data_size = offsetof (Dwarf_Macro_Op_Table, table[5]) };
+static unsigned char macinfo_data[macinfo_data_size]
+       __attribute__ ((aligned (__alignof (Dwarf_Macro_Op_Table))));
+
+static __attribute__ ((constructor)) void
+init_macinfo_table (void)
+{
+  MACRO_PROTO (p_udata_str, DW_FORM_udata, DW_FORM_string);
+  MACRO_PROTO (p_udata_udata, DW_FORM_udata, DW_FORM_udata);
+  MACRO_PROTO (p_none);
+
+  Dwarf_Macro_Op_Proto op_protos[255] =
     {
-      __libdw_seterrno (DWARF_E_NO_ENTRY);
-      return -1;
+      [DW_MACINFO_define - 1] = p_udata_str,
+      [DW_MACINFO_undef - 1] = p_udata_str,
+      [DW_MACINFO_vendor_ext - 1] = p_udata_str,
+      [DW_MACINFO_start_file - 1] = p_udata_udata,
+      [DW_MACINFO_end_file - 1] = p_none,
+      /* If you are adding more elements to this array, increase
+        MACINFO_DATA_SIZE above.  */
+    };
+
+  Dwarf_Macro_Op_Table *macinfo_table = (void *) macinfo_data;
+  memset (macinfo_table, 0, sizeof macinfo_data);
+  build_table (macinfo_table, op_protos);
+  macinfo_table->sec_index = IDX_debug_macinfo;
+}
+
+static Dwarf_Macro_Op_Table *
+get_macinfo_table (Dwarf *dbg, Dwarf_Word macoff, Dwarf_Die *cudie)
+{
+  assert (cudie != NULL);
+
+  Dwarf_Attribute attr_mem, *attr
+    = INTUSE(dwarf_attr) (cudie, DW_AT_stmt_list, &attr_mem);
+  Dwarf_Off line_offset = (Dwarf_Off) -1;
+  if (attr != NULL)
+    if (unlikely (INTUSE(dwarf_formudata) (attr, &line_offset) != 0))
+      return NULL;
+
+  Dwarf_Macro_Op_Table *table = libdw_alloc (dbg, Dwarf_Macro_Op_Table,
+                                            macinfo_data_size, 1);
+  memcpy (table, macinfo_data, macinfo_data_size);
+
+  table->offset = macoff;
+  table->sec_index = IDX_debug_macinfo;
+  table->line_offset = line_offset;
+  table->is_64bit = cudie->cu->address_size == 8;
+  table->comp_dir = __libdw_getcompdir (cudie);
+
+  return table;
+}
+
+static Dwarf_Macro_Op_Table *
+get_table_for_offset (Dwarf *dbg, Dwarf_Word macoff,
+                     const unsigned char *readp,
+                     const unsigned char *const endp,
+                     Dwarf_Die *cudie)
+{
+  const unsigned char *startp = readp;
+
+  /* Request at least 3 bytes for header.  */
+  if (readp + 3 > endp)
+    {
+    invalid_dwarf:
+      __libdw_seterrno (DWARF_E_INVALID_DWARF);
+      return NULL;
     }
 
-  if (offset == 0)
+  uint16_t version = read_2ubyte_unaligned_inc (dbg, readp);
+  if (version != 4)
     {
-      /* Get the appropriate attribute.  */
-      Dwarf_Attribute attr;
-      if (INTUSE(dwarf_attr) (die, DW_AT_macro_info, &attr) == NULL)
-       return -1;
+      __libdw_seterrno (DWARF_E_INVALID_VERSION);
+      return NULL;
+    }
 
-      /* Offset into the .debug_macinfo section.  */
-      Dwarf_Word macoff;
-      if (INTUSE(dwarf_formudata) (&attr, &macoff) != 0)
-       return -1;
+  uint8_t flags = *readp++;
+  bool is_64bit = (flags & 0x1) != 0;
 
-      offset = macoff;
+  Dwarf_Off line_offset = (Dwarf_Off) -1;
+  if ((flags & 0x2) != 0)
+    {
+      line_offset = read_addr_unaligned_inc (is_64bit ? 8 : 4, dbg, readp);
+      if (readp > endp)
+       goto invalid_dwarf;
     }
-  if (unlikely (offset > (ptrdiff_t) d->d_size))
-    goto invalid;
+  else if (cudie != NULL)
+    {
+      Dwarf_Attribute attr_mem, *attr
+       = INTUSE(dwarf_attr) (cudie, DW_AT_stmt_list, &attr_mem);
+      if (attr != NULL)
+       if (unlikely (INTUSE(dwarf_formudata) (attr, &line_offset) != 0))
+         return NULL;
+    }
+
+  /* """The macinfo entry types defined in this standard may, but
+     might not, be described in the table""".
 
-  const unsigned char *readp = d->d_buf + offset;
-  const unsigned char *readendp = d->d_buf + d->d_size;
+     I.e. these may be present.  It's tempting to simply skip them,
+     but it's probably more correct to tolerate that a producer tweaks
+     the way certain opcodes are encoded, for whatever reasons.  */
 
-  if (readp == readendp)
-    return 0;
+  MACRO_PROTO (p_udata_str, DW_FORM_udata, DW_FORM_string);
+  MACRO_PROTO (p_udata_strp, DW_FORM_udata, DW_FORM_strp);
+  MACRO_PROTO (p_udata_udata, DW_FORM_udata, DW_FORM_udata);
+  MACRO_PROTO (p_secoffset, DW_FORM_sec_offset);
+  MACRO_PROTO (p_none);
 
-  while (readp < readendp)
+  Dwarf_Macro_Op_Proto op_protos[255] =
     {
-      unsigned int opcode = *readp++;
-      unsigned int u128;
-      unsigned int u128_2 = 0;
-      const char *str = NULL;
-      const unsigned char *endp;
+      [DW_MACRO_GNU_define - 1] = p_udata_str,
+      [DW_MACRO_GNU_undef - 1] = p_udata_str,
+      [DW_MACRO_GNU_define_indirect - 1] = p_udata_strp,
+      [DW_MACRO_GNU_undef_indirect - 1] = p_udata_strp,
+      [DW_MACRO_GNU_start_file - 1] = p_udata_udata,
+      [DW_MACRO_GNU_end_file - 1] = p_none,
+      [DW_MACRO_GNU_transparent_include - 1] = p_secoffset,
+      /* N.B. DW_MACRO_undef_indirectx, DW_MACRO_define_indirectx
+        should be added when 130313.1 is supported.  */
+    };
 
-      switch (opcode)
+  if ((flags & 0x4) != 0)
+    {
+      unsigned count = *readp++;
+      for (unsigned i = 0; i < count; ++i)
        {
-       case DW_MACINFO_define:
-       case DW_MACINFO_undef:
-       case DW_MACINFO_vendor_ext:
-         /*  For the first two opcodes the parameters are
-               line, string
-             For the latter
-               number, string.
-             We can treat these cases together.  */
-         get_uleb128 (u128, readp);
-
-         endp = memchr (readp, '\0', readendp - readp);
-         if (endp == NULL)
+         unsigned opcode = *readp++;
+
+         Dwarf_Macro_Op_Proto e;
+         if (readp >= endp)
            goto invalid;
+         get_uleb128 (e.nforms, readp, endp);
+         e.forms = readp;
+         op_protos[opcode - 1] = e;
+
+         readp += e.nforms;
+         if (readp > endp)
+           {
+           invalid:
+             __libdw_seterrno (DWARF_E_INVALID_DWARF);
+             return NULL;
+           }
+       }
+    }
+
+  size_t ct = 0;
+  for (unsigned i = 1; i < 256; ++i)
+    if (op_protos[i - 1].forms != NULL)
+      ++ct;
+
+  /* We support at most 0xfe opcodes defined in the table, as 0xff is
+     a value that means that given opcode is not stored at all.  But
+     that should be fine, as opcode 0 is not allocated.  */
+  assert (ct < 0xff);
+
+  size_t macop_table_size = offsetof (Dwarf_Macro_Op_Table, table[ct]);
+
+  Dwarf_Macro_Op_Table *table = libdw_alloc (dbg, Dwarf_Macro_Op_Table,
+                                            macop_table_size, 1);
+
+  *table = (Dwarf_Macro_Op_Table) {
+    .offset = macoff,
+    .sec_index = IDX_debug_macro,
+    .line_offset = line_offset,
+    .header_len = readp - startp,
+    .version = version,
+    .is_64bit = is_64bit,
+
+    /* NULL if CUDIE is NULL or DW_AT_comp_dir is absent.  */
+    .comp_dir = __libdw_getcompdir (cudie),
+  };
+  build_table (table, op_protos);
+
+  return table;
+}
+
+static Dwarf_Macro_Op_Table *
+cache_op_table (Dwarf *dbg, int sec_index, Dwarf_Off macoff,
+               const unsigned char *startp,
+               const unsigned char *const endp,
+               Dwarf_Die *cudie)
+{
+  Dwarf_Macro_Op_Table fake = { .offset = macoff, .sec_index = sec_index };
+  Dwarf_Macro_Op_Table **found = tfind (&fake, &dbg->macro_ops,
+                                       macro_op_compare);
+  if (found != NULL)
+    return *found;
+
+  Dwarf_Macro_Op_Table *table = sec_index == IDX_debug_macro
+    ? get_table_for_offset (dbg, macoff, startp, endp, cudie)
+    : get_macinfo_table (dbg, macoff, cudie);
 
-         str = (char *) readp;
-         readp = endp + 1;
-         break;
+  if (table == NULL)
+    return NULL;
 
-       case DW_MACINFO_start_file:
-         /* The two parameters are line and file index.  */
-         get_uleb128 (u128, readp);
-         get_uleb128 (u128_2, readp);
-         break;
+  Dwarf_Macro_Op_Table **ret = tsearch (table, &dbg->macro_ops,
+                                       macro_op_compare);
+  if (unlikely (ret == NULL))
+    {
+      __libdw_seterrno (DWARF_E_NOMEM);
+      return NULL;
+    }
 
-       case DW_MACINFO_end_file:
-         /* No parameters for this one.  */
-         u128 = 0;
-         break;
+  return *ret;
+}
 
-       case 0:
-         /* Nothing more to do.  */
-         return 0;
+static ptrdiff_t
+read_macros (Dwarf *dbg, int sec_index,
+            Dwarf_Off macoff, int (*callback) (Dwarf_Macro *, void *),
+            void *arg, ptrdiff_t offset, bool accept_0xff,
+            Dwarf_Die *cudie)
+{
+  Elf_Data *d = dbg->sectiondata[sec_index];
+  if (unlikely (d == NULL || d->d_buf == NULL))
+    {
+      __libdw_seterrno (DWARF_E_NO_ENTRY);
+      return -1;
+    }
 
-       default:
-         goto invalid;
+  if (unlikely (macoff >= d->d_size))
+    {
+      __libdw_seterrno (DWARF_E_INVALID_DWARF);
+      return -1;
+    }
+
+  const unsigned char *const startp = d->d_buf + macoff;
+  const unsigned char *const endp = d->d_buf + d->d_size;
+
+  Dwarf_Macro_Op_Table *table = cache_op_table (dbg, sec_index, macoff,
+                                               startp, endp, cudie);
+  if (table == NULL)
+    return -1;
+
+  if (offset == 0)
+    offset = table->header_len;
+
+  assert (offset >= 0);
+  assert (offset < endp - startp);
+  const unsigned char *readp = startp + offset;
+
+  while (readp < endp)
+    {
+      unsigned int opcode = *readp++;
+      if (opcode == 0)
+       /* Nothing more to do.  */
+       return 0;
+
+      if (unlikely (opcode == 0xff && ! accept_0xff))
+       {
+         /* See comment below at dwarf_getmacros for explanation of
+            why we are doing this.  */
+         __libdw_seterrno (DWARF_E_INVALID_OPCODE);
+         return -1;
+       }
+
+      unsigned int idx = table->opcodes[opcode - 1];
+      if (idx == 0xff)
+       {
+         __libdw_seterrno (DWARF_E_INVALID_OPCODE);
+         return -1;
        }
 
-      Dwarf_Macro mac;
-      mac.opcode = opcode;
-      mac.param1 = u128;
-      if (str == NULL)
-       mac.param2.u = u128_2;
+      Dwarf_Macro_Op_Proto *proto = &table->table[idx];
+
+      /* A fake CU with bare minimum data to fool dwarf_formX into
+        doing the right thing with the attributes that we put out.
+        We arbitrarily pretend it's version 4.  */
+      Dwarf_CU fake_cu = {
+       .dbg = dbg,
+       .version = 4,
+       .offset_size = table->is_64bit ? 8 : 4,
+       .startp = (void *) startp + offset,
+       .endp = (void *) endp,
+      };
+
+      Dwarf_Attribute *attributes;
+      Dwarf_Attribute *attributesp = NULL;
+      Dwarf_Attribute nattributes[8];
+      if (unlikely (proto->nforms > 8))
+       {
+         attributesp = malloc (sizeof (Dwarf_Attribute) * proto->nforms);
+         if (attributesp == NULL)
+           {
+             __libdw_seterrno (DWARF_E_NOMEM);
+             return -1;
+           }
+         attributes = attributesp;
+       }
       else
-       mac.param2.s = str;
+       attributes = &nattributes[0];
+
+      for (Dwarf_Word i = 0; i < proto->nforms; ++i)
+       {
+         /* We pretend this is a DW_AT_GNU_macros attribute so that
+            DW_FORM_sec_offset forms get correctly interpreted as
+            offset into .debug_macro.  */
+         attributes[i].code = DW_AT_GNU_macros;
+         attributes[i].form = proto->forms[i];
+         attributes[i].valp = (void *) readp;
+         attributes[i].cu = &fake_cu;
+
+         size_t len = __libdw_form_val_len (&fake_cu, proto->forms[i], readp);
+         if (unlikely (len == (size_t) -1))
+           {
+             free (attributesp);
+             return -1;
+           }
+
+         readp += len;
+       }
+
+      Dwarf_Macro macro = {
+       .table = table,
+       .opcode = opcode,
+       .attributes = attributes,
+      };
+
+      int res = callback (&macro, arg);
+      if (unlikely (attributesp != NULL))
+       free (attributesp);
+
+      if (res != DWARF_CB_OK)
+       return readp - startp;
+    }
+
+  return 0;
+}
+
+/* Token layout:
+
+   - The highest bit is used for distinguishing between callers that
+     know that opcode 0xff may have one of two incompatible meanings.
+     The mask that we use for selecting this bit is
+     DWARF_GETMACROS_START.
+
+   - The rest of the token (31 or 63 bits) encodes address inside the
+     macro unit.
+
+   Besides, token value of 0 signals end of iteration and -1 is
+   reserved for signaling errors.  That means it's impossible to
+   represent maximum offset of a .debug_macro unit to new-style
+   callers (which in practice decreases the permissible macro unit
+   size by another 1 byte).  */
+
+static ptrdiff_t
+token_from_offset (ptrdiff_t offset, bool accept_0xff)
+{
+  if (offset == -1 || offset == 0)
+    return offset;
+
+  /* Make sure the offset didn't overflow into the flag bit.  */
+  if ((offset & DWARF_GETMACROS_START) != 0)
+    {
+      __libdw_seterrno (DWARF_E_TOO_BIG);
+      return -1;
+    }
+
+  if (accept_0xff)
+    offset |= DWARF_GETMACROS_START;
+
+  return offset;
+}
+
+static ptrdiff_t
+offset_from_token (ptrdiff_t token, bool *accept_0xffp)
+{
+  *accept_0xffp = (token & DWARF_GETMACROS_START) != 0;
+  token &= ~DWARF_GETMACROS_START;
+
+  return token;
+}
 
-      if (callback (&mac, arg) != DWARF_CB_OK)
-       return readp - (const unsigned char *) d->d_buf;
+static ptrdiff_t
+gnu_macros_getmacros_off (Dwarf *dbg, Dwarf_Off macoff,
+                         int (*callback) (Dwarf_Macro *, void *),
+                         void *arg, ptrdiff_t offset, bool accept_0xff,
+                         Dwarf_Die *cudie)
+{
+  assert (offset >= 0);
+
+  if (macoff >= dbg->sectiondata[IDX_debug_macro]->d_size)
+    {
+      __libdw_seterrno (DWARF_E_INVALID_OFFSET);
+      return -1;
+    }
+
+  return read_macros (dbg, IDX_debug_macro, macoff,
+                     callback, arg, offset, accept_0xff, cudie);
+}
+
+static ptrdiff_t
+macro_info_getmacros_off (Dwarf *dbg, Dwarf_Off macoff,
+                         int (*callback) (Dwarf_Macro *, void *),
+                         void *arg, ptrdiff_t offset, Dwarf_Die *cudie)
+{
+  assert (offset >= 0);
+
+  return read_macros (dbg, IDX_debug_macinfo, macoff,
+                     callback, arg, offset, true, cudie);
+}
+
+ptrdiff_t
+dwarf_getmacros_off (Dwarf *dbg, Dwarf_Off macoff,
+                    int (*callback) (Dwarf_Macro *, void *),
+                    void *arg, ptrdiff_t token)
+{
+  if (dbg == NULL)
+    {
+      __libdw_seterrno (DWARF_E_NO_DWARF);
+      return -1;
+    }
+
+  bool accept_0xff;
+  ptrdiff_t offset = offset_from_token (token, &accept_0xff);
+  assert (accept_0xff);
+
+  offset = gnu_macros_getmacros_off (dbg, macoff, callback, arg, offset,
+                                    accept_0xff, NULL);
+
+  return token_from_offset (offset, accept_0xff);
+}
+
+ptrdiff_t
+dwarf_getmacros (Dwarf_Die *cudie, int (*callback) (Dwarf_Macro *, void *),
+                void *arg, ptrdiff_t token)
+{
+  if (cudie == NULL)
+    {
+      __libdw_seterrno (DWARF_E_NO_DWARF);
+      return -1;
+    }
+
+  /* This function might be called from a code that expects to see
+     DW_MACINFO_* opcodes, not DW_MACRO_{GNU_,}* ones.  It is fine to
+     serve most DW_MACRO_{GNU_,}* opcodes to such code, because those
+     whose values are the same as DW_MACINFO_* ones also have the same
+     behavior.  It is not very likely that a .debug_macro section
+     would only use the part of opcode space that it shares with
+     .debug_macinfo, but it is possible.  Serving the opcodes that are
+     only valid in DW_MACRO_{GNU_,}* domain is OK as well, because
+     clients in general need to be ready that newer standards define
+     more opcodes, and have coping mechanisms for unfamiliar opcodes.
+
+     The one exception to the above rule is opcode 0xff, which has
+     concrete semantics in .debug_macinfo, but falls into vendor block
+     in .debug_macro, and can be assigned to do whatever.  There is
+     some small probability that the two opcodes would look
+     superficially similar enough that a client would be confused and
+     misbehave as a result.  For this reason, we refuse to serve
+     through this interface 0xff's originating from .debug_macro
+     unless the TOKEN that we obtained indicates the call originates
+     from a new-style caller.  See above for details on what
+     information is encoded into tokens.  */
+
+  bool accept_0xff;
+  ptrdiff_t offset = offset_from_token (token, &accept_0xff);
+
+  /* DW_AT_macro_info */
+  if (dwarf_hasattr (cudie, DW_AT_macro_info))
+    {
+      Dwarf_Word macoff;
+      if (get_offset_from (cudie, DW_AT_macro_info, &macoff) != 0)
+       return -1;
+      offset = macro_info_getmacros_off (cudie->cu->dbg, macoff,
+                                        callback, arg, offset, cudie);
+    }
+  else
+    {
+      /* DW_AT_GNU_macros, DW_AT_macros */
+      Dwarf_Word macoff;
+      if (get_offset_from (cudie, DW_AT_GNU_macros, &macoff) != 0)
+       return -1;
+      offset = gnu_macros_getmacros_off (cudie->cu->dbg, macoff,
+                                        callback, arg, offset, accept_0xff,
+                                        cudie);
     }
 
-  /* If we come here the termination of the data for the CU is not
-     present.  */
- invalid:
-  __libdw_seterrno (DWARF_E_INVALID_DWARF);
-  return -1;
+  return token_from_offset (offset, accept_0xff);
 }