/*!A cross-platform build utility based on Lua * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright (C) 2015-present, Xmake Open Source Community. * * @author ruki * @file prefix.h * */ #ifndef XM_BINUTILS_MACHO_PREFIX_H #define XM_BINUTILS_MACHO_PREFIX_H /* ////////////////////////////////////////////////////////////////////////////////////// * includes */ #include "../prefix.h" /* ////////////////////////////////////////////////////////////////////////////////////// * macros */ #define XM_MACHO_MAGIC_32 0xfeedface // MH_MAGIC - little endian #define XM_MACHO_MAGIC_64 0xfeedfacf // MH_MAGIC_64 - little endian #define XM_MACHO_MAGIC_32_BE 0xcefaedfe // MH_CIGAM - big endian #define XM_MACHO_MAGIC_64_BE 0xcffaedfe // MH_CIGAM_64 - big endian #define XM_MACHO_MAGIC_FAT 0xcafebabe #define XM_MACHO_CPU_TYPE_X86 7 #define XM_MACHO_CPU_TYPE_X86_64 0x01000007 #define XM_MACHO_CPU_TYPE_ARM 12 #define XM_MACHO_CPU_TYPE_ARM64 0x0100000c #define XM_MACHO_CPU_SUBTYPE_X86 3 #define XM_MACHO_CPU_SUBTYPE_X86_64 3 #define XM_MACHO_CPU_SUBTYPE_ARM 9 #define XM_MACHO_CPU_SUBTYPE_ARM64 0 #define XM_MACHO_FILE_TYPE_OBJECT 1 #define XM_MACHO_LC_SEGMENT 0x1 #define XM_MACHO_LC_SEGMENT_64 0x19 #define XM_MACHO_LC_SYMTAB 0x2 #define XM_MACHO_LC_LOAD_DYLIB 0xc #define XM_MACHO_LC_ID_DYLIB 0xd #define XM_MACHO_LC_RPATH (0x1c | 0x80000000) #define XM_MACHO_LC_LOAD_WEAK_DYLIB (0x18 | 0x80000000) #define XM_MACHO_LC_REEXPORT_DYLIB (0x1f | 0x80000000) #define XM_MACHO_LC_BUILD_VERSION 0x32 #define XM_MACHO_PLATFORM_MACOS 1 #define XM_MACHO_PLATFORM_IOS 2 #define XM_MACHO_PLATFORM_TVOS 3 #define XM_MACHO_PLATFORM_WATCHOS 4 #define XM_MACHO_SECT_TYPE_REGULAR 0x0 #define XM_MACHO_SECT_ATTR_SOME_INITS 0x400 #define XM_MACHO_SECT_ATTR_PURE_INSTRUCTIONS 0x80000000 #define XM_MACHO_N_TYPE_MASK 0x0e #define XM_MACHO_N_TYPE_SECT 0x0e #define XM_MACHO_N_EXT 0x01 #define XM_MACHO_VM_PROT_READ 1 #define XM_MACHO_VM_PROT_WRITE 2 #define XM_MACHO_VM_PROT_EXECUTE 4 /* ////////////////////////////////////////////////////////////////////////////////////// * types */ #include "tbox/prefix/packed.h" typedef struct __xm_macho_header_32_t { tb_uint32_t magic; tb_uint32_t cputype; tb_uint32_t cpusubtype; tb_uint32_t filetype; tb_uint32_t ncmds; tb_uint32_t sizeofcmds; tb_uint32_t flags; } __tb_packed__ xm_macho_header_32_t; typedef struct __xm_macho_header_64_t { tb_uint32_t magic; tb_uint32_t cputype; tb_uint32_t cpusubtype; tb_uint32_t filetype; tb_uint32_t ncmds; tb_uint32_t sizeofcmds; tb_uint32_t flags; tb_uint32_t reserved; } __tb_packed__ xm_macho_header_64_t; typedef struct __xm_macho_rpath_command_t { tb_uint32_t cmd; tb_uint32_t cmdsize; tb_uint32_t path_offset; } __tb_packed__ xm_macho_rpath_command_t; typedef struct __xm_macho_segment_command_t { tb_uint32_t cmd; tb_uint32_t cmdsize; tb_char_t segname[16]; tb_uint32_t vmaddr; tb_uint32_t vmsize; tb_uint32_t fileoff; tb_uint32_t filesize; tb_uint32_t maxprot; tb_uint32_t initprot; tb_uint32_t nsects; tb_uint32_t flags; } __tb_packed__ xm_macho_segment_command_t; typedef struct __xm_macho_segment_command_64_t { tb_uint32_t cmd; tb_uint32_t cmdsize; tb_char_t segname[16]; tb_uint64_t vmaddr; tb_uint64_t vmsize; tb_uint64_t fileoff; tb_uint64_t filesize; tb_uint32_t maxprot; tb_uint32_t initprot; tb_uint32_t nsects; tb_uint32_t flags; } __tb_packed__ xm_macho_segment_command_64_t; typedef struct __xm_macho_section_t { tb_char_t sectname[16]; tb_char_t segname[16]; tb_uint32_t addr; tb_uint32_t size; tb_uint32_t offset; tb_uint32_t align; tb_uint32_t reloff; tb_uint32_t nreloc; tb_uint32_t flags; tb_uint32_t reserved1; tb_uint32_t reserved2; } __tb_packed__ xm_macho_section_t; typedef struct __xm_macho_section_64_t { tb_char_t sectname[16]; tb_char_t segname[16]; tb_uint64_t addr; tb_uint64_t size; tb_uint32_t offset; tb_uint32_t align; tb_uint32_t reloff; tb_uint32_t nreloc; tb_uint32_t flags; tb_uint32_t reserved1; tb_uint32_t reserved2; tb_uint32_t reserved3; } __tb_packed__ xm_macho_section_64_t; typedef struct __xm_macho_symtab_command_t { tb_uint32_t cmd; tb_uint32_t cmdsize; tb_uint32_t symoff; tb_uint32_t nsyms; tb_uint32_t stroff; tb_uint32_t strsize; } __tb_packed__ xm_macho_symtab_command_t; typedef struct __xm_macho_build_version_command_t { tb_uint32_t cmd; tb_uint32_t cmdsize; tb_uint32_t platform; tb_uint32_t minos; tb_uint32_t sdk; tb_uint32_t ntools; } __tb_packed__ xm_macho_build_version_command_t; typedef struct __xm_macho_nlist_t { tb_uint32_t strx; tb_uint8_t type; tb_uint8_t sect; tb_int16_t desc; tb_uint32_t value; } __tb_packed__ xm_macho_nlist_t; typedef struct __xm_macho_nlist_64_t { tb_uint32_t strx; tb_uint8_t type; tb_uint8_t sect; tb_uint16_t desc; tb_uint64_t value; } __tb_packed__ xm_macho_nlist_64_t; typedef struct __xm_macho_load_command_t { tb_uint32_t cmd; tb_uint32_t cmdsize; } __tb_packed__ xm_macho_load_command_t; typedef struct __xm_macho_dylib_t { tb_uint32_t offset; tb_uint32_t timestamp; tb_uint32_t current_version; tb_uint32_t compatibility_version; } __tb_packed__ xm_macho_dylib_t; typedef struct __xm_macho_dylib_command_t { tb_uint32_t cmd; tb_uint32_t cmdsize; xm_macho_dylib_t dylib; } __tb_packed__ xm_macho_dylib_command_t; typedef struct __xm_macho_context_t { union { xm_macho_header_32_t header32; xm_macho_header_64_t header64; } header; tb_bool_t is64; tb_bool_t swap; tb_uint32_t ncmds; tb_uint32_t sizeofcmds; } __tb_packed__ xm_macho_context_t; #include "tbox/prefix/packed.h" /* ////////////////////////////////////////////////////////////////////////////////////// * inline implementation */ // byte-swap Mach-O header fields if needed static __tb_inline__ tb_void_t xm_binutils_macho_swap_header_32(xm_macho_header_32_t *header, tb_bool_t swap) { if (swap) { header->magic = tb_bits_swap_u32(header->magic); header->cputype = tb_bits_swap_u32(header->cputype); header->cpusubtype = tb_bits_swap_u32(header->cpusubtype); header->filetype = tb_bits_swap_u32(header->filetype); header->ncmds = tb_bits_swap_u32(header->ncmds); header->sizeofcmds = tb_bits_swap_u32(header->sizeofcmds); header->flags = tb_bits_swap_u32(header->flags); } } // byte-swap Mach-O header 64 fields if needed static __tb_inline__ tb_void_t xm_binutils_macho_swap_header_64(xm_macho_header_64_t *header, tb_bool_t swap) { if (swap) { header->magic = tb_bits_swap_u32(header->magic); header->cputype = tb_bits_swap_u32(header->cputype); header->cpusubtype = tb_bits_swap_u32(header->cpusubtype); header->filetype = tb_bits_swap_u32(header->filetype); header->ncmds = tb_bits_swap_u32(header->ncmds); header->sizeofcmds = tb_bits_swap_u32(header->sizeofcmds); header->flags = tb_bits_swap_u32(header->flags); header->reserved = tb_bits_swap_u32(header->reserved); } } // init Mach-O context static __tb_inline__ tb_bool_t xm_binutils_macho_context_init(tb_stream_ref_t istream, tb_hize_t base_offset, xm_macho_context_t* context) { tb_assert_and_check_return_val(istream && context, tb_false); // read Mach-O header if (!tb_stream_seek(istream, base_offset)) return tb_false; if (!tb_stream_bread(istream, (tb_byte_t*)&context->header.header32, sizeof(xm_macho_header_32_t))) return tb_false; // check magic tb_uint32_t magic = context->header.header32.magic; if (magic == XM_MACHO_MAGIC_32) { context->is64 = tb_false; context->swap = tb_false; } else if (magic == XM_MACHO_MAGIC_32_BE) { context->is64 = tb_false; context->swap = tb_true; } else if (magic == XM_MACHO_MAGIC_64) { context->is64 = tb_true; context->swap = tb_false; } else if (magic == XM_MACHO_MAGIC_64_BE) { context->is64 = tb_true; context->swap = tb_true; } else { return tb_false; // Not a Mach-O file } if (context->is64) { if (!tb_stream_seek(istream, base_offset)) return tb_false; if (!tb_stream_bread(istream, (tb_byte_t*)&context->header.header64, sizeof(xm_macho_header_64_t))) return tb_false; xm_binutils_macho_swap_header_64(&context->header.header64, context->swap); context->ncmds = context->header.header64.ncmds; context->sizeofcmds = context->header.header64.sizeofcmds; } else { xm_binutils_macho_swap_header_32(&context->header.header32, context->swap); context->ncmds = context->header.header32.ncmds; context->sizeofcmds = context->header.header32.sizeofcmds; } return tb_true; } // byte-swap load command fields if needed static __tb_inline__ tb_void_t xm_binutils_macho_swap_load_command(xm_macho_load_command_t *lc, tb_bool_t swap) { if (swap) { lc->cmd = tb_bits_swap_u32(lc->cmd); lc->cmdsize = tb_bits_swap_u32(lc->cmdsize); } } // byte-swap dylib command fields if needed static __tb_inline__ tb_void_t xm_binutils_macho_swap_dylib_command(xm_macho_dylib_command_t *dc, tb_bool_t swap) { if (swap) { dc->cmd = tb_bits_swap_u32(dc->cmd); dc->cmdsize = tb_bits_swap_u32(dc->cmdsize); dc->dylib.offset = tb_bits_swap_u32(dc->dylib.offset); dc->dylib.timestamp = tb_bits_swap_u32(dc->dylib.timestamp); dc->dylib.current_version = tb_bits_swap_u32(dc->dylib.current_version); dc->dylib.compatibility_version = tb_bits_swap_u32(dc->dylib.compatibility_version); } } // byte-swap rpath command fields if needed static __tb_inline__ tb_void_t xm_binutils_macho_swap_rpath_command(xm_macho_rpath_command_t *rc, tb_bool_t swap) { if (swap) { rc->cmd = tb_bits_swap_u32(rc->cmd); rc->cmdsize = tb_bits_swap_u32(rc->cmdsize); rc->path_offset = tb_bits_swap_u32(rc->path_offset); } } // byte-swap symtab command fields if needed static __tb_inline__ tb_void_t xm_binutils_macho_swap_symtab_command(xm_macho_symtab_command_t *cmd, tb_bool_t swap) { if (swap) { cmd->cmd = tb_bits_swap_u32(cmd->cmd); cmd->cmdsize = tb_bits_swap_u32(cmd->cmdsize); cmd->symoff = tb_bits_swap_u32(cmd->symoff); cmd->nsyms = tb_bits_swap_u32(cmd->nsyms); cmd->stroff = tb_bits_swap_u32(cmd->stroff); cmd->strsize = tb_bits_swap_u32(cmd->strsize); } } // byte-swap nlist 32 fields if needed static __tb_inline__ tb_void_t xm_binutils_macho_swap_nlist_32(xm_macho_nlist_t *nlist, tb_bool_t swap) { if (swap) { nlist->strx = tb_bits_swap_u32(nlist->strx); nlist->desc = tb_bits_swap_u16(nlist->desc); nlist->value = tb_bits_swap_u32(nlist->value); } } // byte-swap nlist 64 fields if needed static __tb_inline__ tb_void_t xm_binutils_macho_swap_nlist_64(xm_macho_nlist_64_t *nlist, tb_bool_t swap) { if (swap) { nlist->strx = tb_bits_swap_u32(nlist->strx); nlist->desc = tb_bits_swap_u16(nlist->desc); nlist->value = tb_bits_swap_u64(nlist->value); } } /* get CPU type from architecture string * * @param arch the architecture string (e.g., "x86_64", "i386", "arm64") * @return the CPU type */ static __tb_inline__ tb_uint32_t xm_binutils_macho_get_cputype(tb_char_t const *arch) { if (!arch) { return XM_MACHO_CPU_TYPE_X86_64; } if (tb_strcmp(arch, "x86_64") == 0 || tb_strcmp(arch, "x64") == 0) { return XM_MACHO_CPU_TYPE_X86_64; } else if (tb_strcmp(arch, "arm64") == 0 || tb_strcmp(arch, "aarch64") == 0) { return XM_MACHO_CPU_TYPE_ARM64; } else if (tb_strcmp(arch, "arm") == 0) { return XM_MACHO_CPU_TYPE_ARM; } else if (tb_strcmp(arch, "x86") == 0 || tb_strcmp(arch, "i386") == 0) { return XM_MACHO_CPU_TYPE_X86; } return XM_MACHO_CPU_TYPE_X86_64; } /* get CPU subtype from architecture string * * @param arch the architecture string * @return the CPU subtype */ static __tb_inline__ tb_uint32_t xm_binutils_macho_get_cpusubtype(tb_char_t const *arch) { if (!arch) { return XM_MACHO_CPU_SUBTYPE_X86_64; } if (tb_strcmp(arch, "x86_64") == 0 || tb_strcmp(arch, "x64") == 0) { return XM_MACHO_CPU_SUBTYPE_X86_64; } else if (tb_strcmp(arch, "arm64") == 0 || tb_strcmp(arch, "aarch64") == 0) { return XM_MACHO_CPU_SUBTYPE_ARM64; } else if (tb_strcmp(arch, "arm") == 0) { return XM_MACHO_CPU_SUBTYPE_ARM; } else if (tb_strcmp(arch, "x86") == 0 || tb_strcmp(arch, "i386") == 0) { return XM_MACHO_CPU_SUBTYPE_X86; } return XM_MACHO_CPU_SUBTYPE_X86_64; } /* check if architecture is 64-bit * * @param arch the architecture string * @return tb_true if 64-bit, tb_false otherwise */ static __tb_inline__ tb_bool_t xm_binutils_macho_is_64bit(tb_char_t const *arch) { if (!arch) { return tb_true; } if (tb_strcmp(arch, "x86_64") == 0 || tb_strcmp(arch, "x64") == 0) { return tb_true; } else if (tb_strcmp(arch, "arm64") == 0 || tb_strcmp(arch, "aarch64") == 0) { return tb_true; } else if (tb_strcmp(arch, "arm") == 0) { return tb_false; } else if (tb_strcmp(arch, "x86") == 0 || tb_strcmp(arch, "i386") == 0) { return tb_false; } return tb_true; } /* align value to specified alignment * * @param value the value to align * @param align the alignment (must be power of 2) * @return the aligned value */ static __tb_inline__ tb_uint32_t xm_binutils_macho_align(tb_uint32_t value, tb_uint32_t align) { return ((value + align - 1) & ~(align - 1)); } /* get platform from platform string * * @param platform the platform string (e.g., "macosx", "ios", "tvos", "watchos") * @return the platform constant */ static __tb_inline__ tb_uint32_t xm_binutils_macho_get_platform(tb_char_t const *platform) { if (!platform) { return XM_MACHO_PLATFORM_MACOS; } if (tb_strcmp(platform, "macosx") == 0 || tb_strcmp(platform, "macos") == 0) { return XM_MACHO_PLATFORM_MACOS; } else if (tb_strcmp(platform, "iphoneos") == 0 || tb_strcmp(platform, "ios") == 0) { return XM_MACHO_PLATFORM_IOS; } else if (tb_strcmp(platform, "appletvos") == 0 || tb_strcmp(platform, "tvos") == 0) { return XM_MACHO_PLATFORM_TVOS; } else if (tb_strcmp(platform, "watchos") == 0) { return XM_MACHO_PLATFORM_WATCHOS; } return XM_MACHO_PLATFORM_MACOS; } /* parse version string to Mach-O format * * @param version_str the version string (e.g., "10.0" or "18.2") * @return the version in Mach-O format (major << 16) | (minor << 8) | patch */ static __tb_inline__ tb_uint32_t xm_binutils_macho_parse_version(tb_char_t const *version_str) { if (!version_str || !version_str[0]) { return 0x000a0000; // default: 10.0.0 } tb_uint32_t major = 0; tb_uint32_t minor = 0; tb_uint32_t patch = 0; tb_char_t const *p = version_str; // parse major while (*p && tb_isdigit(*p)) { major = major * 10 + (*p - '0'); p++; } if (*p == '.') { p++; // parse minor while (*p && tb_isdigit(*p)) { minor = minor * 10 + (*p - '0'); p++; } if (*p == '.') { p++; // parse patch while (*p && tb_isdigit(*p)) { patch = patch * 10 + (*p - '0'); p++; } } } return (major << 16) | (minor << 8) | patch; } /* ////////////////////////////////////////////////////////////////////////////////////// * readsyms inline implementation */ /* read string from Mach-O string table * * @param istream the input stream * @param strtab_offset the string table offset * @param offset the string offset (nlist.strx, relative to string table start, including 4-byte size field) * @param name the buffer to store the string * @param name_size the size of the buffer * @return tb_true on success, tb_false on failure */ static __tb_inline__ tb_bool_t xm_binutils_macho_read_string(tb_stream_ref_t istream, tb_hize_t strtab_offset, tb_uint32_t offset, tb_char_t *name, tb_size_t name_size) { tb_assert_and_check_return_val(istream && name && name_size > 0, tb_false); // nlist.strx is offset from string table start (including 4-byte size field) tb_hize_t saved_pos = tb_stream_offset(istream); if (!tb_stream_seek(istream, strtab_offset + offset)) { return tb_false; } tb_size_t pos = 0; tb_byte_t c; while (pos < name_size - 1) { if (!tb_stream_bread(istream, &c, 1)) { tb_stream_seek(istream, saved_pos); return tb_false; } if (c == 0) { break; } name[pos++] = (tb_char_t)c; } name[pos] = '\0'; tb_stream_seek(istream, saved_pos); return tb_true; } /* get symbol type character (nm-style) from Mach-O symbol * * @param type the symbol type byte * @param sect the section number (0 = undefined) * @return the type character (T/t/D/d/B/b/U) */ static __tb_inline__ tb_char_t xm_binutils_macho_get_symbol_type_char(tb_uint8_t type, tb_uint8_t sect) { // undefined symbol if (sect == 0) { return 'U'; } // check if external tb_bool_t is_external = (type & XM_MACHO_N_EXT) != 0; // check if in section tb_uint8_t n_type = type & XM_MACHO_N_TYPE_MASK; if (n_type == XM_MACHO_N_TYPE_SECT) { // section 1 is usually __TEXT,__text (text) // section 2 is usually __DATA,__data (data) // section 3 is usually __DATA,__bss (bss) // For simplicity, we'll use section number to determine type // This is a heuristic and may not be 100% accurate if (sect == 1) { return is_external ? 'T' : 't'; // text section } else if (sect == 2) { return is_external ? 'D' : 'd'; // data section } else if (sect == 3) { return is_external ? 'B' : 'b'; // bss section } else { return is_external ? 'S' : 's'; // other section } } return '?'; // unknown } /* get symbol bind string from Mach-O symbol type * * @param type the symbol type byte * @return the bind string */ static __tb_inline__ tb_char_t const *xm_binutils_macho_get_symbol_bind(tb_uint8_t type) { if (type & XM_MACHO_N_EXT) { return "external"; } return "local"; } #endif