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