From http://www.jwz.org/xscreensaver/xscreensaver-5.33.tar.gz
[xscreensaver] / OSX / enable_gc.c
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 }