From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[xscreensaver] / OSX / enable_gc.c
1 /* xscreensaver, 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 require .saver bundles to use GC.
15  *   - OSX 10.7 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.x on OSX 10.9 no longer supports building executables
21  * that support GC, even optionally.  So there's no way to make XCode
22  * 5.x create a .saver bundle that will work on OSX 10.6. Though it
23  * 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 #include <assert.h>
51 #include <CoreFoundation/CFByteOrder.h>
52 #include <fcntl.h>
53 #include <mach-o/fat.h>
54 #include <mach-o/loader.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <sys/mman.h>
59 #include <sys/stat.h>
60 #include <unistd.h>
61
62 #define BOUNDS_CHECK(ptr, end) \
63   ((const void *)((ptr) + 1) <= (const void *)(end))
64
65 #define BOUNDS_CHECK_PRINT(ptr, end) \
66   (BOUNDS_CHECK(ptr, end) ? 1 : (_got_eof(), 0))
67
68 /*
69   This part is lifted from objc-private.h, because it's not present on
70   most OS X systems.
71   http://www.opensource.apple.com/source/objc4/objc4-551.1/runtime/objc-private.h
72  */
73
74 typedef struct {
75     uint32_t version; // currently 0
76     uint32_t flags;
77 } objc_image_info;
78
79 // masks for objc_image_info.flags
80 #define OBJC_IMAGE_IS_REPLACEMENT (1<<0)
81 #define OBJC_IMAGE_SUPPORTS_GC (1<<1)
82 #define OBJC_IMAGE_REQUIRES_GC (1<<2)
83 #define OBJC_IMAGE_OPTIMIZED_BY_DYLD (1<<3)
84 #define OBJC_IMAGE_SUPPORTS_COMPACTION (1<<4)  // might be re-assignable
85
86 /* End objc-private.h excerpt. */
87
88 static void
89 _got_eof()
90 {
91   fputs("Error: Unexpected EOF\n", stderr);
92 }
93
94 /* This will probably only ever run on OS X, so CoreFoundation is used here. */
95
96 static inline uint32_t 
97 _be_u32(uint32_t x) /* Big Endian _ Unsigned int 32-bit */
98 {
99   return (uint32_t)CFSwapInt32BigToHost(x);
100 }
101
102 static inline uint32_t
103 _le_u32(uint32_t x) /* Little Endian _ Unsigned int 32-bit */
104 {
105   return (uint32_t)CFSwapInt32LittleToHost(x);
106 }
107
108 static inline uint32_t
109 _le_u64(uint64_t x) /* Little Endian _ Unsigned int 64-bit */
110 {
111   return (uint32_t)CFSwapInt64LittleToHost(x);
112 }
113
114 static int 
115 _handle_x86_64(void *exec, void *exec_end)
116 {
117   const uint32_t *magic = exec;
118
119   if(!BOUNDS_CHECK_PRINT(magic, exec_end))
120     return EXIT_FAILURE;
121         
122   if(*magic != _le_u32(MH_MAGIC_64))
123     {
124       fputs("Error: Unknown magic number on Mach header.\n", stderr);
125       return EXIT_FAILURE;
126     }
127
128   /* Mach headers can be little-endian or big-endian. */
129
130   const struct mach_header_64 *hdr = (const struct mach_header_64 *)magic;
131   if(!BOUNDS_CHECK_PRINT(hdr, exec_end))
132     return EXIT_FAILURE;
133
134   if(hdr->cputype != _le_u32(CPU_TYPE_X86_64))
135     {
136       fputs("Error: Unexpected CPU type on Mach header.\n", stderr);
137       return EXIT_FAILURE;
138     }
139         
140   /* I may have missed a few _le_u32 calls, so watch out on PowerPC (heh). */
141         
142   if((const uint8_t *)hdr + _le_u32(hdr->sizeofcmds) >
143      (const uint8_t *)exec_end)
144     {
145       _got_eof();
146       return EXIT_FAILURE;
147     }
148
149   const struct load_command *load_cmd = (const struct load_command *)(hdr + 1);
150   const void *cmds_end = (const uint8_t *)load_cmd + hdr->sizeofcmds;
151         
152   for(unsigned i = 0; i != _le_u32(hdr->ncmds); ++i)
153     {
154       if(!BOUNDS_CHECK_PRINT(load_cmd, cmds_end))
155         return EXIT_FAILURE;
156
157       const struct load_command *next_load_cmd =
158         (const struct load_command *)((const uint8_t *)load_cmd +
159                                       _le_u32(load_cmd->cmdsize));
160
161       if(load_cmd->cmd == _le_u32(LC_SEGMENT_64))
162         {
163           const struct segment_command_64 *seg_cmd =
164             (const struct segment_command_64 *)load_cmd;
165           if(!BOUNDS_CHECK_PRINT(seg_cmd, cmds_end))
166             return EXIT_FAILURE;
167                         
168           if(!strcmp(seg_cmd->segname, "__DATA"))
169             {
170               const struct section_64 *sect =
171                 (const struct section_64 *)(seg_cmd + 1);
172               for(unsigned j = 0; j != _le_u32(seg_cmd->nsects); ++j)
173                 {
174                   if(!BOUNDS_CHECK_PRINT(&sect[j], next_load_cmd))
175                     return EXIT_FAILURE;
176
177                   if(strcmp(sect[j].segname, "__DATA"))
178                     fprintf(stderr,
179                             "Warning: segment name mismatch in __DATA,%.16s\n",
180                             sect[j].sectname);
181                                                 
182                   if(!memcmp(sect[j].sectname, "__objc_imageinfo", 16))
183                     { /* No null-terminator here. */
184                       if(_le_u64(sect[j].size) < sizeof(objc_image_info))
185                         {
186                           fputs("__DATA,__objc_imageinfo too small.\n",
187                                 stderr);
188                           return EXIT_FAILURE;
189                         }
190                                                 
191                       /*
192                         Not checked:
193                         - Overlapping segments.
194                         - Segments overlapping the load commands.
195                       */
196                                                 
197                       objc_image_info *img_info = (objc_image_info *)
198                         ((uint8_t *)exec + _le_u64(sect[j].offset));
199                                                 
200                       if(!BOUNDS_CHECK_PRINT(img_info, exec_end))
201                         return EXIT_FAILURE;
202                                                 
203                       if(img_info->version != 0)
204                         {
205                           fprintf(
206                                   stderr,
207                                   "Error: Unexpected version for "
208                                   "__DATA,__objc_imageinfo section. "
209                                   "Expected 0, got %d\n",
210                                   _le_u32(img_info->version));
211                           return EXIT_FAILURE;
212                         }
213                                                 
214                       if(img_info->flags &
215                          _le_u32(OBJC_IMAGE_REQUIRES_GC |
216                                  OBJC_IMAGE_SUPPORTS_GC))
217                         {
218                           fputs("Warning: Image already supports GC.\n",
219                                 stderr);
220                           return EXIT_SUCCESS;
221                         }
222
223                       /* Finally, do the work. */
224                       img_info->flags |= _le_u32(OBJC_IMAGE_SUPPORTS_GC);
225                       return EXIT_SUCCESS;
226                     }
227                 }
228             }
229         }
230                 
231       load_cmd = next_load_cmd;
232     }
233         
234   if((const void *)load_cmd > cmds_end)
235     {
236       _got_eof();
237       return EXIT_FAILURE;
238     }
239         
240   assert(load_cmd == cmds_end);
241         
242   fputs("Error: __DATA,__objc_imageinfo not found.\n", stderr);
243   return EXIT_FAILURE;
244 }
245
246 int
247 main(int argc, const char **argv)
248 {
249   if(argc != 2)
250     {
251       fprintf(stderr, "Usage: %s executable\n", argv[0]);
252       return EXIT_FAILURE;
253     }
254         
255   const char *exec_path = argv[1];
256
257   int fd = open(exec_path, O_RDWR | O_EXLOCK);
258         
259   if(fd < 0)
260     {
261       perror(exec_path);
262       return EXIT_FAILURE;
263     }
264
265   int result = EXIT_FAILURE;
266         
267   struct stat exec_stat;
268   if(fstat(fd, &exec_stat) < 0)
269     {
270       perror("fstat");
271       exit (1);
272     }
273   else
274     {
275       if(!(exec_stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
276         {
277           fprintf(stderr, "Warning: %s is not executable.\n", exec_path);
278           exit (1);
279         }
280                 
281       assert(exec_stat.st_size >= 0);
282                 
283       /*
284         TODO (technically): mmap(2) can throw signals if somebody unplugs
285         the file system. In such situations, a signal handler
286         should be used to ensure sensible recovery.
287       */
288
289       void *exec = NULL;
290                 
291       if(exec_stat.st_size)
292         {
293           exec = mmap(NULL, exec_stat.st_size, PROT_READ | PROT_WRITE,
294                       MAP_SHARED, fd, 0);
295           if(!exec)
296             perror("mmap");
297         }
298
299       if(exec || !exec_stat.st_size)
300         {
301           const void *exec_end = (const char *)exec + exec_stat.st_size;
302                         
303           const uint32_t *magic = exec;
304
305           if(BOUNDS_CHECK_PRINT(magic, exec_end))
306             {
307               if(*magic == _be_u32(FAT_MAGIC))
308                 {
309                   struct fat_header *hdr = (struct fat_header *)magic;
310                   if(BOUNDS_CHECK_PRINT(hdr, exec_end))
311                     {
312                       uint32_t nfat_arch = _be_u32(hdr->nfat_arch);
313                       const struct fat_arch *arch =
314                         (const struct fat_arch *)(hdr + 1);
315
316                       unsigned i = 0;
317                       for(;;)
318                         {
319                           if(i == nfat_arch)
320                             {
321                               /* This could be done for other architectures. */
322                               fputs("Error: x86_64 architecture not found.\n",
323                                     stderr);
324                               exit (1);
325                               break;
326                             }
327                                                         
328                           if(!BOUNDS_CHECK_PRINT(&arch[i], exec_end))
329                             break;
330
331                           if(arch[i].cputype == _be_u32(CPU_TYPE_X86_64))
332                             {
333                               uint8_t *obj_begin = 
334                                 (uint8_t *)exec + _be_u32(arch[i].offset);
335                               result = _handle_x86_64(obj_begin,
336                                                       obj_begin +
337                                                       _be_u32(arch[i].size));
338                               break;
339                             }
340
341                           ++i;
342                         }
343                     }
344                 }
345               else
346                 {
347                   fprintf(stderr,
348                        "Error: %s is not a recognized Mach binary format.\n",
349                           exec_path);
350                   exit (1);
351                 }
352             }
353                         
354           munmap(exec, exec_stat.st_size);
355         }
356     }
357         
358   close(fd);
359         
360   return result;
361 }