"Fossies" - the Fresh Open Source Software Archive

Member "xscreensaver-5.44/OSX/enable_gc.c" (14 Mar 2015, 11832 Bytes) of package /linux/misc/xscreensaver-5.44.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "enable_gc.c" see the Fossies "Dox" file reference documentation.

    1 /* enable_gc.c, Copyright (c) 2014 Dave Odell <dmo2118@gmail.com>
    2  *
    3  * Permission to use, copy, modify, distribute, and sell this software and its
    4  * documentation for any purpose is hereby granted without fee, provided that
    5  * the above copyright notice appear in all copies and that both that
    6  * copyright notice and this permission notice appear in supporting
    7  * documentation.  No representations are made about the suitability of this
    8  * software for any purpose.  It is provided "as is" without express or
    9  * implied warranty.
   10  * 
   11  * The problem:
   12  * 
   13  *   - OSX 10.5 and earlier require .saver bundles to not use GC.
   14  *   - OSX 10.6 and 10.7 require .saver bundles to use GC.
   15  *   - OSX 10.8 and later require .saver bundles to not use GC.
   16  * 
   17  * So the way to build a portable .saver is to build it with "GC optional",
   18  * via "-fobjc-gc" on the x86-64 architecture.
   19  * 
   20  * But XCode 5.0.2 was the last version of XCode to support building
   21  * executables that support GC, even optionally.  So there's no way to make
   22  * the XCode that ships with OSX 10.9 create a .saver bundle that will work
   23  * on OSX 10.6 and 10.7. Though it will work on 10.5!
   24  * 
   25  * The fix: after compiling, hand-hack the generated binary to tag the
   26  * x86-64 arch with the OBJC_IMAGE_SUPPORTS_GC flag.
   27  * 
   28  * Specifically, OR the __DATA,__objc_imageinfo section with
   29  * "00 00 00 00 02 00 00 00"; normally this section is all zeros.
   30  * The __objc_imageinfo section corresponds to struct objc_image_info in:
   31  * http://www.opensource.apple.com/source/objc4/objc4-551.1/runtime/objc-private.h
   32  * You can use "otool -o Interference.saver/Contents/MacOS/Interference"
   33  * or "otool -s __DATA __objc_imageinfo Interference" to look at the
   34  * section.
   35  *
   36  * This means that the binary is marked as supporting GC, but there
   37  * are no actual GC-supporting write barriers compiled in!  So does it
   38  * actually ever GC?  Yes, apparently it does.  Apparently what's
   39  * going on is that incremental-GCs are doing nothing, but full-GCs
   40  * still collect ObjC objects properly.
   41  *
   42  * Mad Science!
   43  *
   44  * In the xscreensaver build process, the "enable_gc" target is a
   45  * dependency of "libjwxyz" (so that it gets built first) and is
   46  * invoked by "update-info-plist.pl" (so that it gets run on every
   47  * saver).
   48  *
   49  *
   50  * UPDATE, 2-Jun-2014:
   51  *
   52  * Actually, this seems not to be working.  We're seeing intermittent
   53  * crashes in malloc/calloc/free on 10.6 64 bit.  When compiled with
   54  * legit -fobjc-gc, those crashes don't occur.
   55  */
   56 
   57 #include <assert.h>
   58 #include <CoreFoundation/CFByteOrder.h>
   59 #include <fcntl.h>
   60 #include <mach-o/fat.h>
   61 #include <mach-o/loader.h>
   62 #include <stdio.h>
   63 #include <stdlib.h>
   64 #include <string.h>
   65 #include <sys/mman.h>
   66 #include <sys/stat.h>
   67 #include <unistd.h>
   68 
   69 #define BOUNDS_CHECK(ptr, end) \
   70   ((const void *)((ptr) + 1) <= (const void *)(end))
   71 
   72 #define BOUNDS_CHECK_PRINT(ptr, end) \
   73   (BOUNDS_CHECK(ptr, end) ? 1 : (_got_eof(), 0))
   74 
   75 /*
   76   This part is lifted from objc-private.h, because it's not present on
   77   most OS X systems.
   78   http://www.opensource.apple.com/source/objc4/objc4-551.1/runtime/objc-private.h
   79  */
   80 
   81 typedef struct {
   82     uint32_t version; // currently 0
   83     uint32_t flags;
   84 } objc_image_info;
   85 
   86 // masks for objc_image_info.flags
   87 #define OBJC_IMAGE_IS_REPLACEMENT (1<<0)
   88 #define OBJC_IMAGE_SUPPORTS_GC (1<<1)
   89 #define OBJC_IMAGE_REQUIRES_GC (1<<2)
   90 #define OBJC_IMAGE_OPTIMIZED_BY_DYLD (1<<3)
   91 #define OBJC_IMAGE_SUPPORTS_COMPACTION (1<<4)  // might be re-assignable
   92 
   93 /* End objc-private.h excerpt. */
   94 
   95 static void
   96 _got_eof()
   97 {
   98   fputs("Error: Unexpected EOF\n", stderr);
   99 }
  100 
  101 /* This will probably only ever run on OS X, so CoreFoundation is used here. */
  102 
  103 static inline uint32_t 
  104 _be_u32(uint32_t x) /* Big Endian _ Unsigned int 32-bit */
  105 {
  106   return (uint32_t)CFSwapInt32BigToHost(x);
  107 }
  108 
  109 static inline uint32_t
  110 _le_u32(uint32_t x) /* Little Endian _ Unsigned int 32-bit */
  111 {
  112   return (uint32_t)CFSwapInt32LittleToHost(x);
  113 }
  114 
  115 static inline uint32_t
  116 _le_u64(uint64_t x) /* Little Endian _ Unsigned int 64-bit */
  117 {
  118   return (uint32_t)CFSwapInt64LittleToHost(x);
  119 }
  120 
  121 static int 
  122 _handle_x86_64(void *exec, void *exec_end)
  123 {
  124   const uint32_t *magic = exec;
  125 
  126   if(!BOUNDS_CHECK_PRINT(magic, exec_end))
  127     return EXIT_FAILURE;
  128     
  129   if(*magic != _le_u32(MH_MAGIC_64))
  130     {
  131       fputs("Error: Unknown magic number on Mach header.\n", stderr);
  132       return EXIT_FAILURE;
  133     }
  134 
  135   /* Mach headers can be little-endian or big-endian. */
  136 
  137   const struct mach_header_64 *hdr = (const struct mach_header_64 *)magic;
  138   if(!BOUNDS_CHECK_PRINT(hdr, exec_end))
  139     return EXIT_FAILURE;
  140 
  141   if(hdr->cputype != _le_u32(CPU_TYPE_X86_64))
  142     {
  143       fputs("Error: Unexpected CPU type on Mach header.\n", stderr);
  144       return EXIT_FAILURE;
  145     }
  146     
  147   /* I may have missed a few _le_u32 calls, so watch out on PowerPC (heh). */
  148     
  149   if((const uint8_t *)hdr + _le_u32(hdr->sizeofcmds) >
  150      (const uint8_t *)exec_end)
  151     {
  152       _got_eof();
  153       return EXIT_FAILURE;
  154     }
  155 
  156   const struct load_command *load_cmd = (const struct load_command *)(hdr + 1);
  157   const void *cmds_end = (const uint8_t *)load_cmd + hdr->sizeofcmds;
  158     
  159   for(unsigned i = 0; i != _le_u32(hdr->ncmds); ++i)
  160     {
  161       if(!BOUNDS_CHECK_PRINT(load_cmd, cmds_end))
  162         return EXIT_FAILURE;
  163 
  164       const struct load_command *next_load_cmd =
  165         (const struct load_command *)((const uint8_t *)load_cmd +
  166                                       _le_u32(load_cmd->cmdsize));
  167 
  168       if(load_cmd->cmd == _le_u32(LC_SEGMENT_64))
  169         {
  170           const struct segment_command_64 *seg_cmd =
  171             (const struct segment_command_64 *)load_cmd;
  172           if(!BOUNDS_CHECK_PRINT(seg_cmd, cmds_end))
  173             return EXIT_FAILURE;
  174             
  175           if(!strcmp(seg_cmd->segname, "__DATA"))
  176             {
  177               const struct section_64 *sect =
  178                 (const struct section_64 *)(seg_cmd + 1);
  179               for(unsigned j = 0; j != _le_u32(seg_cmd->nsects); ++j)
  180                 {
  181                   if(!BOUNDS_CHECK_PRINT(&sect[j], next_load_cmd))
  182                     return EXIT_FAILURE;
  183 
  184                   if(strcmp(sect[j].segname, "__DATA"))
  185                     fprintf(stderr,
  186                             "Warning: segment name mismatch in __DATA,%.16s\n",
  187                             sect[j].sectname);
  188                         
  189                   if(!memcmp(sect[j].sectname, "__objc_imageinfo", 16))
  190                     { /* No null-terminator here. */
  191                       if(_le_u64(sect[j].size) < sizeof(objc_image_info))
  192                         {
  193                           fputs("__DATA,__objc_imageinfo too small.\n",
  194                                 stderr);
  195                           return EXIT_FAILURE;
  196                         }
  197                         
  198                       /*
  199                         Not checked:
  200                         - Overlapping segments.
  201                         - Segments overlapping the load commands.
  202                       */
  203                         
  204                       objc_image_info *img_info = (objc_image_info *)
  205                         ((uint8_t *)exec + _le_u64(sect[j].offset));
  206                         
  207                       if(!BOUNDS_CHECK_PRINT(img_info, exec_end))
  208                         return EXIT_FAILURE;
  209                         
  210                       if(img_info->version != 0)
  211                         {
  212                           fprintf(
  213                                   stderr,
  214                                   "Error: Unexpected version for "
  215                                   "__DATA,__objc_imageinfo section. "
  216                                   "Expected 0, got %d\n",
  217                                   _le_u32(img_info->version));
  218                           return EXIT_FAILURE;
  219                         }
  220                         
  221                       if(img_info->flags &
  222                          _le_u32(OBJC_IMAGE_REQUIRES_GC |
  223                                  OBJC_IMAGE_SUPPORTS_GC))
  224                         {
  225                           fputs("Warning: Image already supports GC.\n",
  226                                 stderr);
  227                           return EXIT_SUCCESS;
  228                         }
  229 
  230                       /* Finally, do the work. */
  231                       img_info->flags |= _le_u32(OBJC_IMAGE_SUPPORTS_GC);
  232                       return EXIT_SUCCESS;
  233                     }
  234                 }
  235             }
  236         }
  237         
  238       load_cmd = next_load_cmd;
  239     }
  240     
  241   if((const void *)load_cmd > cmds_end)
  242     {
  243       _got_eof();
  244       return EXIT_FAILURE;
  245     }
  246     
  247   assert(load_cmd == cmds_end);
  248     
  249   fputs("Error: __DATA,__objc_imageinfo not found.\n", stderr);
  250   return EXIT_FAILURE;
  251 }
  252 
  253 int
  254 main(int argc, const char **argv)
  255 {
  256   if(argc != 2)
  257     {
  258       fprintf(stderr, "Usage: %s executable\n", argv[0]);
  259       return EXIT_FAILURE;
  260     }
  261     
  262   const char *exec_path = argv[1];
  263 
  264   int fd = open(exec_path, O_RDWR | O_EXLOCK);
  265     
  266   if(fd < 0)
  267     {
  268       perror(exec_path);
  269       return EXIT_FAILURE;
  270     }
  271 
  272   int result = EXIT_FAILURE;
  273     
  274   struct stat exec_stat;
  275   if(fstat(fd, &exec_stat) < 0)
  276     {
  277       perror("fstat");
  278       exit (1);
  279     }
  280   else
  281     {
  282       if(!(exec_stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
  283         {
  284           fprintf(stderr, "Warning: %s is not executable.\n", exec_path);
  285           exit (1);
  286         }
  287         
  288       assert(exec_stat.st_size >= 0);
  289         
  290       /*
  291         TODO (technically): mmap(2) can throw signals if somebody unplugs
  292         the file system. In such situations, a signal handler
  293         should be used to ensure sensible recovery.
  294       */
  295 
  296       void *exec = NULL;
  297         
  298       if(exec_stat.st_size)
  299         {
  300           exec = mmap(NULL, exec_stat.st_size, PROT_READ | PROT_WRITE,
  301                       MAP_SHARED, fd, 0);
  302           if(!exec)
  303             perror("mmap");
  304         }
  305 
  306       if(exec || !exec_stat.st_size)
  307         {
  308           const void *exec_end = (const char *)exec + exec_stat.st_size;
  309             
  310           const uint32_t *magic = exec;
  311 
  312           if(BOUNDS_CHECK_PRINT(magic, exec_end))
  313             {
  314               if(*magic == _be_u32(FAT_MAGIC))
  315                 {
  316                   struct fat_header *hdr = (struct fat_header *)magic;
  317                   if(BOUNDS_CHECK_PRINT(hdr, exec_end))
  318                     {
  319                       uint32_t nfat_arch = _be_u32(hdr->nfat_arch);
  320                       const struct fat_arch *arch =
  321                         (const struct fat_arch *)(hdr + 1);
  322 
  323                       unsigned i = 0;
  324                       for(;;)
  325                         {
  326                           if(i == nfat_arch)
  327                             {
  328                               /* This could be done for other architectures. */
  329                               fputs("Error: x86_64 architecture not found.\n",
  330                                     stderr);
  331                               exit (1);
  332                               break;
  333                             }
  334                             
  335                           if(!BOUNDS_CHECK_PRINT(&arch[i], exec_end))
  336                             break;
  337 
  338                           if(arch[i].cputype == _be_u32(CPU_TYPE_X86_64))
  339                             {
  340                               uint8_t *obj_begin = 
  341                                 (uint8_t *)exec + _be_u32(arch[i].offset);
  342                               result = _handle_x86_64(obj_begin,
  343                                                       obj_begin +
  344                                                       _be_u32(arch[i].size));
  345                               break;
  346                             }
  347 
  348                           ++i;
  349                         }
  350                     }
  351                 }
  352               else
  353                 {
  354                   fprintf(stderr,
  355                        "Error: %s is not a recognized Mach binary format.\n",
  356                           exec_path);
  357                   exit (1);
  358                 }
  359             }
  360             
  361           munmap(exec, exec_stat.st_size);
  362         }
  363     }
  364     
  365   close(fd);
  366     
  367   return result;
  368 }