1 /* enable_gc.c, 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 and 10.7 require .saver bundles to use GC.
15 * - OSX 10.8 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.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!
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
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.
58 #include <CoreFoundation/CFByteOrder.h>
60 #include <mach-o/fat.h>
61 #include <mach-o/loader.h>
69 #define BOUNDS_CHECK(ptr, end) \
70 ((const void *)((ptr) + 1) <= (const void *)(end))
72 #define BOUNDS_CHECK_PRINT(ptr, end) \
73 (BOUNDS_CHECK(ptr, end) ? 1 : (_got_eof(), 0))
76 This part is lifted from objc-private.h, because it's not present on
78 http://www.opensource.apple.com/source/objc4/objc4-551.1/runtime/objc-private.h
82 uint32_t version; // currently 0
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
93 /* End objc-private.h excerpt. */
98 fputs("Error: Unexpected EOF\n", stderr);
101 /* This will probably only ever run on OS X, so CoreFoundation is used here. */
103 static inline uint32_t
104 _be_u32(uint32_t x) /* Big Endian _ Unsigned int 32-bit */
106 return (uint32_t)CFSwapInt32BigToHost(x);
109 static inline uint32_t
110 _le_u32(uint32_t x) /* Little Endian _ Unsigned int 32-bit */
112 return (uint32_t)CFSwapInt32LittleToHost(x);
115 static inline uint32_t
116 _le_u64(uint64_t x) /* Little Endian _ Unsigned int 64-bit */
118 return (uint32_t)CFSwapInt64LittleToHost(x);
122 _handle_x86_64(void *exec, void *exec_end)
124 const uint32_t *magic = exec;
126 if(!BOUNDS_CHECK_PRINT(magic, exec_end))
129 if(*magic != _le_u32(MH_MAGIC_64))
131 fputs("Error: Unknown magic number on Mach header.\n", stderr);
135 /* Mach headers can be little-endian or big-endian. */
137 const struct mach_header_64 *hdr = (const struct mach_header_64 *)magic;
138 if(!BOUNDS_CHECK_PRINT(hdr, exec_end))
141 if(hdr->cputype != _le_u32(CPU_TYPE_X86_64))
143 fputs("Error: Unexpected CPU type on Mach header.\n", stderr);
147 /* I may have missed a few _le_u32 calls, so watch out on PowerPC (heh). */
149 if((const uint8_t *)hdr + _le_u32(hdr->sizeofcmds) >
150 (const uint8_t *)exec_end)
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;
159 for(unsigned i = 0; i != _le_u32(hdr->ncmds); ++i)
161 if(!BOUNDS_CHECK_PRINT(load_cmd, cmds_end))
164 const struct load_command *next_load_cmd =
165 (const struct load_command *)((const uint8_t *)load_cmd +
166 _le_u32(load_cmd->cmdsize));
168 if(load_cmd->cmd == _le_u32(LC_SEGMENT_64))
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))
175 if(!strcmp(seg_cmd->segname, "__DATA"))
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)
181 if(!BOUNDS_CHECK_PRINT(§[j], next_load_cmd))
184 if(strcmp(sect[j].segname, "__DATA"))
186 "Warning: segment name mismatch in __DATA,%.16s\n",
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))
193 fputs("__DATA,__objc_imageinfo too small.\n",
200 - Overlapping segments.
201 - Segments overlapping the load commands.
204 objc_image_info *img_info = (objc_image_info *)
205 ((uint8_t *)exec + _le_u64(sect[j].offset));
207 if(!BOUNDS_CHECK_PRINT(img_info, exec_end))
210 if(img_info->version != 0)
214 "Error: Unexpected version for "
215 "__DATA,__objc_imageinfo section. "
216 "Expected 0, got %d\n",
217 _le_u32(img_info->version));
222 _le_u32(OBJC_IMAGE_REQUIRES_GC |
223 OBJC_IMAGE_SUPPORTS_GC))
225 fputs("Warning: Image already supports GC.\n",
230 /* Finally, do the work. */
231 img_info->flags |= _le_u32(OBJC_IMAGE_SUPPORTS_GC);
238 load_cmd = next_load_cmd;
241 if((const void *)load_cmd > cmds_end)
247 assert(load_cmd == cmds_end);
249 fputs("Error: __DATA,__objc_imageinfo not found.\n", stderr);
254 main(int argc, const char **argv)
258 fprintf(stderr, "Usage: %s executable\n", argv[0]);
262 const char *exec_path = argv[1];
264 int fd = open(exec_path, O_RDWR | O_EXLOCK);
272 int result = EXIT_FAILURE;
274 struct stat exec_stat;
275 if(fstat(fd, &exec_stat) < 0)
282 if(!(exec_stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
284 fprintf(stderr, "Warning: %s is not executable.\n", exec_path);
288 assert(exec_stat.st_size >= 0);
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.
298 if(exec_stat.st_size)
300 exec = mmap(NULL, exec_stat.st_size, PROT_READ | PROT_WRITE,
306 if(exec || !exec_stat.st_size)
308 const void *exec_end = (const char *)exec + exec_stat.st_size;
310 const uint32_t *magic = exec;
312 if(BOUNDS_CHECK_PRINT(magic, exec_end))
314 if(*magic == _be_u32(FAT_MAGIC))
316 struct fat_header *hdr = (struct fat_header *)magic;
317 if(BOUNDS_CHECK_PRINT(hdr, exec_end))
319 uint32_t nfat_arch = _be_u32(hdr->nfat_arch);
320 const struct fat_arch *arch =
321 (const struct fat_arch *)(hdr + 1);
328 /* This could be done for other architectures. */
329 fputs("Error: x86_64 architecture not found.\n",
335 if(!BOUNDS_CHECK_PRINT(&arch[i], exec_end))
338 if(arch[i].cputype == _be_u32(CPU_TYPE_X86_64))
341 (uint8_t *)exec + _be_u32(arch[i].offset);
342 result = _handle_x86_64(obj_begin,
344 _be_u32(arch[i].size));
355 "Error: %s is not a recognized Mach binary format.\n",
361 munmap(exec, exec_stat.st_size);