1 /* xscreensaver, Copyright (c) 2014 Dave Odell <dmo2118@gmail.com>
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
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.
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.
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
25 * The fix: after compiling, hand-hack the generated binary to tag the
26 * x86-64 arch with the OBJC_IMAGE_SUPPORTS_GC flag.
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
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.
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
51 #include <CoreFoundation/CFByteOrder.h>
53 #include <mach-o/fat.h>
54 #include <mach-o/loader.h>
62 #define BOUNDS_CHECK(ptr, end) \
63 ((const void *)((ptr) + 1) <= (const void *)(end))
65 #define BOUNDS_CHECK_PRINT(ptr, end) \
66 (BOUNDS_CHECK(ptr, end) ? 1 : (_got_eof(), 0))
69 This part is lifted from objc-private.h, because it's not present on
71 http://www.opensource.apple.com/source/objc4/objc4-551.1/runtime/objc-private.h
75 uint32_t version; // currently 0
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
86 /* End objc-private.h excerpt. */
91 fputs("Error: Unexpected EOF\n", stderr);
94 /* This will probably only ever run on OS X, so CoreFoundation is used here. */
96 static inline uint32_t
97 _be_u32(uint32_t x) /* Big Endian _ Unsigned int 32-bit */
99 return (uint32_t)CFSwapInt32BigToHost(x);
102 static inline uint32_t
103 _le_u32(uint32_t x) /* Little Endian _ Unsigned int 32-bit */
105 return (uint32_t)CFSwapInt32LittleToHost(x);
108 static inline uint32_t
109 _le_u64(uint64_t x) /* Little Endian _ Unsigned int 64-bit */
111 return (uint32_t)CFSwapInt64LittleToHost(x);
115 _handle_x86_64(void *exec, void *exec_end)
117 const uint32_t *magic = exec;
119 if(!BOUNDS_CHECK_PRINT(magic, exec_end))
122 if(*magic != _le_u32(MH_MAGIC_64))
124 fputs("Error: Unknown magic number on Mach header.\n", stderr);
128 /* Mach headers can be little-endian or big-endian. */
130 const struct mach_header_64 *hdr = (const struct mach_header_64 *)magic;
131 if(!BOUNDS_CHECK_PRINT(hdr, exec_end))
134 if(hdr->cputype != _le_u32(CPU_TYPE_X86_64))
136 fputs("Error: Unexpected CPU type on Mach header.\n", stderr);
140 /* I may have missed a few _le_u32 calls, so watch out on PowerPC (heh). */
142 if((const uint8_t *)hdr + _le_u32(hdr->sizeofcmds) >
143 (const uint8_t *)exec_end)
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;
152 for(unsigned i = 0; i != _le_u32(hdr->ncmds); ++i)
154 if(!BOUNDS_CHECK_PRINT(load_cmd, cmds_end))
157 const struct load_command *next_load_cmd =
158 (const struct load_command *)((const uint8_t *)load_cmd +
159 _le_u32(load_cmd->cmdsize));
161 if(load_cmd->cmd == _le_u32(LC_SEGMENT_64))
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))
168 if(!strcmp(seg_cmd->segname, "__DATA"))
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)
174 if(!BOUNDS_CHECK_PRINT(§[j], next_load_cmd))
177 if(strcmp(sect[j].segname, "__DATA"))
179 "Warning: segment name mismatch in __DATA,%.16s\n",
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))
186 fputs("__DATA,__objc_imageinfo too small.\n",
193 - Overlapping segments.
194 - Segments overlapping the load commands.
197 objc_image_info *img_info = (objc_image_info *)
198 ((uint8_t *)exec + _le_u64(sect[j].offset));
200 if(!BOUNDS_CHECK_PRINT(img_info, exec_end))
203 if(img_info->version != 0)
207 "Error: Unexpected version for "
208 "__DATA,__objc_imageinfo section. "
209 "Expected 0, got %d\n",
210 _le_u32(img_info->version));
215 _le_u32(OBJC_IMAGE_REQUIRES_GC |
216 OBJC_IMAGE_SUPPORTS_GC))
218 fputs("Warning: Image already supports GC.\n",
223 /* Finally, do the work. */
224 img_info->flags |= _le_u32(OBJC_IMAGE_SUPPORTS_GC);
231 load_cmd = next_load_cmd;
234 if((const void *)load_cmd > cmds_end)
240 assert(load_cmd == cmds_end);
242 fputs("Error: __DATA,__objc_imageinfo not found.\n", stderr);
247 main(int argc, const char **argv)
251 fprintf(stderr, "Usage: %s executable\n", argv[0]);
255 const char *exec_path = argv[1];
257 int fd = open(exec_path, O_RDWR | O_EXLOCK);
265 int result = EXIT_FAILURE;
267 struct stat exec_stat;
268 if(fstat(fd, &exec_stat) < 0)
275 if(!(exec_stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
277 fprintf(stderr, "Warning: %s is not executable.\n", exec_path);
281 assert(exec_stat.st_size >= 0);
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.
291 if(exec_stat.st_size)
293 exec = mmap(NULL, exec_stat.st_size, PROT_READ | PROT_WRITE,
299 if(exec || !exec_stat.st_size)
301 const void *exec_end = (const char *)exec + exec_stat.st_size;
303 const uint32_t *magic = exec;
305 if(BOUNDS_CHECK_PRINT(magic, exec_end))
307 if(*magic == _be_u32(FAT_MAGIC))
309 struct fat_header *hdr = (struct fat_header *)magic;
310 if(BOUNDS_CHECK_PRINT(hdr, exec_end))
312 uint32_t nfat_arch = _be_u32(hdr->nfat_arch);
313 const struct fat_arch *arch =
314 (const struct fat_arch *)(hdr + 1);
321 /* This could be done for other architectures. */
322 fputs("Error: x86_64 architecture not found.\n",
328 if(!BOUNDS_CHECK_PRINT(&arch[i], exec_end))
331 if(arch[i].cputype == _be_u32(CPU_TYPE_X86_64))
334 (uint8_t *)exec + _be_u32(arch[i].offset);
335 result = _handle_x86_64(obj_begin,
337 _be_u32(arch[i].size));
348 "Error: %s is not a recognized Mach binary format.\n",
354 munmap(exec, exec_stat.st_size);