--- /dev/null
+/*
+ * Copyright (c) 2007-2009 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+// objcimageinfo.cpp
+// Print or edit ObjC image info bits.
+// This is used to verify ld's handling of these bits
+// for values that are not emitted by current compilers.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <mach-o/fat.h>
+#include <mach-o/arch.h>
+#include <mach-o/loader.h>
+
+#include "MachOFileAbstraction.hpp"
+
+#if __BIG_ENDIAN__
+ typedef BigEndian CurrentEndian;
+ typedef LittleEndian OtherEndian;
+#elif __LITTLE_ENDIAN__
+ typedef LittleEndian CurrentEndian;
+ typedef BigEndian OtherEndian;
+#else
+# error unknown endianness
+#endif
+
+static const bool debug = false;
+
+static bool processFile(const char *filename, uint32_t set, uint32_t clear);
+
+// fixme use objc/objc-abi.h instead
+struct objc_image_info {
+ uint32_t version;
+ uint32_t flags;
+
+ enum : uint32_t {
+ IsReplacement = 1<<0, // used for Fix&Continue, now ignored
+ SupportsGC = 1<<1, // image supports GC
+ RequiresGC = 1<<2, // image requires GC
+ OptimizedByDyld = 1<<3, // image is from an optimized shared cache
+ CorrectedSynthesize = 1<<4, // used for an old workaround, now ignored
+ IsSimulated = 1<<5, // image compiled for a simulator platform
+ HasCategoryClassProperties = 1<<6, // class properties in category_t
+
+ SwiftVersionMask = 0xff << 8 // Swift ABI version
+ };
+};
+
+// objc_image_info flags and their names
+static const struct {
+ const char *name;
+ uint32_t value;
+} Flags[] = {
+ { "supports-gc", objc_image_info::SupportsGC },
+ { "requires-gc", objc_image_info::RequiresGC },
+ { "has-category-class-properties", objc_image_info::HasCategoryClassProperties },
+ { 0, 0 }
+};
+
+static void usage(const char *self)
+{
+ printf("usage: %s [+FLAG|-FLAG ...] file ...\n", self);
+ printf("Use +FLAG to set and -FLAG to clear.\n");
+ printf("Known flags: ");
+ for (int i = 0; Flags[i].name != 0; i++) {
+ printf("%s ", Flags[i].name);
+ }
+ printf("\n");
+}
+
+static uint32_t flagForName(const char *name)
+{
+ for (int i = 0; Flags[i].name != 0; i++) {
+ if (0 == strcmp(Flags[i].name, name)) {
+ return Flags[i].value;
+ }
+ }
+ return 0;
+}
+
+static const char *nameForFlag(uint32_t flag)
+{
+ for (int i = 0; Flags[i].name != 0; i++) {
+ if (Flags[i].value == flag) {
+ return Flags[i].name;
+ }
+ }
+ return 0;
+}
+
+static void printFlags(uint32_t flags)
+{
+ printf("0x%x", flags);
+
+ // Print flags and unknown bits
+ for (int i = 0; i < 24; i++) {
+ uint32_t flag = 1<<i;
+ if (flag & objc_image_info::SwiftVersionMask) continue;
+ if (flags & flag) {
+ const char *name = nameForFlag(flag);
+ if (name) printf(" %s", name);
+ else printf(" unknown-%u", flag);
+ }
+ }
+
+ // Print Swift version
+ uint32_t mask = objc_image_info::SwiftVersionMask;
+ uint32_t shift = __builtin_ctzl(mask);
+ uint32_t swift = (flags & mask) >> shift;
+ if (swift > 0) {
+ printf(" swift-version=%u", swift);
+ }
+}
+
+static bool isFlagArgument(const char *arg)
+{
+ return (arg && (arg[0] == '+' || arg[0] == '-'));
+}
+
+int main(int argc, const char *argv[]) {
+ uint32_t set = 0;
+ uint32_t clear = 0;
+
+ // Find flag arguments (which are +FLAG or -FLAG).
+ int i;
+ for (i = 1; i < argc && isFlagArgument(argv[i]); i++) {
+ const char *arg = argv[i];
+ uint32_t flag = flagForName(arg+1);
+ if (flag) {
+ if (arg[0] == '+') {
+ set |= flag;
+ } else {
+ clear |= flag;
+ }
+ } else {
+ printf("error: unrecognized ObjC flag '%s'\n", arg);
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ // Complain if +FLAG and -FLAG are both set for some flag.
+ uint32_t overlap = set & clear;
+ if (overlap) {
+ printf("error: conflicting changes specified: ");
+ printFlags(overlap);
+ printf("\n");
+ usage(argv[0]);
+ return 1;
+ }
+
+ // Complain if there are no filenames.
+ if (i == argc) {
+ printf("error: no files specified\n");
+ usage(argv[0]);
+ return 1;
+ }
+
+ // Process files.
+ for (; i < argc; i++) {
+ if (!processFile(argv[i], set, clear)) return 1;
+ }
+ return 0;
+}
+
+
+// Segment and section names are 16 bytes and may be un-terminated.
+static bool segnameEquals(const char *lhs, const char *rhs)
+{
+ return 0 == strncmp(lhs, rhs, 16);
+}
+
+static bool segnameStartsWith(const char *segname, const char *prefix)
+{
+ return 0 == strncmp(segname, prefix, strlen(prefix));
+}
+
+static bool sectnameEquals(const char *lhs, const char *rhs)
+{
+ return segnameEquals(lhs, rhs);
+}
+
+
+template <typename P>
+static void dosect(const char *filename, uint8_t *start, macho_section<P> *sect,
+ uint32_t set, uint32_t clear)
+{
+ if (debug) printf("section %.16s from segment %.16s\n",
+ sect->sectname(), sect->segname());
+
+ if ((segnameStartsWith(sect->segname(), "__DATA") &&
+ sectnameEquals(sect->sectname(), "__objc_imageinfo")) ||
+ (segnameEquals(sect->segname(), "__OBJC") &&
+ sectnameEquals(sect->sectname(), "__image_info")))
+ {
+ objc_image_info *ii = (objc_image_info *)(start + sect->offset());
+ uint32_t oldFlags = P::E::get32(ii->flags);
+ uint32_t newFlags = (oldFlags | set) & ~clear;
+ if (oldFlags != newFlags) {
+ P::E::set32(ii->flags, newFlags);
+ if (debug) printf("changed flags from 0x%x to 0x%x\n",
+ oldFlags, newFlags);
+ }
+
+ printf("%s: ", filename);
+ printFlags(newFlags);
+ printf("\n");
+ }
+}
+
+template <typename P>
+static void doseg(const char *filename,
+ uint8_t *start, macho_segment_command<P> *seg,
+ uint32_t set, uint32_t clear)
+{
+ if (debug) printf("segment name: %.16s, nsects %u\n",
+ seg->segname(), seg->nsects());
+ macho_section<P> *sect = (macho_section<P> *)(seg + 1);
+ for (uint32_t i = 0; i < seg->nsects(); ++i) {
+ dosect(filename, start, §[i], set, clear);
+ }
+}
+
+
+template<typename P>
+static bool parse_macho(const char *filename, uint8_t *buffer,
+ uint32_t set, uint32_t clear)
+{
+ macho_header<P>* mh = (macho_header<P>*)buffer;
+ uint8_t *cmds = (uint8_t *)(mh + 1);
+ for (uint32_t c = 0; c < mh->ncmds(); c++) {
+ macho_load_command<P>* cmd = (macho_load_command<P>*)cmds;
+ cmds += cmd->cmdsize();
+ if (cmd->cmd() == LC_SEGMENT || cmd->cmd() == LC_SEGMENT_64) {
+ doseg(filename, buffer, (macho_segment_command<P>*)cmd, set, clear);
+ }
+ }
+
+ return true;
+}
+
+
+static bool parse_macho(const char *filename, uint8_t *buffer,
+ uint32_t set, uint32_t clear)
+{
+ uint32_t magic = *(uint32_t *)buffer;
+
+ switch (magic) {
+ case MH_MAGIC_64:
+ return parse_macho<Pointer64<CurrentEndian>>
+ (filename, buffer, set, clear);
+ case MH_MAGIC:
+ return parse_macho<Pointer32<CurrentEndian>>
+ (filename, buffer, set, clear);
+ case MH_CIGAM_64:
+ return parse_macho<Pointer64<OtherEndian>>
+ (filename, buffer, set, clear);
+ case MH_CIGAM:
+ return parse_macho<Pointer32<OtherEndian>>
+ (filename, buffer, set, clear);
+ default:
+ printf("error: file '%s' is not mach-o (magic %x)\n", filename, magic);
+ return false;
+ }
+}
+
+
+static bool parse_fat(const char *filename, uint8_t *buffer, size_t size,
+ uint32_t set, uint32_t clear)
+{
+ uint32_t magic;
+
+ if (size < sizeof(magic)) {
+ printf("error: file '%s' is too small\n", filename);
+ return false;
+ }
+
+ magic = *(uint32_t *)buffer;
+ if (magic != FAT_MAGIC && magic != FAT_CIGAM) {
+ /* Not a fat file */
+ return parse_macho(filename, buffer, set, clear);
+ } else {
+ struct fat_header *fh;
+ uint32_t fat_magic, fat_nfat_arch;
+ struct fat_arch *archs;
+
+ if (size < sizeof(struct fat_header)) {
+ printf("error: file '%s' is too small\n", filename);
+ return false;
+ }
+
+ fh = (struct fat_header *)buffer;
+ fat_magic = OSSwapBigToHostInt32(fh->magic);
+ fat_nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
+
+ if (size < (sizeof(struct fat_header) + fat_nfat_arch * sizeof(struct fat_arch))) {
+ printf("error: file '%s' is too small\n", filename);
+ return false;
+ }
+
+ archs = (struct fat_arch *)(buffer + sizeof(struct fat_header));
+
+ /* Special case hidden CPU_TYPE_ARM64 */
+ if (size >= (sizeof(struct fat_header) + (fat_nfat_arch + 1) * sizeof(struct fat_arch))) {
+ if (fat_nfat_arch > 0
+ && OSSwapBigToHostInt32(archs[fat_nfat_arch].cputype) == CPU_TYPE_ARM64) {
+ fat_nfat_arch++;
+ }
+ }
+ /* End special case hidden CPU_TYPE_ARM64 */
+
+ if (debug) printf("%d fat architectures\n", fat_nfat_arch);
+
+ for (uint32_t i = 0; i < fat_nfat_arch; i++) {
+ uint32_t arch_cputype = OSSwapBigToHostInt32(archs[i].cputype);
+ uint32_t arch_cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype);
+ uint32_t arch_offset = OSSwapBigToHostInt32(archs[i].offset);
+ uint32_t arch_size = OSSwapBigToHostInt32(archs[i].size);
+
+ if (debug) printf("cputype %d cpusubtype %d\n",
+ arch_cputype, arch_cpusubtype);
+
+ /* Check that slice data is after all fat headers and archs */
+ if (arch_offset < (sizeof(struct fat_header) + fat_nfat_arch * sizeof(struct fat_arch))) {
+ printf("error: file is badly formed\n");
+ return false;
+ }
+
+ /* Check that the slice ends before the file does */
+ if (arch_offset > size) {
+ printf("error: file '%s' is badly formed\n", filename);
+ return false;
+ }
+
+ if (arch_size > size) {
+ printf("error: file '%s' is badly formed\n", filename);
+ return false;
+ }
+
+ if (arch_offset > (size - arch_size)) {
+ printf("error: file '%s' is badly formed\n", filename);
+ return false;
+ }
+
+ bool ok = parse_macho(filename, buffer + arch_offset, set, clear);
+ if (!ok) return false;
+ }
+ return true;
+ }
+}
+
+static bool processFile(const char *filename, uint32_t set, uint32_t clear)
+{
+ if (debug) printf("file %s\n", filename);
+ int openPerm = O_RDONLY;
+ int mmapPerm = PROT_READ;
+ if (set || clear) {
+ openPerm = O_RDWR;
+ mmapPerm = PROT_READ | PROT_WRITE;
+ }
+
+ int fd = open(filename, openPerm);
+ if (fd < 0) {
+ printf("error: open %s: %s\n", filename, strerror(errno));
+ return false;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ printf("error: fstat %s: %s\n", filename, strerror(errno));
+ return false;
+ }
+
+ void *buffer = mmap(NULL, (size_t)st.st_size, mmapPerm,
+ MAP_FILE|MAP_SHARED, fd, 0);
+ if (buffer == MAP_FAILED) {
+ printf("error: mmap %s: %s\n", filename, strerror(errno));
+ return false;
+ }
+
+ bool result =
+ parse_fat(filename, (uint8_t *)buffer, (size_t)st.st_size, set, clear);
+ munmap(buffer, (size_t)st.st_size);
+ close(fd);
+ return result;
+}