1/*
2 * Copyright (c) 2007-2009 Apple Inc.  All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <stdlib.h>
25#include <unistd.h>
26#include <string.h>
27#include <stdio.h>
28#include <stdbool.h>
29#include <fcntl.h>
30#include <limits.h>
31#include <sys/stat.h>
32#include <mach-o/fat.h>
33#include <mach-o/arch.h>
34#include <mach-o/loader.h>
35
36// from "objc-private.h"
37// masks for objc_image_info.flags
38#define OBJC_IMAGE_IS_REPLACEMENT (1<<0)
39#define OBJC_IMAGE_SUPPORTS_GC (1<<1)
40#define OBJC_IMAGE_REQUIRES_GC (1<<2)
41#define OBJC_IMAGE_OPTIMIZED_BY_DYLD (1<<3)
42
43bool debug;
44bool verbose;
45bool quiet;
46bool rrOnly;
47bool patch = true;
48bool unpatch = false;
49
50struct gcinfo {
51        bool hasObjC;
52        bool hasInfo;
53        uint32_t flags;
54        char *arch;
55} GCInfo[4];
56
57void dumpinfo(char *filename);
58
59int Errors = 0;
60char *FileBase;
61size_t FileSize;
62const char *FileName;
63
64int main(int argc, char *argv[]) {
65    //NSAutoreleasePool *pool = [NSAutoreleasePool new];
66    int i;
67    //dumpinfo("/System/Library/Frameworks/AppKit.framework/AppKit");
68    if (argc == 1) {
69        printf("Usage: markgc [-v] [-r] [--] library_or_executable_image [image2 ...]\n");
70        printf(" changes Garbage Collection readiness of named images, ignoring those without ObjC segments\n");
71        printf("  -p        - patch RR binary to (apparently) support GC (default)\n");
72        printf("  -u        - unpatch GC binary to RR only\n");
73        printf("\nAuthor: blaine@apple.com\n");
74        exit(0);
75    }
76    for (i = 1; i < argc; ++i) {
77        if (!strcmp(argv[i], "-v")) {
78            verbose = true;
79            continue;
80        }
81        if (!strcmp(argv[i], "-d")) {
82            debug = true;
83            continue;
84        }
85        if (!strcmp(argv[i], "-q")) {
86            quiet = true;
87            continue;
88        }
89        if (!strcmp(argv[i], "-p")) {
90            patch = true;
91            continue;
92        }
93        if (!strcmp(argv[i], "-u")) {
94            unpatch = true;
95            patch = false;
96            continue;
97        }
98        dumpinfo(argv[i]);
99    }
100    return Errors;
101}
102
103struct imageInfo {
104    uint32_t version;
105    uint32_t flags;
106};
107
108void patchFile(uint32_t value, size_t offset) {
109    int fd = open(FileName, 1);
110    off_t lresult = lseek(fd, offset, SEEK_SET);
111    if (lresult == -1) {
112        printf("couldn't seek to 0x%lx position on fd %d\n", offset, fd);
113        ++Errors;
114        return;
115    }
116    size_t wresult = write(fd, &value, 4);
117    if (wresult != 4) {
118        ++Errors;
119        printf("didn't write new value\n");
120    }
121    else {
122        printf("patched %s at offset 0x%lx\n", FileName, offset);
123    }
124    close(fd);
125}
126
127uint32_t iiflags(struct imageInfo *ii, size_t size, bool needsFlip) {
128    if (needsFlip) {
129        ii->flags = OSSwapInt32(ii->flags);
130    }
131    if (debug) printf("flags->%x, nitems %lu\n", ii->flags, size/sizeof(struct imageInfo));
132    uint32_t support_mask = OBJC_IMAGE_SUPPORTS_GC;
133    uint32_t flags = ii->flags;
134    if (patch && (flags & support_mask) != support_mask) {
135        //printf("will patch %s at offset %p\n", FileName, (char*)(&ii->flags) - FileBase);
136        uint32_t newvalue = flags | support_mask;
137        if (needsFlip) newvalue = OSSwapInt32(newvalue);
138        patchFile(newvalue, (char*)(&ii->flags) - FileBase);
139    }
140    if (unpatch && (flags & support_mask) == support_mask) {
141        uint32_t newvalue = flags & ~support_mask;
142        if (needsFlip) newvalue = OSSwapInt32(newvalue);
143        patchFile(newvalue, (char*)(&ii->flags) - FileBase);
144    }
145    for(unsigned niis = 1; niis < size/sizeof(struct imageInfo); ++niis) {
146        if (needsFlip) ii[niis].flags = OSSwapInt32(ii[niis].flags);
147        if (ii[niis].flags != flags) {
148            // uh, oh.
149            printf("XXX ii[%d].flags %x != ii[0].flags %x\n", niis, ii[niis].flags, flags);
150            ++Errors;
151        }
152    }
153    return flags;
154}
155
156void printflags(uint32_t flags) {
157    if (flags & 0x1) printf(" F&C");
158    if (flags & 0x2) printf(" GC");
159    if (flags & 0x4) printf(" GC-only");
160    else printf(" RR");
161}
162
163/*
164void doimageinfo(struct imageInfo *ii, uint32_t size, bool needsFlip) {
165    uint32_t flags = iiflags(ii, size, needsFlip);
166    printflags(flags);
167}
168*/
169
170
171void dosect32(void *start, struct section *sect, bool needsFlip, struct gcinfo *gcip) {
172    if (debug) printf("section %s from segment %s\n", sect->sectname, sect->segname);
173    if (strcmp(sect->segname, "__OBJC")) return;
174    gcip->hasObjC = true;
175    if (strcmp(sect->sectname, "__image_info")) return;
176    gcip->hasInfo = true;
177    if (needsFlip) {
178        sect->offset = OSSwapInt32(sect->offset);
179        sect->size = OSSwapInt32(sect->size);
180    }
181    // these guys aren't inline - they point elsewhere
182    gcip->flags = iiflags(start + sect->offset, sect->size, needsFlip);
183}
184
185void dosect64(void *start, struct section_64 *sect, bool needsFlip, struct gcinfo *gcip) {
186    if (debug) printf("section %s from segment %s\n", sect->sectname, sect->segname);
187    if (strcmp(sect->segname, "__OBJC") && strcmp(sect->segname, "__DATA")) return;
188    if (strcmp(sect->sectname, "__image_info") && strncmp(sect->sectname, "__objc_imageinfo", 16)) return;
189    gcip->hasObjC = true;
190    gcip->hasInfo = true;
191    if (needsFlip) {
192        sect->offset = OSSwapInt32(sect->offset);
193        sect->size = OSSwapInt64(sect->size);
194    }
195    // these guys aren't inline - they point elsewhere
196    gcip->flags = iiflags(start + sect->offset, (size_t)sect->size, needsFlip);
197}
198
199void doseg32(void *start, struct segment_command *seg, bool needsFlip, struct gcinfo *gcip) {
200    // lets do sections
201    if (needsFlip) {
202        seg->fileoff = OSSwapInt32(seg->fileoff);
203        seg->nsects = OSSwapInt32(seg->nsects);
204    }
205    if (debug) printf("segment name: %s, nsects %d\n", seg->segname, seg->nsects);
206    if (seg->segname[0]) {
207        if (strcmp("__OBJC", seg->segname)) return;
208    }
209    struct section *sect = (struct section *)(seg + 1);
210    for (uint32_t nsects = 0; nsects < seg->nsects; ++nsects) {
211        // sections directly follow
212
213        dosect32(start, sect + nsects, needsFlip, gcip);
214    }
215}
216void doseg64(void *start, struct segment_command_64 *seg, bool needsFlip, struct gcinfo *gcip) {
217    if (debug) printf("segment name: %s\n", seg->segname);
218    if (seg->segname[0] && strcmp("__OBJC", seg->segname) && strcmp("__DATA", seg->segname)) return;
219    gcip->hasObjC = true;
220    // lets do sections
221    if (needsFlip) {
222        seg->fileoff = OSSwapInt64(seg->fileoff);
223        seg->nsects = OSSwapInt32(seg->nsects);
224    }
225    struct section_64 *sect = (struct section_64 *)(seg + 1);
226    for (uint32_t nsects = 0; nsects < seg->nsects; ++nsects) {
227        // sections directly follow
228
229        dosect64(start, sect + nsects, needsFlip, gcip);
230    }
231}
232
233#if 0
234/*
235 * A variable length string in a load command is represented by an lc_str
236 * union.  The strings are stored just after the load command structure and
237 * the offset is from the start of the load command structure.  The size
238 * of the string is reflected in the cmdsize field of the load command.
239 * Once again any padded bytes to bring the cmdsize field to a multiple
240 * of 4 bytes must be zero.
241 */
242union lc_str {
243	uint32_t	offset;	/* offset to the string */
244#ifndef __LP64__
245	char		*ptr;	/* pointer to the string */
246#endif
247};
248
249struct dylib {
250    union lc_str  name;			/* library's path name */
251    uint32_t timestamp;			/* library's build time stamp */
252    uint32_t current_version;		/* library's current version number */
253    uint32_t compatibility_version;	/* library's compatibility vers number*/
254};
255
256 * A dynamically linked shared library (filetype == MH_DYLIB in the mach header)
257 * contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library.
258 * An object that uses a dynamically linked shared library also contains a
259 * dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or
260 * LC_REEXPORT_DYLIB) for each library it uses.
261
262struct dylib_command {
263	uint32_t	cmd;		/* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,
264					   LC_REEXPORT_DYLIB */
265	uint32_t	cmdsize;	/* includes pathname string */
266	struct dylib	dylib;		/* the library identification */
267};
268#endif
269
270void dodylib(void *start, struct dylib_command *dylibCmd, bool needsFlip) {
271    if (!verbose) return;
272    if (needsFlip) {
273    }
274    size_t count = dylibCmd->cmdsize - sizeof(struct dylib_command);
275    //printf("offset is %d, count is %d\n", dylibCmd->dylib.name.offset, count);
276    if (dylibCmd->dylib.name.offset > count) return;
277    //printf("-->%.*s<---", count, ((void *)dylibCmd)+dylibCmd->dylib.name.offset);
278    if (verbose) printf("load %s\n", ((char *)dylibCmd)+dylibCmd->dylib.name.offset);
279}
280
281struct load_command *doloadcommand(void *start, struct load_command *lc, bool needsFlip, bool is32, struct gcinfo *gcip) {
282    if (needsFlip) {
283        lc->cmd = OSSwapInt32(lc->cmd);
284        lc->cmdsize = OSSwapInt32(lc->cmdsize);
285    }
286
287    switch(lc->cmd) {
288    case LC_SEGMENT_64:
289	if (debug) printf("...segment64\n");
290        if (is32) printf("XXX we have a 64-bit segment in a 32-bit mach-o\n");
291        doseg64(start, (struct segment_command_64 *)lc, needsFlip, gcip);
292        break;
293    case LC_SEGMENT:
294	if (debug) printf("...segment32\n");
295        doseg32(start, (struct segment_command *)lc, needsFlip, gcip);
296        break;
297    case LC_SYMTAB: if (debug) printf("...dynamic symtab\n"); break;
298    case LC_DYSYMTAB: if (debug) printf("...symtab\n"); break;
299    case LC_LOAD_DYLIB:
300        dodylib(start, (struct dylib_command *)lc, needsFlip);
301        break;
302    case LC_SUB_UMBRELLA: if (debug) printf("...load subumbrella\n"); break;
303    default:    if (debug) printf("cmd is %x\n", lc->cmd); break;
304    }
305
306    return (struct load_command *)((void *)lc + lc->cmdsize);
307}
308
309void doofile(void *start, size_t size, struct gcinfo *gcip) {
310    struct mach_header *mh = (struct mach_header *)start;
311    bool isFlipped = false;
312    if (mh->magic == MH_CIGAM || mh->magic == MH_CIGAM_64) {
313        if (debug) printf("(flipping)\n");
314        mh->magic = OSSwapInt32(mh->magic);
315        mh->cputype = OSSwapInt32(mh->cputype);
316        mh->cpusubtype = OSSwapInt32(mh->cpusubtype);
317        mh->filetype = OSSwapInt32(mh->filetype);
318        mh->ncmds = OSSwapInt32(mh->ncmds);
319        mh->sizeofcmds = OSSwapInt32(mh->sizeofcmds);
320        mh->flags = OSSwapInt32(mh->flags);
321        isFlipped = true;
322    }
323    if (rrOnly && mh->filetype != MH_DYLIB) return; // ignore executables
324    NXArchInfo *info = (NXArchInfo *)NXGetArchInfoFromCpuType(mh->cputype, mh->cpusubtype);
325    //printf("%s:", info->description);
326    gcip->arch = (char *)info->description;
327    //if (debug) printf("...description is %s\n", info->description);
328    bool is32 = !(mh->cputype & CPU_ARCH_ABI64);
329    if (debug) printf("is 32? %d\n", is32);
330    if (debug) printf("filetype -> %d\n", mh->filetype);
331    if (debug) printf("ncmds -> %d\n", mh->ncmds);
332    struct load_command *lc = (is32 ? (struct load_command *)(mh + 1) : (struct load_command *)((struct mach_header_64 *)start + 1));
333    unsigned ncmds;
334    for (ncmds = 0; ncmds < mh->ncmds; ++ncmds) {
335        lc = doloadcommand(start, lc, isFlipped, is32, gcip);
336    }
337    //printf("\n");
338}
339
340void initGCInfo() {
341    bzero((void *)GCInfo, sizeof(GCInfo));
342}
343
344void printGCInfo(char *filename) {
345    if (!GCInfo[0].hasObjC) return; // don't bother
346    // verify that flags are all the same
347    uint32_t flags = GCInfo[0].flags;
348    bool allSame = true;
349    for (int i = 1; i < 4 && GCInfo[i].arch; ++i) {
350        if (flags != GCInfo[i].flags) {
351            allSame = false;
352        }
353    }
354    if (rrOnly) {
355        if (allSame && (flags & 0x2))
356            return;
357        printf("*** not all GC in %s:\n", filename);
358    }
359    if (allSame && !verbose) {
360        printf("%s:", filename);
361        printflags(flags);
362        printf("\n");
363    }
364    else {
365        printf("%s:\n", filename);
366        for (int i = 0; i < 4 && GCInfo[i].arch; ++i) {
367            printf("%s:", GCInfo[i].arch);
368            printflags(GCInfo[i].flags);
369            printf("\n");
370        }
371        printf("\n");
372    }
373}
374
375void dofat(void *start) {
376    struct fat_header *fh = start;
377    bool needsFlip = false;
378    if (fh->magic == FAT_CIGAM) {
379        fh->nfat_arch = OSSwapInt32(fh->nfat_arch);
380        needsFlip = true;
381    }
382    if (debug) printf("%d architectures\n", fh->nfat_arch);
383    unsigned narchs;
384    struct fat_arch *arch_ptr = (struct fat_arch *)(fh + 1);
385    for (narchs = 0; narchs < fh->nfat_arch; ++narchs) {
386        if (debug) printf("doing arch %d\n", narchs);
387        if (needsFlip) {
388            arch_ptr->offset = OSSwapInt32(arch_ptr->offset);
389            arch_ptr->size = OSSwapInt32(arch_ptr->size);
390        }
391        doofile(start+arch_ptr->offset, arch_ptr->size, &GCInfo[narchs]);
392        arch_ptr++;
393    }
394}
395
396bool openFile(const char *filename) {
397    FileName = filename;
398    // get size
399    struct stat statb;
400    int fd = open(filename, 0);
401    if (fd < 0) {
402        printf("couldn't open %s for reading\n", filename);
403        return false;
404    }
405    int osresult = fstat(fd, &statb);
406    if (osresult != 0) {
407        printf("couldn't get size of %s\n", filename);
408        close(fd);
409        return false;
410    }
411	if ((sizeof(size_t) == 4) && ((size_t)statb.st_size > SIZE_T_MAX)) {
412        printf("couldn't malloc %llu bytes\n", statb.st_size);
413        close(fd);
414        return false;
415	}
416    FileSize = (size_t)statb.st_size;
417    FileBase = malloc(FileSize);
418    if (!FileBase) {
419        printf("couldn't malloc %lu bytes\n", FileSize);
420        close(fd);
421        return false;
422    }
423    ssize_t readsize = read(fd, FileBase, FileSize);
424    if ((readsize == -1) || ((size_t)readsize != FileSize)) {
425        printf("read %ld bytes, wanted %ld\n", (size_t)readsize, FileSize);
426        close(fd);
427        return false;
428    }
429    close(fd);
430    return true;
431}
432
433void closeFile() {
434    free(FileBase);
435}
436
437void dumpinfo(char *filename) {
438    initGCInfo();
439    if (!openFile(filename)) exit(1);
440    struct fat_header *fh = (struct fat_header *)FileBase;
441    if (fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM) {
442        dofat((void *)FileBase);
443        //printGCInfo(filename);
444    }
445    else if (fh->magic == MH_MAGIC || fh->magic == MH_CIGAM || fh->magic == MH_MAGIC_64 || fh->magic == MH_CIGAM_64) {
446        doofile((void *)FileBase, FileSize, &GCInfo[0]);
447        //printGCInfo(filename);
448    }
449    else if (!quiet) {
450        printf("don't understand %s!\n", filename);
451    }
452    closeFile();
453 }
454
455