summaryrefslogtreecommitdiffstats
path: root/OSX/enable_gc.c
diff options
context:
space:
mode:
Diffstat (limited to 'OSX/enable_gc.c')
-rw-r--r--OSX/enable_gc.c368
1 files changed, 368 insertions, 0 deletions
diff --git a/OSX/enable_gc.c b/OSX/enable_gc.c
new file mode 100644
index 0000000..699c9a6
--- /dev/null
+++ b/OSX/enable_gc.c
@@ -0,0 +1,368 @@
+/* enable_gc.c, Copyright (c) 2014 Dave Odell <dmo2118@gmail.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * The problem:
+ *
+ * - OSX 10.5 and earlier require .saver bundles to not use GC.
+ * - OSX 10.6 and 10.7 require .saver bundles to use GC.
+ * - OSX 10.8 and later require .saver bundles to not use GC.
+ *
+ * So the way to build a portable .saver is to build it with "GC optional",
+ * via "-fobjc-gc" on the x86-64 architecture.
+ *
+ * But XCode 5.0.2 was the last version of XCode to support building
+ * executables that support GC, even optionally. So there's no way to make
+ * the XCode that ships with OSX 10.9 create a .saver bundle that will work
+ * on OSX 10.6 and 10.7. Though it will work on 10.5!
+ *
+ * The fix: after compiling, hand-hack the generated binary to tag the
+ * x86-64 arch with the OBJC_IMAGE_SUPPORTS_GC flag.
+ *
+ * Specifically, OR the __DATA,__objc_imageinfo section with
+ * "00 00 00 00 02 00 00 00"; normally this section is all zeros.
+ * The __objc_imageinfo section corresponds to struct objc_image_info in:
+ * http://www.opensource.apple.com/source/objc4/objc4-551.1/runtime/objc-private.h
+ * You can use "otool -o Interference.saver/Contents/MacOS/Interference"
+ * or "otool -s __DATA __objc_imageinfo Interference" to look at the
+ * section.
+ *
+ * This means that the binary is marked as supporting GC, but there
+ * are no actual GC-supporting write barriers compiled in! So does it
+ * actually ever GC? Yes, apparently it does. Apparently what's
+ * going on is that incremental-GCs are doing nothing, but full-GCs
+ * still collect ObjC objects properly.
+ *
+ * Mad Science!
+ *
+ * In the xscreensaver build process, the "enable_gc" target is a
+ * dependency of "libjwxyz" (so that it gets built first) and is
+ * invoked by "update-info-plist.pl" (so that it gets run on every
+ * saver).
+ *
+ *
+ * UPDATE, 2-Jun-2014:
+ *
+ * Actually, this seems not to be working. We're seeing intermittent
+ * crashes in malloc/calloc/free on 10.6 64 bit. When compiled with
+ * legit -fobjc-gc, those crashes don't occur.
+ */
+
+#include <assert.h>
+#include <CoreFoundation/CFByteOrder.h>
+#include <fcntl.h>
+#include <mach-o/fat.h>
+#include <mach-o/loader.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define BOUNDS_CHECK(ptr, end) \
+ ((const void *)((ptr) + 1) <= (const void *)(end))
+
+#define BOUNDS_CHECK_PRINT(ptr, end) \
+ (BOUNDS_CHECK(ptr, end) ? 1 : (_got_eof(), 0))
+
+/*
+ This part is lifted from objc-private.h, because it's not present on
+ most OS X systems.
+ http://www.opensource.apple.com/source/objc4/objc4-551.1/runtime/objc-private.h
+ */
+
+typedef struct {
+ uint32_t version; // currently 0
+ uint32_t flags;
+} objc_image_info;
+
+// masks for objc_image_info.flags
+#define OBJC_IMAGE_IS_REPLACEMENT (1<<0)
+#define OBJC_IMAGE_SUPPORTS_GC (1<<1)
+#define OBJC_IMAGE_REQUIRES_GC (1<<2)
+#define OBJC_IMAGE_OPTIMIZED_BY_DYLD (1<<3)
+#define OBJC_IMAGE_SUPPORTS_COMPACTION (1<<4) // might be re-assignable
+
+/* End objc-private.h excerpt. */
+
+static void
+_got_eof()
+{
+ fputs("Error: Unexpected EOF\n", stderr);
+}
+
+/* This will probably only ever run on OS X, so CoreFoundation is used here. */
+
+static inline uint32_t
+_be_u32(uint32_t x) /* Big Endian _ Unsigned int 32-bit */
+{
+ return (uint32_t)CFSwapInt32BigToHost(x);
+}
+
+static inline uint32_t
+_le_u32(uint32_t x) /* Little Endian _ Unsigned int 32-bit */
+{
+ return (uint32_t)CFSwapInt32LittleToHost(x);
+}
+
+static inline uint32_t
+_le_u64(uint64_t x) /* Little Endian _ Unsigned int 64-bit */
+{
+ return (uint32_t)CFSwapInt64LittleToHost(x);
+}
+
+static int
+_handle_x86_64(void *exec, void *exec_end)
+{
+ const uint32_t *magic = exec;
+
+ if(!BOUNDS_CHECK_PRINT(magic, exec_end))
+ return EXIT_FAILURE;
+
+ if(*magic != _le_u32(MH_MAGIC_64))
+ {
+ fputs("Error: Unknown magic number on Mach header.\n", stderr);
+ return EXIT_FAILURE;
+ }
+
+ /* Mach headers can be little-endian or big-endian. */
+
+ const struct mach_header_64 *hdr = (const struct mach_header_64 *)magic;
+ if(!BOUNDS_CHECK_PRINT(hdr, exec_end))
+ return EXIT_FAILURE;
+
+ if(hdr->cputype != _le_u32(CPU_TYPE_X86_64))
+ {
+ fputs("Error: Unexpected CPU type on Mach header.\n", stderr);
+ return EXIT_FAILURE;
+ }
+
+ /* I may have missed a few _le_u32 calls, so watch out on PowerPC (heh). */
+
+ if((const uint8_t *)hdr + _le_u32(hdr->sizeofcmds) >
+ (const uint8_t *)exec_end)
+ {
+ _got_eof();
+ return EXIT_FAILURE;
+ }
+
+ const struct load_command *load_cmd = (const struct load_command *)(hdr + 1);
+ const void *cmds_end = (const uint8_t *)load_cmd + hdr->sizeofcmds;
+
+ for(unsigned i = 0; i != _le_u32(hdr->ncmds); ++i)
+ {
+ if(!BOUNDS_CHECK_PRINT(load_cmd, cmds_end))
+ return EXIT_FAILURE;
+
+ const struct load_command *next_load_cmd =
+ (const struct load_command *)((const uint8_t *)load_cmd +
+ _le_u32(load_cmd->cmdsize));
+
+ if(load_cmd->cmd == _le_u32(LC_SEGMENT_64))
+ {
+ const struct segment_command_64 *seg_cmd =
+ (const struct segment_command_64 *)load_cmd;
+ if(!BOUNDS_CHECK_PRINT(seg_cmd, cmds_end))
+ return EXIT_FAILURE;
+
+ if(!strcmp(seg_cmd->segname, "__DATA"))
+ {
+ const struct section_64 *sect =
+ (const struct section_64 *)(seg_cmd + 1);
+ for(unsigned j = 0; j != _le_u32(seg_cmd->nsects); ++j)
+ {
+ if(!BOUNDS_CHECK_PRINT(&sect[j], next_load_cmd))
+ return EXIT_FAILURE;
+
+ if(strcmp(sect[j].segname, "__DATA"))
+ fprintf(stderr,
+ "Warning: segment name mismatch in __DATA,%.16s\n",
+ sect[j].sectname);
+
+ if(!memcmp(sect[j].sectname, "__objc_imageinfo", 16))
+ { /* No null-terminator here. */
+ if(_le_u64(sect[j].size) < sizeof(objc_image_info))
+ {
+ fputs("__DATA,__objc_imageinfo too small.\n",
+ stderr);
+ return EXIT_FAILURE;
+ }
+
+ /*
+ Not checked:
+ - Overlapping segments.
+ - Segments overlapping the load commands.
+ */
+
+ objc_image_info *img_info = (objc_image_info *)
+ ((uint8_t *)exec + _le_u64(sect[j].offset));
+
+ if(!BOUNDS_CHECK_PRINT(img_info, exec_end))
+ return EXIT_FAILURE;
+
+ if(img_info->version != 0)
+ {
+ fprintf(
+ stderr,
+ "Error: Unexpected version for "
+ "__DATA,__objc_imageinfo section. "
+ "Expected 0, got %d\n",
+ _le_u32(img_info->version));
+ return EXIT_FAILURE;
+ }
+
+ if(img_info->flags &
+ _le_u32(OBJC_IMAGE_REQUIRES_GC |
+ OBJC_IMAGE_SUPPORTS_GC))
+ {
+ fputs("Warning: Image already supports GC.\n",
+ stderr);
+ return EXIT_SUCCESS;
+ }
+
+ /* Finally, do the work. */
+ img_info->flags |= _le_u32(OBJC_IMAGE_SUPPORTS_GC);
+ return EXIT_SUCCESS;
+ }
+ }
+ }
+ }
+
+ load_cmd = next_load_cmd;
+ }
+
+ if((const void *)load_cmd > cmds_end)
+ {
+ _got_eof();
+ return EXIT_FAILURE;
+ }
+
+ assert(load_cmd == cmds_end);
+
+ fputs("Error: __DATA,__objc_imageinfo not found.\n", stderr);
+ return EXIT_FAILURE;
+}
+
+int
+main(int argc, const char **argv)
+{
+ if(argc != 2)
+ {
+ fprintf(stderr, "Usage: %s executable\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ const char *exec_path = argv[1];
+
+ int fd = open(exec_path, O_RDWR | O_EXLOCK);
+
+ if(fd < 0)
+ {
+ perror(exec_path);
+ return EXIT_FAILURE;
+ }
+
+ int result = EXIT_FAILURE;
+
+ struct stat exec_stat;
+ if(fstat(fd, &exec_stat) < 0)
+ {
+ perror("fstat");
+ exit (1);
+ }
+ else
+ {
+ if(!(exec_stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
+ {
+ fprintf(stderr, "Warning: %s is not executable.\n", exec_path);
+ exit (1);
+ }
+
+ assert(exec_stat.st_size >= 0);
+
+ /*
+ TODO (technically): mmap(2) can throw signals if somebody unplugs
+ the file system. In such situations, a signal handler
+ should be used to ensure sensible recovery.
+ */
+
+ void *exec = NULL;
+
+ if(exec_stat.st_size)
+ {
+ exec = mmap(NULL, exec_stat.st_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if(!exec)
+ perror("mmap");
+ }
+
+ if(exec || !exec_stat.st_size)
+ {
+ const void *exec_end = (const char *)exec + exec_stat.st_size;
+
+ const uint32_t *magic = exec;
+
+ if(BOUNDS_CHECK_PRINT(magic, exec_end))
+ {
+ if(*magic == _be_u32(FAT_MAGIC))
+ {
+ struct fat_header *hdr = (struct fat_header *)magic;
+ if(BOUNDS_CHECK_PRINT(hdr, exec_end))
+ {
+ uint32_t nfat_arch = _be_u32(hdr->nfat_arch);
+ const struct fat_arch *arch =
+ (const struct fat_arch *)(hdr + 1);
+
+ unsigned i = 0;
+ for(;;)
+ {
+ if(i == nfat_arch)
+ {
+ /* This could be done for other architectures. */
+ fputs("Error: x86_64 architecture not found.\n",
+ stderr);
+ exit (1);
+ break;
+ }
+
+ if(!BOUNDS_CHECK_PRINT(&arch[i], exec_end))
+ break;
+
+ if(arch[i].cputype == _be_u32(CPU_TYPE_X86_64))
+ {
+ uint8_t *obj_begin =
+ (uint8_t *)exec + _be_u32(arch[i].offset);
+ result = _handle_x86_64(obj_begin,
+ obj_begin +
+ _be_u32(arch[i].size));
+ break;
+ }
+
+ ++i;
+ }
+ }
+ }
+ else
+ {
+ fprintf(stderr,
+ "Error: %s is not a recognized Mach binary format.\n",
+ exec_path);
+ exit (1);
+ }
+ }
+
+ munmap(exec, exec_stat.st_size);
+ }
+ }
+
+ close(fd);
+
+ return result;
+}