CMake/Source/cmMachO.cxx

363 lines
9.0 KiB
C++
Raw Normal View History

Simplify CMake per-source license notices Per-source copyright/license notice headers that spell out copyright holder names and years are hard to maintain and often out-of-date or plain wrong. Precise contributor information is already maintained automatically by the version control tool. Ultimately it is the receiver of a file who is responsible for determining its licensing status, and per-source notices are merely a convenience. Therefore it is simpler and more accurate for each source to have a generic notice of the license name and references to more detailed information on copyright holders and full license terms. Our `Copyright.txt` file now contains a list of Contributors whose names appeared source-level copyright notices. It also references version control history for more precise information. Therefore we no longer need to spell out the list of Contributors in each source file notice. Replace CMake per-source copyright/license notice headers with a short description of the license and links to `Copyright.txt` and online information available from "https://cmake.org/licensing". The online URL also handles cases of modules being copied out of our source into other projects, so we can drop our notices about replacing links with full license text. Run the `Utilities/Scripts/filter-notices.bash` script to perform the majority of the replacements mechanically. Manually fix up shebang lines and trailing newlines in a few files. Manually update the notices in a few files that the script does not handle.
2016-09-27 19:01:08 +00:00
/* 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 "cmsys/FStream.hxx"
2016-11-22 23:41:44 +00:00
#include <algorithm>
#include <stddef.h>
#include <string>
#include <vector>
// Include the Mach-O format information system header.
#include <mach-o/fat.h>
#include <mach-o/loader.h>
/**
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)
{
if (!fin.read(reinterpret_cast<char*>(&v), sizeof(T))) {
return false;
}
return true;
}
// 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;
}
if (!fin.read(reinterpret_cast<char*>(&v[0]), sizeof(T) * v.size())) {
return false;
}
return true;
}
}
// 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() {}
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;
}
protected:
bool read_load_commands(uint32_t ncmds, uint32_t sizeofcmds,
cmsys::ifstream& fin);
bool Swap;
std::vector<RawLoadCommand> LoadCommands;
};
// Implementation for reading Mach-O header and load commands.
// This is 32 or 64 bit arch specific.
template <class 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;
}
this->Header.cputype = swap(this->Header.cputype);
this->Header.cpusubtype = swap(this->Header.cpusubtype);
this->Header.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();
// 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<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()
{
for (auto& i : this->MachOList) {
delete i;
}
}
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;
}
cmMachOHeaderAndLoadCommands* f = nullptr;
if (magic == MH_CIGAM || magic == MH_MAGIC) {
bool swap = false;
if (magic == MH_CIGAM) {
swap = true;
}
f = new 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 = new cmMachOHeaderAndLoadCommandsImpl<mach_header_64>(swap);
}
if (f && f->read_mach_o(this->Fin)) {
this->MachOList.push_back(f);
} else {
delete f;
this->ErrorMessage = "Failed to read Mach-O header.";
return false;
}
return true;
}
//============================================================================
// External class implementation.
cmMachO::cmMachO(const char* fname)
: Internal(nullptr)
{
this->Internal = new cmMachOInternal(fname);
}
cmMachO::~cmMachO()
{
delete this->Internal;
}
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
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 = 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
{
}