[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Patches] ARM hard-float ABI - runtime linker checks



Hi,

I'm one of a team of people working on building Linux distributions
using the hard-float version of the ARM EABI, i.e. passing VFP
arguments in VFP registers rather than in the integer registers which
is the default. We're most of the way to having things working in
Debian, Ubuntu and Fedora, but one of the potential issues that we're
looking at is possible mixing of soft-float and hard-float binaries
causing confusion. Function calls passing floating point values into
libraries built for the wrong ABI in this situation may often *appear*
to work correctly (i.e. no crash), but will just return the wrong
values for calculations etc. This is clearly not desirable!

Several of the distributions are planning on moving to a multi-arch
setup and then moving the location of ld.so from /lib to /lib/$triplet
which will help mitigate this problem, but that may take a while to
filter through and I fear it won't solve all the potential problems.

As ld.so already checks for compatibility in various ways when loading
programs and libraries, I've looked into extending those checks
further to add support for recognising the ABI in use. Compilers for
ARM are already adding an attributes section to their output and that
section includes a tag to say if VFP args are being passed in VFP
registers. Readelf already knows how to decode these tags, so inspired
by that code I've written the attached patch.

I was worried that implementing extra checks here (and in particular
increasing the amount that ld.so needs to load from each binary) would
cause a performance degradation, so I've benchmarked program startup
for a range of different programs to measure the effect of this extra
work. The results show negligible effect - see [1] for full details.

I'm also concerned about how and where to link this code in. For now,
I've just hooked into the end of elf/dl-load.c:open_verify() and put
the code in the same file. I was hoping to find a cleaner way to add
some architecture-specific checks here, but was disappointed to not
find any. I'm thinking that this code would be better off in a
separate file and linked appropriately, and guidance on ways to do
that would be appreciated.

Comments please? :-)

[1] http://lists.linaro.org/pipermail/cross-distro/2011-October/000094.html

Cheers,
-- 
Steve McIntyre                                steve.mcintyre@xxxxxxxxxx
<http://www.linaro.org/> Linaro.org | Open source software for ARM SoCs
--- eglibc-2.13.old/elf/dl-load.c	2011-02-04 22:13:20.000000000 +0000
+++ eglibc-2.13/elf/dl-load.c	2011-10-21 14:19:31.141593883 +0000
@@ -1547,6 +1551,199 @@
     _dl_debug_printf_c ("\t\t(%s)\n", what);
 }
 
