]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
On OSX, post-process the tool executables to adjust the Mach-O headers
authorJulian Seward <jseward@acm.org>
Wed, 6 Apr 2011 11:17:16 +0000 (11:17 +0000)
committerJulian Seward <jseward@acm.org>
Wed, 6 Apr 2011 11:17:16 +0000 (11:17 +0000)
in certain circumstances.  This works around a bug in the linker that
ships in Xcode 4.0.0 and 4.0.1 causing the 64-bit tool executables to
segfault at startup.  Fixes #267997.

git-svn-id: svn://svn.valgrind.org/valgrind/trunk@11686

coregrind/Makefile.am
coregrind/fixup_macho_loadcmds.c [new file with mode: 0644]
coregrind/link_tool_exe_darwin.in

index 5b50c4e1f3b2863c4d78ff80e346b87c8597ae1e..e34999b767cec21f5e7ac21aa2c2db09a4876c6f 100644 (file)
@@ -441,3 +441,18 @@ clean-local: clean-noinst_DSYMS
 
 install-exec-local: install-noinst_PROGRAMS install-noinst_DSYMS
 
+#----------------------------------------------------------------------------
+# Darwin linker kludges
+#----------------------------------------------------------------------------
+
+if VGCONF_OS_IS_DARWIN
+
+BUILT_SOURCES += fixup_macho_loadcmds
+fixup_macho_loadcmds: fixup_macho_loadcmds.c
+       $(CC) -g -Wall -o fixup_macho_loadcmds fixup_macho_loadcmds.c
+
+CLEANFILES += fixup_macho_loadcmds
+
+endif
+
+EXTRA_DIST += fixup_macho_loadcmds.c
diff --git a/coregrind/fixup_macho_loadcmds.c b/coregrind/fixup_macho_loadcmds.c
new file mode 100644 (file)
index 0000000..37e77dd
--- /dev/null
@@ -0,0 +1,605 @@
+
+/* Derived from Valgrind sources, coregrind/m_debuginfo/readmacho.c.
+   GPL 2+ therefore.
+
+   Can be compiled as either a 32- or 64-bit program (doesn't matter).
+*/
+
+/* What does this program do?  In short it postprocesses tool
+   executables on MacOSX, after linking using /usr/bin/ld.  This is so
+   as to work around a bug in the linker on Xcode 4.0.0 and Xcode
+   4.0.1.  Xcode versions prior to 4.0.0 are unaffected.
+
+   The tracking bug is https://bugs.kde.org/show_bug.cgi?id=267997
+
+   The bug causes 64-bit tool executables to segfault at startup,
+   because:
+
+   Comparing the MachO load commands vs a (working) tool executable
+   that was created by Xcode 3.2.x, it appears that the new linker has
+   partially ignored the build system's request to place the tool
+   executable's stack at a non standard location.  The build system
+   tells the linker "-stack_addr 0x134000000 -stack_size 0x800000".
+
+   With the Xcode 3.2 linker those flags produce two results:
+
+   (1) A load command to allocate the stack at the said location:
+          Load command 3
+                cmd LC_SEGMENT_64
+            cmdsize 72
+            segname __UNIXSTACK
+             vmaddr 0x0000000133800000
+             vmsize 0x0000000000800000
+            fileoff 2285568
+           filesize 0
+            maxprot 0x00000007
+           initprot 0x00000003
+             nsects 0
+              flags 0x0
+
+   (2) A request (in LC_UNIXTHREAD) to set %rsp to the correct value
+       at process startup, 0x134000000.
+
+   With Xcode 4.0.1, (1) is missing but (2) is still present.  The
+   tool executable therefore starts up with %rsp pointing to unmapped
+   memory and faults almost instantly.
+
+   The workaround implemented by this program is documented in comment
+   8 of bug 267997, viz:
+
+   One really sick workaround is to observe that the executables
+   contain a redundant MachO load command:
+
+      Load command 2
+            cmd LC_SEGMENT_64
+        cmdsize 72
+        segname __LINKEDIT
+         vmaddr 0x0000000138dea000
+         vmsize 0x00000000000ad000
+        fileoff 2658304
+       filesize 705632
+        maxprot 0x00000007
+       initprot 0x00000001
+         nsects 0
+          flags 0x0
+
+   The described section presumably contains information intended for
+   the dynamic linker, but is irrelevant because this is a statically
+   linked executable.  Hence it might be possible to postprocess the
+   executables after linking, to overwrite this entry with the
+   information that would have been in the missing __UNIXSTACK entry.
+   I tried this by hand (with a binary editor) earlier and got
+   something that worked.
+*/
+
+#define DEBUGPRINTING 0
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+
+#undef PLAT_x86_darwin
+#undef PLAT_amd64_darwin
+
+#if defined(__APPLE__) && defined(__i386__)
+#  define PLAT_x86_darwin 1
+#elif defined(__APPLE__) && defined(__x86_64__)
+#  define PLAT_amd64_darwin 1
+#else
+#  error "Can't be compiled on this platform"
+#endif
+
+#include <mach-o/loader.h>
+#include <mach-o/nlist.h>
+#include <mach-o/fat.h>
+#include <mach/i386/thread_status.h>
+
+
+typedef  unsigned char   UChar;
+typedef    signed char   Char;
+typedef           char   HChar; /* signfulness depends on host */
+
+typedef  unsigned int    UInt;
+typedef    signed int    Int;
+
+typedef  unsigned char   Bool;
+#define  True   ((Bool)1)
+#define  False  ((Bool)0)
+
+typedef  unsigned long   UWord;
+
+typedef  UWord           SizeT;
+typedef  UWord           Addr;
+
+typedef  unsigned long long int   ULong;
+typedef    signed long long int   Long;
+
+
+
+__attribute__((noreturn))
+void fail ( HChar* msg )
+{
+   fprintf(stderr, "fixup_macho_loadcmds: fail: %s\n", msg);
+   exit(1);
+}
+
+
+/*------------------------------------------------------------*/
+/*---                                                      ---*/
+/*--- Mach-O file mapping/unmapping helpers                ---*/
+/*---                                                      ---*/
+/*------------------------------------------------------------*/
+
+typedef
+   struct {
+      /* These two describe the entire mapped-in ("primary") image,
+         fat headers, kitchen sink, whatnot: the entire file.  The
+         image is mapped into img[0 .. img_szB-1]. */
+      UChar* img;
+      SizeT  img_szB;
+      /* These two describe the Mach-O object of interest, which is
+         presumably somewhere inside the primary image.
+         map_image_aboard() below, which generates this info, will
+         carefully check that the macho_ fields denote a section of
+         memory that falls entirely inside img[0 .. img_szB-1]. */
+      UChar* macho_img;
+      SizeT  macho_img_szB;
+   }
+   ImageInfo;
+
+
+Bool is_macho_object_file( const void* buf, SizeT szB )
+{
+   /* (JRS: the Mach-O headers might not be in this mapped data,
+      because we only mapped a page for this initial check,
+      or at least not very much, and what's at the start of the file
+      is in general a so-called fat header.  The Mach-O object we're
+      interested in could be arbitrarily far along the image, and so
+      we can't assume its header will fall within this page.) */
+
+   /* But we can say that either it's a fat object, in which case it
+      begins with a fat header, or it's unadorned Mach-O, in which
+      case it starts with a normal header.  At least do what checks we
+      can to establish whether or not we're looking at something
+      sane. */
+
+   const struct fat_header*  fh_be = buf;
+   const struct mach_header_64* mh    = buf;
+
+   assert(buf);
+   if (szB < sizeof(struct fat_header))
+      return False;
+   if (ntohl(fh_be->magic) == FAT_MAGIC)
+      return True;
+
+   if (szB < sizeof(struct mach_header_64))
+      return False;
+   if (mh->magic == MH_MAGIC_64)
+      return True;
+
+   return False;
+}
+
+
+/* Unmap an image mapped in by map_image_aboard. */
+static void unmap_image ( /*MOD*/ImageInfo* ii )
+{
+   Int r;
+   assert(ii->img);
+   assert(ii->img_szB > 0);
+   r = munmap( ii->img, ii->img_szB );
+   /* Do we care if this fails?  I suppose so; it would indicate
+      some fairly serious snafu with the mapping of the file. */
+   assert( !r );
+   memset(ii, 0, sizeof(*ii));
+}
+
+
+/* Map a given fat or thin object aboard, find the thin part if
+   necessary, do some checks, and write details of both the fat and
+   thin parts into *ii.  Returns 32 (and leaves the file unmapped) if
+   the thin part is a 32 bit file.  Returns 64 if it's a 64 bit file.
+   Does not return on failure.  Guarantees to return pointers to a
+   valid(ish) Mach-O image if it succeeds. */
+static Int map_image_aboard ( /*OUT*/ImageInfo* ii, HChar* filename )
+{
+   memset(ii, 0, sizeof(*ii));
+
+   /* First off, try to map the thing in. */
+   { SizeT  size;
+     Int r, fd;
+     struct stat stat_buf;
+
+     r = stat(filename, &stat_buf);
+     if (r)
+        fail("Can't stat image (to determine its size)?!");
+     size = stat_buf.st_size;
+
+     fd = open(filename, O_RDWR, 0);
+     if (fd == -1)
+        fail("Can't open image for possible modification!");
+     if (DEBUGPRINTING)
+        printf("size %lu fd %d\n", size, fd);
+     void* v = mmap ( NULL, size, PROT_READ|PROT_WRITE,
+                                  MAP_FILE|MAP_SHARED, fd, 0 );
+     if (v == MAP_FAILED) {
+        perror("mmap failed");
+        fail("Can't mmap image for possible modification!");
+     }
+
+     close(fd);
+
+     ii->img     = (UChar*)v;
+     ii->img_szB = size;
+   }
+
+   /* Now it's mapped in and we have .img and .img_szB set.  Look for
+      the embedded Mach-O object.  If not findable, unmap and fail. */
+   { struct fat_header*  fh_be;
+     struct fat_header   fh;
+     struct mach_header_64* mh;
+     
+     // Assume initially that we have a thin image, and update
+     // these if it turns out to be fat.
+     ii->macho_img     = ii->img;
+     ii->macho_img_szB = ii->img_szB;
+
+     // Check for fat header.
+     if (ii->img_szB < sizeof(struct fat_header))
+        fail("Invalid Mach-O file (0 too small).");
+
+     // Fat header is always BIG-ENDIAN
+     fh_be = (struct fat_header *)ii->img;
+     fh.magic = ntohl(fh_be->magic);
+     fh.nfat_arch = ntohl(fh_be->nfat_arch);
+     if (fh.magic == FAT_MAGIC) {
+        // Look for a good architecture.
+        struct fat_arch *arch_be;
+        struct fat_arch arch;
+        Int f;
+        if (ii->img_szB < sizeof(struct fat_header)
+                          + fh.nfat_arch * sizeof(struct fat_arch))
+           fail("Invalid Mach-O file (1 too small).");
+
+        for (f = 0, arch_be = (struct fat_arch *)(fh_be+1); 
+             f < fh.nfat_arch;
+             f++, arch_be++) {
+           Int cputype;
+#          if defined(PLAT_x86_darwin)
+           cputype = CPU_TYPE_X86;
+#          elif defined(PLAT_amd64_darwin)
+           cputype = CPU_TYPE_X86_64;
+#          else
+#            error "unknown architecture"
+#          endif
+           arch.cputype    = ntohl(arch_be->cputype);
+           arch.cpusubtype = ntohl(arch_be->cpusubtype);
+           arch.offset     = ntohl(arch_be->offset);
+           arch.size       = ntohl(arch_be->size);
+           if (arch.cputype == cputype) {
+              if (ii->img_szB < arch.offset + arch.size)
+                 fail("Invalid Mach-O file (2 too small).");
+              ii->macho_img     = ii->img + arch.offset;
+              ii->macho_img_szB = arch.size;
+              break;
+           }
+        }
+        if (f == fh.nfat_arch)
+           fail("No acceptable architecture found in fat file.");
+     }
+
+     /* Sanity check what we found. */
+
+     /* assured by logic above */
+     assert(ii->img_szB >= sizeof(struct fat_header));
+
+     if (ii->macho_img_szB < sizeof(struct mach_header_64))
+        fail("Invalid Mach-O file (3 too small).");
+
+     if (ii->macho_img_szB > ii->img_szB)
+        fail("Invalid Mach-O file (thin bigger than fat).");
+
+     if (ii->macho_img >= ii->img
+         && ii->macho_img + ii->macho_img_szB <= ii->img + ii->img_szB) {
+        /* thin entirely within fat, as expected */
+     } else {
+        fail("Invalid Mach-O file (thin not inside fat).");
+     }
+
+     mh = (struct mach_header_64 *)ii->macho_img;
+     if (mh->magic == MH_MAGIC) {
+        assert(ii->img);
+        assert(ii->macho_img);
+        assert(ii->img_szB > 0);
+        assert(ii->macho_img_szB > 0);
+        assert(ii->macho_img >= ii->img);
+        assert(ii->macho_img + ii->macho_img_szB <= ii->img + ii->img_szB);
+        return 32;
+     }
+     if (mh->magic != MH_MAGIC_64)
+        fail("Invalid Mach-O file (bad magic).");
+
+     if (ii->macho_img_szB < sizeof(struct mach_header_64) + mh->sizeofcmds)
+        fail("Invalid Mach-O file (4 too small).");
+   }
+
+   assert(ii->img);
+   assert(ii->macho_img);
+   assert(ii->img_szB > 0);
+   assert(ii->macho_img_szB > 0);
+   assert(ii->macho_img >= ii->img);
+   assert(ii->macho_img + ii->macho_img_szB <= ii->img + ii->img_szB);
+   return 64;
+}
+
+
+/*------------------------------------------------------------*/
+/*---                                                      ---*/
+/*--- Mach-O top-level processing                          ---*/
+/*---                                                      ---*/
+/*------------------------------------------------------------*/
+
+void modify_macho_loadcmds ( HChar* filename,
+                             ULong  expected_stack_start,
+                             ULong  expected_stack_size )
+{
+   ImageInfo ii;
+   memset(&ii, 0, sizeof(ii));
+
+   Int size = map_image_aboard( &ii, filename );
+   if (size == 32) {
+      fprintf(stderr, "fixup_macho_loadcmds:   Is 32-bit MachO file;"
+              " no modifications needed.\n");
+      goto out;
+   }
+
+   assert(size == 64);
+
+   assert(ii.macho_img != NULL && ii.macho_img_szB > 0);
+
+   /* Poke around in the Mach-O header, to find some important
+      stuff.
+      * the location of the __UNIXSTACK load command, if any
+      * the location of the __LINKEDIT load command, if any
+      * the initial RSP value as stated in the LC_UNIXTHREAD
+   */
+
+   /* The collected data */
+   ULong init_rsp = 0;
+   Bool  have_rsp = False;
+   struct segment_command_64* seg__unixstack = NULL;
+   struct segment_command_64* seg__linkedit  = NULL;
+
+   /* Loop over the load commands and fill in the above 4 variables. */
+
+   { struct mach_header_64 *mh = (struct mach_header_64 *)ii.macho_img;
+      struct load_command *cmd;
+      Int c;
+
+      for (c = 0, cmd = (struct load_command *)(mh+1);
+           c < mh->ncmds;
+           c++, cmd = (struct load_command *)(cmd->cmdsize
+                                              + (unsigned long)cmd)) {
+         if (DEBUGPRINTING)
+            printf("load cmd: offset %4lu   size %3d   kind %2d = ",
+                   (unsigned long)((UChar*)cmd - (UChar*)ii.macho_img),
+                   cmd->cmdsize, cmd->cmd);
+
+         switch (cmd->cmd) {
+            case LC_SEGMENT_64:
+               if (DEBUGPRINTING)
+                  printf("LC_SEGMENT_64");
+               break;
+            case LC_SYMTAB:
+               if (DEBUGPRINTING)
+                  printf("LC_SYMTAB");
+               break;
+            case LC_UUID:
+               if (DEBUGPRINTING)
+                  printf("LC_UUID");
+               break;
+            case LC_UNIXTHREAD:
+               if (DEBUGPRINTING)
+                  printf("LC_UNIXTHREAD");
+               break;
+            default:
+                  printf("???");
+               fail("unexpected load command in Mach header");
+            break;
+         }
+         if (DEBUGPRINTING)
+            printf("\n");
+
+         /* Note what the stated initial RSP value is, so we can
+            check it is as expected. */
+         if (cmd->cmd == LC_UNIXTHREAD) {
+            struct thread_command* tcmd = (struct thread_command*)cmd;
+            UInt* w32s = (UInt*)( (UChar*)tcmd + sizeof(*tcmd) );
+            if (DEBUGPRINTING)
+               printf("UnixThread: flavor %u = ", w32s[0]);
+            if (w32s[0] == x86_THREAD_STATE64 && !have_rsp) {
+               if (DEBUGPRINTING)
+                  printf("x86_THREAD_STATE64\n");
+               x86_thread_state64_t* state64
+                  = (x86_thread_state64_t*)(&w32s[2]);
+               have_rsp = True;
+               init_rsp = state64->__rsp;
+               if (DEBUGPRINTING)
+                  printf("rsp = 0x%llx\n", init_rsp);
+            } else {
+               if (DEBUGPRINTING)
+                  printf("???");
+            }
+            if (DEBUGPRINTING)
+               printf("\n");
+         }
+
+         if (cmd->cmd == LC_SEGMENT_64) {
+            struct segment_command_64 *seg = (struct segment_command_64 *)cmd;
+            if (0 == strcmp(seg->segname, "__LINKEDIT"))
+               seg__linkedit = seg;
+            if (0 == strcmp(seg->segname, "__UNIXSTACK"))
+               seg__unixstack = seg;
+         }
+
+      }
+   }
+
+   /*
+      Actions are then as follows:
+
+      * (always) check the RSP value is as expected, and abort if not
+
+      * if there's a UNIXSTACK load command, check it is as expected.
+        If not abort, if yes, do nothing more.
+
+      * (so there's no UNIXSTACK load command).  if there's a LINKEDIT
+        load command, check if it is minimally usable (has 0 for
+        nsects and flags).  If yes, convert it to a UNIXSTACK load
+        command.  If there is none, or is unusable, then we're out of
+        options and have to abort.
+   */
+   if (!have_rsp)
+      fail("Can't find / check initial RSP setting");
+   if (init_rsp != expected_stack_start + expected_stack_size)
+      fail("Initial RSP value not as expected");
+
+   fprintf(stderr, "fixup_macho_loadcmds:   "
+                   "initial RSP is as expected (0x%llx)\n",
+                   expected_stack_start + expected_stack_size );
+
+   if (seg__unixstack) {
+      struct segment_command_64 *seg = seg__unixstack;
+      if (seg->vmaddr != expected_stack_start)
+         fail("has __UNIXSTACK, but wrong ::vmaddr");
+      if (seg->vmsize != expected_stack_size)
+         fail("has __UNIXSTACK, but wrong ::vmsize");
+      if (seg->maxprot != 7)
+         fail("has __UNIXSTACK, but wrong ::maxprot (should be 7)");
+      if (seg->initprot != 3)
+         fail("has __UNIXSTACK, but wrong ::initprot (should be 3)");
+      if (seg->nsects != 0)
+         fail("has __UNIXSTACK, but wrong ::nsects (should be 0)");
+      if (seg->flags != 0)
+         fail("has __UNIXSTACK, but wrong ::flags (should be 0)");
+      /* looks ok */
+      fprintf(stderr, "fixup_macho_loadcmds:   "
+              "acceptable __UNIXSTACK present; no modifications.\n" );
+      goto out;
+   }
+
+   if (seg__linkedit) {
+      struct segment_command_64 *seg = seg__linkedit;
+      if (seg->nsects != 0)
+         fail("has __LINKEDIT, but wrong ::nsects (should be 0)");
+      if (seg->flags != 0)
+         fail("has __LINKEDIT, but wrong ::flags (should be 0)");
+      fprintf(stderr, "fixup_macho_loadcmds:   "
+              "no __UNIXSTACK present.\n" );
+      fprintf(stderr, "fixup_macho_loadcmds:   "
+              "converting __LINKEDIT to __UNIXSTACK.\n" );
+      strcpy(seg->segname, "__UNIXSTACK");
+      seg->vmaddr   = expected_stack_start;
+      seg->vmsize   = expected_stack_size;
+      seg->fileoff  = 0;
+      seg->filesize = 0;
+      seg->maxprot  = 7;
+      seg->initprot = 3;
+      /* success */
+      goto out;
+   }
+
+   /* out of options */
+   fail("no __UNIXSTACK found and no usable __LINKEDIT found; "
+        "out of options.");
+   /* NOTREACHED */
+
+  out:
+   if (ii.img)
+      unmap_image(&ii);
+}
+
+
+static Bool is_plausible_tool_exe_name ( HChar* nm )
+{
+   HChar* p;
+   if (!nm)
+      return False;
+
+   // Does it end with this string?
+   p = strstr(nm, "-x86-darwin");
+   if (p && 0 == strcmp(p, "-x86-darwin"))
+      return True;
+
+   p = strstr(nm, "-amd64-darwin");
+   if (p && 0 == strcmp(p, "-amd64-darwin"))
+      return True;
+
+   return False;
+}
+
+
+int main ( int argc, char** argv )
+{
+   Int   r;
+   ULong req_stack_addr = 0;
+   ULong req_stack_size = 0;
+
+   if (argc != 4)
+      fail("args: -stack_addr-arg -stack_size-arg "
+           "name-of-tool-executable-to-modify"); 
+
+   r= sscanf(argv[1], "0x%llx", &req_stack_addr);
+   if (r != 1) fail("invalid stack_addr arg");
+
+   r= sscanf(argv[2], "0x%llx", &req_stack_size);
+   if (r != 1) fail("invalid stack_size arg");
+
+   fprintf(stderr, "fixup_macho_loadcmds: "
+           "requested stack_addr (top) 0x%llx, "
+           "stack_size 0x%llx\n", req_stack_addr, req_stack_size );
+
+   if (!is_plausible_tool_exe_name(argv[3]))
+      fail("implausible tool exe name -- not of the form *-{x86,amd64}-darwin");
+
+   fprintf(stderr, "fixup_macho_loadcmds: examining tool exe: %s\n", 
+           argv[3] );
+   modify_macho_loadcmds( argv[3], req_stack_addr - req_stack_size,
+                          req_stack_size );
+
+   return 0;
+}
+
+/*
+      cmd LC_SEGMENT_64
+  cmdsize 72
+  segname __LINKEDIT
+   vmaddr 0x0000000138dea000
+   vmsize 0x00000000000ad000
+  fileoff 2658304
+ filesize 705632
+  maxprot 0x00000007
+ initprot 0x00000001
+   nsects 0
+    flags 0x0
+*/
+
+/*
+      cmd LC_SEGMENT_64
+  cmdsize 72
+  segname __UNIXSTACK
+   vmaddr 0x0000000133800000
+   vmsize 0x0000000000800000
+  fileoff 2498560
+ filesize 0
+  maxprot 0x00000007
+ initprot 0x00000003
+   nsects 0
+    flags 0x0
+*/
index be1a1d9d3cbefa60295b8f6a6d576ee4d580354c..0b2919bc362674b811034837469f5b4c43f53900 100644 (file)
@@ -160,14 +160,31 @@ foreach my $n (2 .. $#ARGV) {
     }
 }
 
-#print "link_tool_exe_darwin: $cmd\n";
-
+print "link_tool_exe_darwin: $cmd\n";
 
 # Execute the command:
 my $r = system("$cmd");
 
-if ($r == 0) {
-    exit 0;
-} else {
-    exit 1;
+if ($r != 0) {
+   exit 1;
 }
+
+
+# and now kludge the tool exe
+# see bug 267997
+
+$cmd = "../coregrind/fixup_macho_loadcmds";
+$cmd = "$cmd $stack_addr_str $stack_size_str $outname";
+
+print "link_tool_exe_darwin: $cmd\n";
+
+my $r = system("$cmd");
+
+if ($r != 0) {
+   exit 1;
+}
+
+
+
+
+exit 0;