--- src/macho.cc 2026-06-01 00:14:40.000000000 +0800 +++ src/macho.cc 2026-07-11 19:43:50.000000000 +0800 @@ -117,6 +117,40 @@ default: return "arm"; } + case CPU_TYPE_POWERPC: + switch (cpusubtype) { + case CPU_SUBTYPE_POWERPC_601: + return "ppc601"; + case CPU_SUBTYPE_POWERPC_602: + return "ppc602"; + case CPU_SUBTYPE_POWERPC_603: + return "ppc603"; + case CPU_SUBTYPE_POWERPC_603e: + return "ppc603e"; + case CPU_SUBTYPE_POWERPC_603ev: + return "ppc603ev"; + case CPU_SUBTYPE_POWERPC_604: + return "ppc604"; + case CPU_SUBTYPE_POWERPC_604e: + return "ppc604e"; + case CPU_SUBTYPE_POWERPC_750: + return "ppc750"; + case CPU_SUBTYPE_POWERPC_7400: + return "ppc7400"; + case CPU_SUBTYPE_POWERPC_7450: + return "ppc7450"; + case CPU_SUBTYPE_POWERPC_970: + return "ppc970"; + default: + return "ppc"; + } + case CPU_TYPE_POWERPC64: + switch (cpusubtype) { + case CPU_SUBTYPE_POWERPC_970: + return "ppc970-64"; + default: + return "ppc64"; + } default: return absl::StrFormat("cpu_%d", cputype); } @@ -124,6 +158,7 @@ struct LoadCommand { bool is64bit; + bool is_byteswapped; uint32_t cmd; string_view command_data; string_view file_data; @@ -135,39 +170,53 @@ template <> bool Is64Bit() { return true; } +// Byte-swap helpers for big-endian (PowerPC) Mach-O parsing. +// When is_byteswapped is true all integer fields read from load command +// structs must be swapped before use. +static uint32_t MaybeSwap32(uint32_t v, bool swap) { + return swap ? ByteSwap(v) : v; +} +static uint64_t MaybeSwap64(uint64_t v, bool swap) { + return swap ? ByteSwap(v) : v; +} + template void ParseMachOHeaderImpl(string_view macho_data, RangeSink* overhead_sink, - Func&& loadcmd_func) { + bool byteswap, Func&& loadcmd_func) { string_view header_data = macho_data; auto header = GetStructPointerAndAdvance(&header_data); MaybeAddOverhead(overhead_sink, "[Mach-O Headers]", macho_data.substr(0, sizeof(Struct))); - uint32_t ncmds = header->ncmds; + uint32_t ncmds = MaybeSwap32(header->ncmds, byteswap); for (uint32_t i = 0; i < ncmds; i++) { auto command = GetStructPointer(header_data); + uint32_t cmdsize = MaybeSwap32(command->cmdsize, byteswap); + uint32_t cmd = MaybeSwap32(command->cmd, byteswap); + // Validate load command size to prevent hangs or buffer overruns - if (command->cmdsize == 0) { + if (cmdsize == 0) { THROW("Mach-O load command had zero size."); } - if (command->cmdsize < sizeof(load_command)) { + if (cmdsize < sizeof(load_command)) { THROW("Mach-O load command size smaller than minimum."); } - if (command->cmdsize > header_data.size()) { + if (cmdsize > header_data.size()) { THROW("Mach-O load command size exceeds remaining header data."); } LoadCommand data; - data.is64bit = Is64Bit(); - data.cmd = command->cmd; - data.command_data = StrictSubstr(header_data, 0, command->cmdsize); - data.file_data = macho_data; + data.is64bit = Is64Bit(); + data.is_byteswapped = byteswap; + data.cmd = cmd; + data.command_data = StrictSubstr(header_data, 0, cmdsize); + data.file_data = macho_data; std::forward(loadcmd_func)(data); MaybeAddOverhead(overhead_sink, "[Mach-O Headers]", data.command_data); - header_data = header_data.substr(command->cmdsize); + header_data = header_data.substr(cmdsize); } } @@ -184,28 +233,23 @@ // Still, you can build 32-bit binaries as of this writing, and // there are existing 32-bit binaries floating around, so we might // as well support them. - ParseMachOHeaderImpl(macho_file, overhead_sink, + ParseMachOHeaderImpl(macho_file, overhead_sink, false, std::forward(loadcmd_func)); break; case MH_MAGIC_64: ParseMachOHeaderImpl( - macho_file, overhead_sink, std::forward(loadcmd_func)); + macho_file, overhead_sink, false, std::forward(loadcmd_func)); break; case MH_CIGAM: + // Big-endian 32-bit Mach-O (e.g. PowerPC). + ParseMachOHeaderImpl(macho_file, overhead_sink, true, + std::forward(loadcmd_func)); + break; case MH_CIGAM_64: - // OS X and Darwin currently only run on x86/x86-64 (little-endian - // platforms), so we expect basically all Mach-O files to be - // little-endian. Additionally, pretty much all CPU architectures - // are little-endian these days. ARM has the option to be - // big-endian, but I can't find any OS that is actually compiled to - // use big-endian mode. debian-mips is the only big-endian OS I can - // find (and maybe SPARC). - // - // All of this is to say, this case should only happen if you are - // running Bloaty on debian-mips. I consider that uncommon enough - // (and hard enough to test) that we don't support this until there - // is a demonstrated need. - THROW("We don't support cross-endian Mach-O files."); + // Big-endian 64-bit Mach-O (e.g. PowerPC64). + ParseMachOHeaderImpl( + macho_file, overhead_sink, true, std::forward(loadcmd_func)); + break; default: THROW("Corrupt Mach-O file"); } @@ -256,16 +300,16 @@ template void AddSegmentAsFallback(string_view command_data, string_view file_data, - RangeSink* sink) { + bool swap, RangeSink* sink) { auto segment = GetStructPointerAndAdvance(&command_data); - if (segment->maxprot == VM_PROT_NONE) { + if (MaybeSwap32(segment->maxprot, swap) == VM_PROT_NONE) { return; } string_view segname = ArrayToStr(segment->segname, 16); - uint32_t nsects = segment->nsects; + uint32_t nsects = MaybeSwap32(segment->nsects, swap); // Validate that nsects count doesn't exceed command data size if (nsects > command_data.size() / sizeof(Section)) { @@ -275,9 +319,14 @@ for (uint32_t j = 0; j < nsects; j++) { auto section = GetStructPointerAndAdvance
(&command_data); + uint64_t sec_size = MaybeSwap64((uint64_t)section->size, swap); + uint64_t sec_addr = MaybeSwap64((uint64_t)section->addr, swap); + uint32_t sec_offset = MaybeSwap32(section->offset, swap); + uint32_t sec_flags = MaybeSwap32(section->flags, swap); + // filesize equals vmsize unless the section is zerofill - uint64_t filesize = section->size; - switch (section->flags & SECTION_TYPE) { + uint64_t filesize = sec_size; + switch (sec_flags & SECTION_TYPE) { case S_ZEROFILL: case S_GB_ZEROFILL: case S_THREAD_LOCAL_ZEROFILL: @@ -290,17 +339,23 @@ std::string label = absl::StrJoin( std::make_tuple(segname, ArrayToStr(section->sectname, 16)), ","); label = "[" + label + "]"; - sink->AddRange("macho_fallback", label, section->addr, section->size, - StrictSubstr(file_data, section->offset, filesize)); + sink->AddRange("macho_fallback", label, sec_addr, sec_size, + StrictSubstr(file_data, sec_offset, filesize)); } + uint64_t vmaddr = MaybeSwap64((uint64_t)segment->vmaddr, swap); + uint64_t vmsize = MaybeSwap64((uint64_t)segment->vmsize, swap); + uint64_t fileoff = MaybeSwap64((uint64_t)segment->fileoff, swap); + uint64_t filesize = MaybeSwap64((uint64_t)segment->filesize, swap); + sink->AddRange("macho_fallback", "[" + std::string(segname) + "]", - segment->vmaddr, segment->vmsize, - StrictSubstr(file_data, segment->fileoff, segment->filesize)); + vmaddr, vmsize, + StrictSubstr(file_data, fileoff, filesize)); } template void ParseSegment(LoadCommand cmd, RangeSink* sink) { + bool swap = cmd.is_byteswapped; auto segment = GetStructPointerAndAdvance(&cmd.command_data); string_view segname = ArrayToStr(segment->segname, 16); @@ -312,20 +367,25 @@ // // Since such segments should not be mapped, we simply ignore the // vmaddr/vmsize of such segments. - bool unmapped = segment->maxprot == VM_PROT_NONE; + bool unmapped = MaybeSwap32(segment->maxprot, swap) == VM_PROT_NONE; + + uint64_t vmaddr = MaybeSwap64((uint64_t)segment->vmaddr, swap); + uint64_t vmsize = MaybeSwap64((uint64_t)segment->vmsize, swap); + uint64_t fileoff = MaybeSwap64((uint64_t)segment->fileoff, swap); + uint64_t filesize = MaybeSwap64((uint64_t)segment->filesize, swap); if (sink->data_source() == DataSource::kSegments) { if (unmapped) { sink->AddFileRange( "macho_segment", segname, - StrictSubstr(cmd.file_data, segment->fileoff, segment->filesize)); + StrictSubstr(cmd.file_data, fileoff, filesize)); } else { sink->AddRange( - "macho_segment", segname, segment->vmaddr, segment->vmsize, - StrictSubstr(cmd.file_data, segment->fileoff, segment->filesize)); + "macho_segment", segname, vmaddr, vmsize, + StrictSubstr(cmd.file_data, fileoff, filesize)); } } else if (sink->data_source() == DataSource::kSections) { - uint32_t nsects = segment->nsects; + uint32_t nsects = MaybeSwap32(segment->nsects, swap); // Validate that nsects count doesn't exceed command data size if (nsects > cmd.command_data.size() / sizeof(Section)) { @@ -335,13 +395,18 @@ for (uint32_t j = 0; j < nsects; j++) { auto section = GetStructPointerAndAdvance
(&cmd.command_data); + uint64_t sec_size = MaybeSwap64((uint64_t)section->size, swap); + uint64_t sec_addr = MaybeSwap64((uint64_t)section->addr, swap); + uint32_t sec_offset = MaybeSwap32(section->offset, swap); + uint32_t sec_flags = MaybeSwap32(section->flags, swap); + // filesize equals vmsize unless the section is zerofill - uint64_t filesize = section->size; - switch (section->flags & SECTION_TYPE) { + uint64_t sec_filesize = sec_size; + switch (sec_flags & SECTION_TYPE) { case S_ZEROFILL: case S_GB_ZEROFILL: case S_THREAD_LOCAL_ZEROFILL: - filesize = 0; + sec_filesize = 0; break; default: break; @@ -352,10 +417,10 @@ if (unmapped) { sink->AddFileRange( "macho_section", label, - StrictSubstr(cmd.file_data, section->offset, filesize)); + StrictSubstr(cmd.file_data, sec_offset, sec_filesize)); } else { - sink->AddRange("macho_section", label, section->addr, section->size, - StrictSubstr(cmd.file_data, section->offset, filesize)); + sink->AddRange("macho_section", label, sec_addr, sec_size, + StrictSubstr(cmd.file_data, sec_offset, sec_filesize)); } } } else { @@ -366,7 +431,9 @@ static bool IsObjectFile(string_view data) { string_view header_data = data; auto header = GetStructPointerAndAdvance(&header_data); - return header->filetype == MH_OBJECT; + uint32_t magic = ReadMagic(data); + bool swap = (magic == MH_CIGAM || magic == MH_CIGAM_64); + return MaybeSwap32(header->filetype, swap) == MH_OBJECT; } static void CheckNotObject(const char* source, RangeSink* sink) { @@ -380,68 +447,89 @@ static void ParseDyldInfo(const LoadCommand& cmd, RangeSink* sink) { auto info = GetStructPointer(cmd.command_data); + bool swap = cmd.is_byteswapped; sink->AddFileRange( "macho_dyld", "Rebase Info", - StrictSubstr(cmd.file_data, info->rebase_off, info->rebase_size)); + StrictSubstr(cmd.file_data, MaybeSwap32(info->rebase_off, swap), + MaybeSwap32(info->rebase_size, swap))); sink->AddFileRange( "macho_dyld", "Binding Info", - StrictSubstr(cmd.file_data, info->bind_off, info->bind_size)); + StrictSubstr(cmd.file_data, MaybeSwap32(info->bind_off, swap), + MaybeSwap32(info->bind_size, swap))); sink->AddFileRange( "macho_dyld", "Weak Binding Info", - StrictSubstr(cmd.file_data, info->weak_bind_off, info->weak_bind_size)); + StrictSubstr(cmd.file_data, MaybeSwap32(info->weak_bind_off, swap), + MaybeSwap32(info->weak_bind_size, swap))); sink->AddFileRange( "macho_dyld", "Lazy Binding Info", - StrictSubstr(cmd.file_data, info->lazy_bind_off, info->lazy_bind_size)); + StrictSubstr(cmd.file_data, MaybeSwap32(info->lazy_bind_off, swap), + MaybeSwap32(info->lazy_bind_size, swap))); sink->AddFileRange( "macho_dyld", "Export Info", - StrictSubstr(cmd.file_data, info->export_off, info->export_size)); + StrictSubstr(cmd.file_data, MaybeSwap32(info->export_off, swap), + MaybeSwap32(info->export_size, swap))); } static void ParseSymbolTable(const LoadCommand& cmd, RangeSink* sink) { auto symtab = GetStructPointer(cmd.command_data); + bool swap = cmd.is_byteswapped; size_t size = cmd.is64bit ? sizeof(nlist_64) : sizeof(struct nlist); sink->AddFileRange( "macho_symtab", "Symbol Table", - StrictSubstr(cmd.file_data, symtab->symoff, symtab->nsyms * size)); + StrictSubstr(cmd.file_data, MaybeSwap32(symtab->symoff, swap), + MaybeSwap32(symtab->nsyms, swap) * size)); sink->AddFileRange( "macho_symtab", "String Table", - StrictSubstr(cmd.file_data, symtab->stroff, symtab->strsize)); + StrictSubstr(cmd.file_data, MaybeSwap32(symtab->stroff, swap), + MaybeSwap32(symtab->strsize, swap))); } static void ParseDynamicSymbolTable(const LoadCommand& cmd, RangeSink* sink) { auto dysymtab = GetStructPointer(cmd.command_data); + bool swap = cmd.is_byteswapped; sink->AddFileRange( "macho_dynsymtab", "Table of Contents", - StrictSubstr(cmd.file_data, dysymtab->tocoff, - dysymtab->ntoc * sizeof(dylib_table_of_contents))); - sink->AddFileRange("macho_dynsymtab", "Module Table", - StrictSubstr(cmd.file_data, dysymtab->modtaboff, - dysymtab->nmodtab * sizeof(dylib_module_64))); + StrictSubstr(cmd.file_data, MaybeSwap32(dysymtab->tocoff, swap), + MaybeSwap32(dysymtab->ntoc, swap) * + sizeof(dylib_table_of_contents))); + sink->AddFileRange( + "macho_dynsymtab", "Module Table", + StrictSubstr(cmd.file_data, MaybeSwap32(dysymtab->modtaboff, swap), + MaybeSwap32(dysymtab->nmodtab, swap) * + sizeof(dylib_module_64))); sink->AddFileRange( "macho_dynsymtab", "Referenced Symbol Table", - StrictSubstr(cmd.file_data, dysymtab->extrefsymoff, - dysymtab->nextrefsyms * sizeof(dylib_reference))); - sink->AddFileRange("macho_dynsymtab", "Indirect Symbol Table", - StrictSubstr(cmd.file_data, dysymtab->indirectsymoff, - dysymtab->nindirectsyms * sizeof(uint32_t))); - sink->AddFileRange("macho_dynsymtab", "External Relocation Entries", - StrictSubstr(cmd.file_data, dysymtab->extreloff, - dysymtab->nextrel * sizeof(relocation_info))); + StrictSubstr(cmd.file_data, MaybeSwap32(dysymtab->extrefsymoff, swap), + MaybeSwap32(dysymtab->nextrefsyms, swap) * + sizeof(dylib_reference))); + sink->AddFileRange( + "macho_dynsymtab", "Indirect Symbol Table", + StrictSubstr(cmd.file_data, MaybeSwap32(dysymtab->indirectsymoff, swap), + MaybeSwap32(dysymtab->nindirectsyms, swap) * + sizeof(uint32_t))); + sink->AddFileRange( + "macho_dynsymtab", "External Relocation Entries", + StrictSubstr(cmd.file_data, MaybeSwap32(dysymtab->extreloff, swap), + MaybeSwap32(dysymtab->nextrel, swap) * + sizeof(relocation_info))); sink->AddFileRange( "macho_dynsymtab", "Local Relocation Entries", - StrictSubstr(cmd.file_data, dysymtab->locreloff, - dysymtab->nlocrel * sizeof(struct relocation_info))); + StrictSubstr(cmd.file_data, MaybeSwap32(dysymtab->locreloff, swap), + MaybeSwap32(dysymtab->nlocrel, swap) * + sizeof(struct relocation_info))); } static void ParseLinkeditCommand(string_view label, const LoadCommand& cmd, RangeSink* sink) { auto linkedit = GetStructPointer(cmd.command_data); + bool swap = cmd.is_byteswapped; sink->AddFileRange( "macho_linkedit", label, - StrictSubstr(cmd.file_data, linkedit->dataoff, linkedit->datasize)); + StrictSubstr(cmd.file_data, MaybeSwap32(linkedit->dataoff, swap), + MaybeSwap32(linkedit->datasize, swap))); } void ParseLoadCommand(const LoadCommand& cmd, RangeSink* sink) { @@ -499,17 +587,20 @@ void ParseSymbolsFromSymbolTable(const LoadCommand& cmd, SymbolTable* table, RangeSink* sink) { auto symtab_cmd = GetStructPointer(cmd.command_data); + bool swap = cmd.is_byteswapped; // Validate that nsyms count doesn't cause overflow or exceed file size - uint32_t nsyms = symtab_cmd->nsyms; + uint32_t nsyms = MaybeSwap32(symtab_cmd->nsyms, swap); if (nsyms > cmd.file_data.size() / sizeof(NList)) { THROW("invalid symbol count in symbol table"); } - string_view symtab = StrictSubstr(cmd.file_data, symtab_cmd->symoff, - nsyms * sizeof(NList)); - string_view strtab = - StrictSubstr(cmd.file_data, symtab_cmd->stroff, symtab_cmd->strsize); + uint32_t symoff = MaybeSwap32(symtab_cmd->symoff, swap); + uint32_t stroff = MaybeSwap32(symtab_cmd->stroff, swap); + uint32_t strsize = MaybeSwap32(symtab_cmd->strsize, swap); + + string_view symtab = StrictSubstr(cmd.file_data, symoff, nsyms * sizeof(NList)); + string_view strtab = StrictSubstr(cmd.file_data, stroff, strsize); for (uint32_t i = 0; i < nsyms; i++) { auto sym = GetStructPointerAndAdvance(&symtab); @@ -519,23 +610,26 @@ continue; } - string_view name_region = StrictSubstr(strtab, sym->n_un.n_strx); + uint64_t n_value = MaybeSwap64((uint64_t)sym->n_value, swap); + uint32_t n_strx = MaybeSwap32(sym->n_un.n_strx, swap); + + string_view name_region = StrictSubstr(strtab, n_strx); string_view name = ReadNullTerminated(&name_region); if (sink->data_source() >= DataSource::kSymbols) { - sink->AddVMRange("macho_symbols", sym->n_value, RangeSink::kUnknownSize, + sink->AddVMRange("macho_symbols", n_value, RangeSink::kUnknownSize, ItaniumDemangle(name, sink->data_source())); } if (table) { table->insert(std::make_pair( - name, std::make_pair(sym->n_value, RangeSink::kUnknownSize))); + name, std::make_pair(n_value, RangeSink::kUnknownSize))); } // Capture the trailing NULL. name = string_view(name.data(), name.size() + 1); - sink->AddFileRangeForVMAddr("macho_symtab_name", sym->n_value, name); - sink->AddFileRangeForVMAddr("macho_symtab_sym", sym->n_value, sym_range); + sink->AddFileRangeForVMAddr("macho_symtab_name", n_value, name); + sink->AddFileRangeForVMAddr("macho_symtab_sym", n_value, sym_range); } } @@ -565,11 +659,11 @@ switch (cmd.cmd) { case LC_SEGMENT_64: AddSegmentAsFallback( - cmd.command_data, cmd.file_data, sink); + cmd.command_data, cmd.file_data, cmd.is_byteswapped, sink); break; case LC_SEGMENT: - AddSegmentAsFallback(cmd.command_data, - cmd.file_data, sink); + AddSegmentAsFallback( + cmd.command_data, cmd.file_data, cmd.is_byteswapped, sink); break; } }); @@ -579,6 +673,7 @@ template void ReadDebugSectionsFromSegment(LoadCommand cmd, dwarf::File *dwarf, RangeSink *sink) { + bool swap = cmd.is_byteswapped; auto segment = GetStructPointerAndAdvance(&cmd.command_data); string_view segname = ArrayToStr(segment->segname, 16); @@ -586,7 +681,7 @@ return; } - uint32_t nsects = segment->nsects; + uint32_t nsects = MaybeSwap32(segment->nsects, swap); // Validate that nsects count doesn't exceed command data size if (nsects > cmd.command_data.size() / sizeof(Section)) { @@ -597,9 +692,13 @@ auto section = GetStructPointerAndAdvance
(&cmd.command_data); string_view sectname = ArrayToStr(section->sectname, 16); + uint64_t sec_size = MaybeSwap64((uint64_t)section->size, swap); + uint32_t sec_offset = MaybeSwap32(section->offset, swap); + uint32_t sec_flags = MaybeSwap32(section->flags, swap); + // filesize equals vmsize unless the section is zerofill - uint64_t filesize = section->size; - switch (section->flags & SECTION_TYPE) { + uint64_t filesize = sec_size; + switch (sec_flags & SECTION_TYPE) { case S_ZEROFILL: case S_GB_ZEROFILL: case S_THREAD_LOCAL_ZEROFILL: @@ -609,8 +708,7 @@ break; } - string_view contents = - StrictSubstr(cmd.file_data, section->offset, filesize); + string_view contents = StrictSubstr(cmd.file_data, sec_offset, filesize); if (sectname.find("__debug_") == 0) { sectname.remove_prefix(string_view("__debug_").size()); @@ -727,10 +825,10 @@ for (uint32_t i = 0; i < nfat_arch; i++) { auto arch = GetStructPointerAndAdvance(&header_data); - uint32_t cputype = ByteSwap(arch->cputype); + uint32_t cputype = ByteSwap(arch->cputype); uint32_t cpusubtype = ByteSwap(arch->cpusubtype); - uint32_t offset = ByteSwap(arch->offset); - uint32_t size = ByteSwap(arch->size); + uint32_t offset = ByteSwap(arch->offset); + uint32_t size = ByteSwap(arch->size); std::string arch_name = CpuTypeToString(cputype, cpusubtype); string_view slice_data = StrictSubstr(file_data().data(), offset, size); @@ -739,7 +837,10 @@ } } else { auto header = GetStructPointer(file_data().data()); - std::string arch_name = CpuTypeToString(header->cputype, header->cpusubtype); + bool swap = (magic == MH_CIGAM || magic == MH_CIGAM_64); + uint32_t cputype = MaybeSwap32(header->cputype, swap); + uint32_t cpusubtype = MaybeSwap32(header->cpusubtype, swap); + std::string arch_name = CpuTypeToString(cputype, cpusubtype); sink->AddFileRange("archs", arch_name, file_data().data()); } @@ -758,10 +859,12 @@ std::unique_ptr TryOpenMachOFile(std::unique_ptr &file) { uint32_t magic = macho::ReadMagic(file->data()); - // We only support little-endian host and little endian binaries (see - // ParseMachOHeader() for more rationale). Fat headers are always on disk as - // big-endian. - if (magic == MH_MAGIC || magic == MH_MAGIC_64 || magic == FAT_CIGAM) { + // Support little-endian (MH_MAGIC/MH_MAGIC_64), big-endian thin binaries + // (MH_CIGAM/MH_CIGAM_64, e.g. PowerPC), and universal fat binaries + // (FAT_CIGAM, always big-endian on disk). + if (magic == MH_MAGIC || magic == MH_MAGIC_64 || + magic == MH_CIGAM || magic == MH_CIGAM_64 || + magic == FAT_CIGAM) { return std::unique_ptr( new macho::MachOObjectFile(std::move(file))); }