+#ifdef __arm__
+/* Read an unsigned leb128 value from P, store the value in VAL, return
+   P incremented past the value.  We assume that a word is large enough to
+   hold any value so encoded; if it is smaller than a pointer on some target,
+   pointers should not be leb128 encoded on that target.  */
+static const unsigned char *
+read_uleb128 (const unsigned char *p, unsigned long *val)
+{
+  unsigned int shift = 0;
+  unsigned char byte;
+  unsigned long result;
+
+  result = 0;
+  do
+    {
+      byte = *p++;
+      result |= (byte & 0x7f) << shift;
+      shift += 7;
+    }
+  while (byte & 0x80);
+
+  *val = result;
+  return p;
+}
+
+
+#define ATTR_TAG_FILE          1
+#define ABI_VFP_args          28
+#define VFP_ARGS_IN_VFP_REGS   1
+
+/* Check consistency of ABI in the ARM attributes. Search through the
+   section headers looking for the ARM attributes section, then
+   check the VFP_ARGS attribute. */
+static int
+check_arm_attributes_hfabi(int fd, ElfW(Ehdr) *ehdr, bool *is_hf)
+{
+  unsigned int i;
+  ElfW(Shdr) *shdrs;
+  int sh_size = ehdr->e_shentsize * ehdr->e_shnum;
+
+  /* Load in the section headers so we can look for the attributes
+   * section */
+  shdrs = alloca(sh_size);
+  __lseek (fd, ehdr->e_shoff, SEEK_SET);
+  if ((size_t) __libc_read (fd, (void *) shdrs, sh_size) != sh_size)
+    return -1;
+
+  for (i = 0; i < ehdr->e_shnum; i++)
+    {        
+      if (SHT_ARM_ATTRIBUTES == shdrs[i].sh_type)
+        {
+	  /* We've found a likely section. Load the contents and
+	   * check the tags */
+	  unsigned char *contents = alloca(shdrs[i].sh_size);
+	  unsigned char *p = contents;
+	  unsigned char * end;
+
+	  __lseek (fd, shdrs[i].sh_offset, SEEK_SET);
+	  if ((size_t) __libc_read (fd, (void *) contents, shdrs[i].sh_size) != shdrs[i].sh_size)
+	    return -1;
+
+	  /* Sanity-check the attribute section details. Make sure
+	   * that it's the "aeabi" section, that's all we care
+	   * about. */
+	  if (*p == 'A')
+            {
+	      unsigned long len = shdrs[i].sh_size - 1;
+	      unsigned long namelen;
+	      p++;
+                
+	      while (len > 0)
+                {
+		  unsigned long section_len = p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+		  if (section_len > len)
+                    {
+		      _dl_debug_printf_c ("    invalid section len %lu, max remaining %lu\n", section_len, len);
+		      section_len = len;
+                    }
+
+		  p += 4;                    
+		  len -= section_len;
+		  section_len -= 4;
+
+		  if (0 != strcmp((char *)p, "aeabi"))
+                    {
+		      _dl_debug_printf_c ("    ignoring unknown attr section %s\n", p);
+		      p += section_len;
+		      continue;
+                    }
+		  namelen = strlen((char *)p) + 1;
+		  p += namelen;
+		  section_len -= namelen;
+                    
+		  /* We're in a valid section. Walk through this
+		   * section looking for the tag we care about
+		   * (ABI_VFP_args) */
+		  while (section_len > 0)
+                    {
+		      unsigned long tag, val;
+		      unsigned long size;
+
+		      end = p;
+		      tag = (*p++);
+
+		      size = p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+		      if (size > section_len)
+                        {
+			  _dl_debug_printf_c ("    invalid subsection length %lu, max allowed %lu\n", size, section_len);
+			  size = section_len;
+                        }
+		      p += 4;
+                        
+		      section_len -= size;
+		      end += size;
+		      if (ATTR_TAG_FILE != tag)
+                        {
+			  /* ignore, we don't care */
+			  _dl_debug_printf_c ("    ignoring unknown subsection with type %u length %lu\n", tag, size);
+			  p = end;
+			  continue;
+                        }
+		      while (p < end)
+                        {
+			  p = read_uleb128 (p, &tag);
+			  /* Handle the different types of tag. */
+			  if ( (tag == 4) || (tag == 5) || (tag == 67) )
+                            {
+			      /* Special cases for string values */
+			      namelen = strlen((char *)p) + 1;
+			      p += namelen;
+                            }
+			  else
+                            {
+			      p = read_uleb128 (p, &val);
+                            }
+			  if ( (tag == ABI_VFP_args) && (val == VFP_ARGS_IN_VFP_REGS) )
+                            {
+			      *is_hf = 1;
+			      return 0;
+                            }
+                        }
+                    }
+                }
+            }                
+        }            
+    }
+    
+  return 0;
+}
+
+
+/* ARM-specific checks. Currently only checks for consistency of ABI
+   in terms of passing VFP args. */
+static int
+arm_specific_checks(int fd, const char *name, ElfW(Ehdr) *ehdr)
+{
+  static int all_hf = -1; /* unset */
+  bool is_hf = false;
+  int ret;
+
+  ret = check_arm_attributes_hfabi(fd, ehdr, &is_hf);
+  if (ret != 0)
+    return ret;
+    
+  if (all_hf == -1)
+    {
+      if (is_hf)
+	all_hf = 1;
+      else
+	all_hf = 0;
+      return 0;
+    }
+  else if (all_hf == 1 && !is_hf)
+    return 1;
+  else if (all_hf == 0 && is_hf)
+    return 1;
+}
+#endif
+
+
+/* Run any architecture-specific checks that might be needed for the
+   current architecture. */
+static int
+arch_specific_checks(int fd, const char *name, ElfW(Ehdr) *ehdr)
+{
+#ifdef __arm__
+    return arm_specific_checks(fd, name, ehdr);
+#endif
+
+  return 0;
+}
+
+
 /* Open a file and verify it is an ELF file for this architecture.  We
    ignore only ELF files for other architectures.  Non-ELF files and
    ELF files with different header information cause fatal errors since
@@ -1745,6 +1942,7 @@
 
       /* Check .note.ABI-tag if present.  */
       for (ph = phdr; ph < &phdr[ehdr->e_phnum]; ++ph)
+      {
 	if (ph->p_type == PT_NOTE && ph->p_filesz >= 32 && ph->p_align >= 4)
 	  {
 	    ElfW(Addr) size = ph->p_filesz;
@@ -1794,6 +1992,16 @@
 	  }
     }
 
+        if (-1 != fd)
+        {
+            if (arch_specific_checks(fd, name, ehdr))
+            {
+                errstring = N_("architecture-specific checks failed");
+                goto call_lose;
+            }
+        }
+    }
+
   return fd;
 }
 
_______________________________________________
Patches mailing list
Patches@xxxxxxxxxx
http://eglibc.org/cgi-bin/mailman/listinfo/patches