| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmMachO.h" |
| |
| #include <cstddef> |
| #include <string> |
| |
| #include <cm/memory> |
| |
| #include "cmsys/FStream.hxx" |
| |
| #include "cmAlgorithms.h" |
| |
| // Include the Mach-O format information system header. |
| #include <mach-o/arch.h> |
| #include <mach-o/fat.h> |
| #include <mach-o/loader.h> |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 130000 |
| # include <mach-o/utils.h> |
| #endif |
| |
| /** |
| |
| https://developer.apple.com/library/mac/documentation/ |
| DeveloperTools/Conceptual/MachORuntime/index.html |
| |
| A Mach-O file has 3 major regions: header, load commands and segments. |
| Data Structures are provided from <mach-o/loader.h> which |
| correspond to the file structure. |
| |
| The header can be either a struct mach_header or struct mach_header_64. |
| One can peek at the first 4 bytes to identify the type of header. |
| |
| Following is the load command region which starts with |
| struct load_command, and is followed by n number of load commands. |
| |
| In the case of a universal binary (an archive of multiple Mach-O files), |
| the file begins with a struct fat_header and is followed by multiple |
| struct fat_arch instances. The struct fat_arch indicates the offset |
| for each Mach-O file. |
| |
| */ |
| |
| namespace { |
| |
| // peek in the file |
| template <typename T> |
| bool peek(cmsys::ifstream& fin, T& v) |
| { |
| std::streampos p = fin.tellg(); |
| if (!fin.read(reinterpret_cast<char*>(&v), sizeof(T))) { |
| return false; |
| } |
| fin.seekg(p); |
| return fin.good(); |
| } |
| |
| // read from the file and fill a data structure |
| template <typename T> |
| bool read(cmsys::ifstream& fin, T& v) |
| { |
| return static_cast<bool>(fin.read(reinterpret_cast<char*>(&v), sizeof(T))); |
| } |
| |
| // read from the file and fill multiple data structures where |
| // the vector has been resized |
| template <typename T> |
| bool read(cmsys::ifstream& fin, std::vector<T>& v) |
| { |
| // nothing to read |
| if (v.empty()) { |
| return true; |
| } |
| return static_cast<bool>( |
| fin.read(reinterpret_cast<char*>(v.data()), sizeof(T) * v.size())); |
| } |
| } |
| |
| // Contains header and load commands for a single Mach-O file |
| class cmMachOHeaderAndLoadCommands |
| { |
| public: |
| // A load_command and its associated data |
| struct RawLoadCommand |
| { |
| uint32_t type(const cmMachOHeaderAndLoadCommands& m) const |
| { |
| if (this->LoadCommand.size() < sizeof(load_command)) { |
| return 0; |
| } |
| const load_command* cmd = |
| reinterpret_cast<const load_command*>(&this->LoadCommand[0]); |
| return m.swap(cmd->cmd); |
| } |
| std::vector<char> LoadCommand; |
| }; |
| |
| cmMachOHeaderAndLoadCommands(bool _swap) |
| : Swap(_swap) |
| { |
| } |
| virtual ~cmMachOHeaderAndLoadCommands() = default; |
| |
| virtual bool read_mach_o(cmsys::ifstream& fin) = 0; |
| |
| const std::vector<RawLoadCommand>& load_commands() const |
| { |
| return this->LoadCommands; |
| } |
| |
| uint32_t swap(uint32_t v) const |
| { |
| if (this->Swap) { |
| char* c = reinterpret_cast<char*>(&v); |
| std::swap(c[0], c[3]); |
| std::swap(c[1], c[2]); |
| } |
| return v; |
| } |
| |
| struct cmMachO::MachHeader mach_header() const { return MachHeader; } |
| |
| protected: |
| bool read_load_commands(uint32_t ncmds, uint32_t sizeofcmds, |
| cmsys::ifstream& fin); |
| |
| bool Swap; |
| std::vector<RawLoadCommand> LoadCommands; |
| struct cmMachO::MachHeader MachHeader; |
| }; |
| |
| // Implementation for reading Mach-O header and load commands. |
| // This is 32 or 64 bit arch specific. |
| template <typename T> |
| class cmMachOHeaderAndLoadCommandsImpl : public cmMachOHeaderAndLoadCommands |
| { |
| public: |
| cmMachOHeaderAndLoadCommandsImpl(bool _swap) |
| : cmMachOHeaderAndLoadCommands(_swap) |
| { |
| } |
| bool read_mach_o(cmsys::ifstream& fin) override |
| { |
| if (!read(fin, this->Header)) { |
| return false; |
| } |
| // swap the header data and export a (potentially) useful subset via the |
| // parent class. |
| this->MachHeader.CpuType = swap(this->Header.cputype); |
| this->MachHeader.CpuSubType = swap(this->Header.cpusubtype); |
| this->MachHeader.FileType = swap(this->Header.filetype); |
| this->Header.ncmds = swap(this->Header.ncmds); |
| this->Header.sizeofcmds = swap(this->Header.sizeofcmds); |
| this->Header.flags = swap(this->Header.flags); |
| |
| return read_load_commands(this->Header.ncmds, this->Header.sizeofcmds, |
| fin); |
| } |
| |
| protected: |
| T Header; |
| }; |
| |
| bool cmMachOHeaderAndLoadCommands::read_load_commands(uint32_t ncmds, |
| uint32_t sizeofcmds, |
| cmsys::ifstream& fin) |
| { |
| uint32_t size_read = 0; |
| this->LoadCommands.resize(ncmds); |
| for (uint32_t i = 0; i < ncmds; i++) { |
| load_command lc; |
| if (!peek(fin, lc)) { |
| return false; |
| } |
| lc.cmd = swap(lc.cmd); |
| lc.cmdsize = swap(lc.cmdsize); |
| size_read += lc.cmdsize; |
| |
| RawLoadCommand& c = this->LoadCommands[i]; |
| c.LoadCommand.resize(lc.cmdsize); |
| if (!read(fin, c.LoadCommand)) { |
| return false; |
| } |
| } |
| |
| if (size_read != sizeofcmds) { |
| this->LoadCommands.clear(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| class cmMachOInternal |
| { |
| public: |
| cmMachOInternal(const char* fname); |
| cmMachOInternal(const cmMachOInternal&) = delete; |
| ~cmMachOInternal(); |
| |
| cmMachOInternal& operator=(const cmMachOInternal&) = delete; |
| |
| // read a Mach-O file |
| bool read_mach_o(uint32_t file_offset); |
| |
| // the file we are reading |
| cmsys::ifstream Fin; |
| |
| // The archs in the universal binary |
| // If the binary is not a universal binary, this will be empty. |
| std::vector<fat_arch> FatArchs; |
| |
| // the error message while parsing |
| std::string ErrorMessage; |
| |
| // the list of Mach-O's |
| std::vector<std::unique_ptr<cmMachOHeaderAndLoadCommands>> MachOList; |
| }; |
| |
| cmMachOInternal::cmMachOInternal(const char* fname) |
| : Fin(fname) |
| { |
| // Quit now if the file could not be opened. |
| if (!this->Fin || !this->Fin.get()) { |
| this->ErrorMessage = "Error opening input file."; |
| return; |
| } |
| |
| if (!this->Fin.seekg(0)) { |
| this->ErrorMessage = "Error seeking to beginning of file."; |
| return; |
| } |
| |
| // Read the binary identification block. |
| uint32_t magic = 0; |
| if (!peek(this->Fin, magic)) { |
| this->ErrorMessage = "Error reading Mach-O identification."; |
| return; |
| } |
| |
| // Verify the binary identification. |
| if (!(magic == MH_CIGAM || magic == MH_MAGIC || magic == MH_CIGAM_64 || |
| magic == MH_MAGIC_64 || magic == FAT_CIGAM || magic == FAT_MAGIC)) { |
| this->ErrorMessage = "File does not have a valid Mach-O identification."; |
| return; |
| } |
| |
| if (magic == FAT_MAGIC || magic == FAT_CIGAM) { |
| // this is a universal binary |
| fat_header header; |
| if (!read(this->Fin, header)) { |
| this->ErrorMessage = "Error reading fat header."; |
| return; |
| } |
| |
| // read fat_archs |
| this->FatArchs.resize(OSSwapBigToHostInt32(header.nfat_arch)); |
| if (!read(this->Fin, this->FatArchs)) { |
| this->ErrorMessage = "Error reading fat header archs."; |
| return; |
| } |
| |
| // parse each Mach-O file |
| for (const auto& arch : this->FatArchs) { |
| if (!this->read_mach_o(OSSwapBigToHostInt32(arch.offset))) { |
| return; |
| } |
| } |
| } else { |
| // parse Mach-O file at the beginning of the file |
| this->read_mach_o(0); |
| } |
| } |
| |
| cmMachOInternal::~cmMachOInternal() = default; |
| |
| bool cmMachOInternal::read_mach_o(uint32_t file_offset) |
| { |
| if (!this->Fin.seekg(file_offset)) { |
| this->ErrorMessage = "Failed to locate Mach-O content."; |
| return false; |
| } |
| |
| uint32_t magic; |
| if (!peek(this->Fin, magic)) { |
| this->ErrorMessage = "Error reading Mach-O identification."; |
| return false; |
| } |
| |
| std::unique_ptr<cmMachOHeaderAndLoadCommands> f; |
| if (magic == MH_CIGAM || magic == MH_MAGIC) { |
| bool swap = false; |
| if (magic == MH_CIGAM) { |
| swap = true; |
| } |
| f = cm::make_unique<cmMachOHeaderAndLoadCommandsImpl<mach_header>>(swap); |
| } else if (magic == MH_CIGAM_64 || magic == MH_MAGIC_64) { |
| bool swap = false; |
| if (magic == MH_CIGAM_64) { |
| swap = true; |
| } |
| f = |
| cm::make_unique<cmMachOHeaderAndLoadCommandsImpl<mach_header_64>>(swap); |
| } |
| |
| if (f && f->read_mach_o(this->Fin)) { |
| this->MachOList.push_back(std::move(f)); |
| } else { |
| this->ErrorMessage = "Failed to read Mach-O header."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| //============================================================================ |
| // External class implementation. |
| |
| cmMachO::cmMachO(const char* fname) |
| : Internal(cm::make_unique<cmMachOInternal>(fname)) |
| { |
| for (const auto& m : this->Internal->MachOList) { |
| Headers.push_back(m->mach_header()); |
| } |
| } |
| |
| cmMachO::~cmMachO() = default; |
| |
| std::string const& cmMachO::GetErrorMessage() const |
| { |
| return this->Internal->ErrorMessage; |
| } |
| |
| bool cmMachO::Valid() const |
| { |
| return !this->Internal->MachOList.empty(); |
| } |
| |
| bool cmMachO::GetInstallName(std::string& install_name) |
| { |
| if (this->Internal->MachOList.empty()) { |
| return false; |
| } |
| |
| // grab the first Mach-O and get the install name from that one |
| std::unique_ptr<cmMachOHeaderAndLoadCommands>& macho = |
| this->Internal->MachOList[0]; |
| for (size_t i = 0; i < macho->load_commands().size(); i++) { |
| const cmMachOHeaderAndLoadCommands::RawLoadCommand& cmd = |
| macho->load_commands()[i]; |
| uint32_t lc_cmd = cmd.type(*macho); |
| if (lc_cmd == LC_ID_DYLIB || lc_cmd == LC_LOAD_WEAK_DYLIB || |
| lc_cmd == LC_LOAD_DYLIB) { |
| if (sizeof(dylib_command) < cmd.LoadCommand.size()) { |
| uint32_t namelen = static_cast<uint32_t>(cmd.LoadCommand.size() - |
| sizeof(dylib_command)); |
| install_name.assign(&cmd.LoadCommand[sizeof(dylib_command)], namelen); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| void cmMachO::PrintInfo(std::ostream& /*os*/) const |
| { |
| } |
| |
| cmMachO::StringList cmMachO::GetArchitectures() const |
| { |
| cmMachO::StringList archs; |
| if (Valid() && !this->Headers.empty()) { |
| for (const auto& header : this->Headers) { |
| const char* archName = "unknown"; |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 130000 |
| if (__builtin_available(macOS 13.0, *)) { |
| archName = (header.CpuType & CPU_TYPE_ARM) |
| ? macho_arch_name_for_cpu_type(header.CpuType, header.CpuSubType) |
| : macho_arch_name_for_cpu_type(header.CpuType, CPU_SUBTYPE_MULTIPLE); |
| } else |
| #endif |
| { |
| #if defined __clang__ |
| # define CM_MACOS_DEPRECATED_NXGetArchInfoFromCpuType |
| # pragma clang diagnostic push |
| # pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| #endif |
| const NXArchInfo* archInfo = (header.CpuType & CPU_TYPE_ARM) |
| ? NXGetArchInfoFromCpuType(header.CpuType, header.CpuSubType) |
| : NXGetArchInfoFromCpuType(header.CpuType, CPU_SUBTYPE_MULTIPLE); |
| #ifdef CM_MACOS_DEPRECATED_NXGetArchInfoFromCpuType |
| # undef CM_MACOS_DEPRECATED_NXGetArchInfoFromCpuType |
| # pragma clang diagnostic pop |
| #endif |
| if (archInfo) { |
| archName = archInfo->name; |
| } |
| } |
| archs.push_back(archName); |
| } |
| } |
| return archs; |
| } |