file: Add undocumented READ_MACHO subcommand on macOS

Provide a way to parse the architectures of a Mach-O binary.

Issue: #25952
This commit is contained in:
René Bertin 2024-05-04 19:59:27 +02:00 committed by Brad King
parent 1df18d5e54
commit 598bc70474
7 changed files with 198 additions and 4 deletions

View File

@ -557,6 +557,8 @@ add_library(
cmFLTKWrapUICommand.h
cmFileCommand.cxx
cmFileCommand.h
cmFileCommand_ReadMacho.cxx
cmFileCommand_ReadMacho.h
cmFindBase.cxx
cmFindBase.h
cmFindCommon.cxx

View File

@ -35,6 +35,7 @@
#include "cmELF.h"
#include "cmExecutionStatus.h"
#include "cmFSPermissions.h"
#include "cmFileCommand_ReadMacho.h"
#include "cmFileCopier.h"
#include "cmFileInstaller.h"
#include "cmFileLockPool.h"
@ -3965,6 +3966,7 @@ bool cmFileCommand(std::vector<std::string> const& args,
{ "RPATH_CHECK"_s, HandleRPathCheckCommand },
{ "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
{ "READ_ELF"_s, HandleReadElfCommand },
{ "READ_MACHO"_s, HandleReadMachoCommand },
{ "REAL_PATH"_s, HandleRealPathCommand },
{ "RELATIVE_PATH"_s, HandleRelativePathCommand },
{ "TO_CMAKE_PATH"_s, HandleCMakePathCommand },

View File

@ -0,0 +1,99 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmFileCommand_ReadMacho.h"
#include "cmArgumentParser.h"
#include "cmExecutionStatus.h"
#include "cmMakefile.h"
#include "cmRange.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#if defined(CMake_USE_MACH_PARSER)
# include "cmMachO.h"
#endif
#include <cmext/string_view>
bool HandleReadMachoCommand(std::vector<std::string> const& args,
cmExecutionStatus& status)
{
if (args.size() < 4) {
status.SetError("READ_MACHO must be called with at least three additional "
"arguments.");
return false;
}
std::string const& fileNameArg = args[1];
struct Arguments
{
std::string Architectures;
std::string Error;
};
static auto const parser =
cmArgumentParser<Arguments>{}
.Bind("ARCHITECTURES"_s, &Arguments::Architectures)
.Bind("CAPTURE_ERROR"_s, &Arguments::Error);
Arguments const arguments = parser.Parse(cmMakeRange(args).advance(2),
/*unparsedArguments=*/nullptr);
if (!arguments.Architectures.empty()) {
// always return something sensible for ARCHITECTURES
status.GetMakefile().AddDefinition(arguments.Architectures, "unknown"_s);
}
if (!cmSystemTools::FileExists(fileNameArg, true)) {
if (arguments.Error.empty()) {
status.SetError(cmStrCat("READ_MACHO given FILE \"", fileNameArg,
"\" that does not exist."));
return false;
}
status.GetMakefile().AddDefinition(
arguments.Error, cmStrCat(fileNameArg, " does not exist"));
return true;
}
#if defined(CMake_USE_MACH_PARSER)
cmMachO macho(fileNameArg.c_str());
if (!macho) {
if (arguments.Error.empty()) {
status.SetError(cmStrCat("READ_MACHO given FILE:\n ", fileNameArg,
"\nthat is not a valid Macho-O file."));
return false;
}
status.GetMakefile().AddDefinition(
arguments.Error, cmStrCat(fileNameArg, " is not a valid Macho-O file"));
return true;
} else if (!macho.GetErrorMessage().empty()) {
if (arguments.Error.empty()) {
status.SetError(cmStrCat(
"READ_MACHO given FILE:\n ", fileNameArg,
"\nthat is not a supported Macho-O file: ", macho.GetErrorMessage()));
return false;
}
status.GetMakefile().AddDefinition(
arguments.Error,
cmStrCat(fileNameArg,
" is not a supported Macho-O file: ", macho.GetErrorMessage()));
return true;
}
std::string output;
if (!arguments.Architectures.empty()) {
auto archs = macho.GetArchitectures();
output = cmJoin(archs, ";");
// Save the output in a makefile variable.
status.GetMakefile().AddDefinition(arguments.Architectures, output);
}
#else
if (arguments.Error.empty()) {
status.SetError("READ_MACHO support not available on this platform.");
return false;
}
status.GetMakefile().AddDefinition(
arguments.Error, "READ_MACHO support not available on this platform.");
#endif // CMake_USE_MACH_PARSER
return true;
}

View File

@ -0,0 +1,11 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include <string>
#include <vector>
class cmExecutionStatus;
bool HandleReadMachoCommand(std::vector<std::string> const& args,
cmExecutionStatus& status);

View File

@ -4,7 +4,6 @@
#include <cstddef>
#include <string>
#include <vector>
#include <cm/memory>
@ -13,8 +12,12 @@
#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
/**
@ -115,12 +118,15 @@ public:
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.
@ -138,9 +144,11 @@ public:
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);
// 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);
@ -311,6 +319,9 @@ bool cmMachOInternal::read_mach_o(uint32_t file_offset)
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;
@ -355,3 +366,39 @@ bool cmMachO::GetInstallName(std::string& install_name)
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;
}

View File

@ -7,6 +7,9 @@
#include <iosfwd>
#include <memory>
#include <string>
#include <vector>
#include <mach/machine.h>
#if !defined(CMake_USE_MACH_PARSER)
# error "This file may be included only if CMake_USE_MACH_PARSER is enabled."
@ -20,6 +23,16 @@ class cmMachOInternal;
class cmMachO
{
public:
struct MachHeader
{
cpu_type_t CpuType;
cpu_subtype_t CpuSubType;
uint32_t FileType;
};
class StringList : public std::vector<std::string>
{
};
/** Construct with the name of the Mach-O input file to parse. */
cmMachO(const char* fname);
@ -38,8 +51,17 @@ public:
/** Print human-readable information about the Mach-O file. */
void PrintInfo(std::ostream& os) const;
/** Get the architectural header(s) from the Mach-O file. */
std::vector<struct MachHeader> GetHeaders() const { return this->Headers; }
/** Get a list of the recognized architectures present in the Mach-O file
* in the order in which they are found.
*/
StringList GetArchitectures() const;
private:
friend class cmMachOInternal;
bool Valid() const;
std::unique_ptr<cmMachOInternal> Internal;
std::vector<struct MachHeader> Headers;
};

View File

@ -353,6 +353,7 @@ CMAKE_CXX_SOURCES="\
cmExprParserHelper \
cmExternalMakefileProjectGenerator \
cmFileCommand \
cmFileCommand_ReadMacho \
cmFileCopier \
cmFileInstaller \
cmFileSet \
@ -521,6 +522,12 @@ CMAKE_CXX_SOURCES="\
cm_fileno \
"
if ${cmake_system_darwin}; then
CMAKE_CXX_SOURCES="${CMAKE_CXX_SOURCES}\
cmMachO \
"
fi
if ${cmake_system_mingw}; then
CMAKE_CXX_SOURCES="${CMAKE_CXX_SOURCES}\
cmGlobalMSYSMakefileGenerator \
@ -1669,6 +1676,10 @@ else
cmake_report cmConfigure.h${_tmp} "#define CMAKE_BOOTSTRAP_MAKEFILES"
fi
if ${cmake_system_darwin}; then
cmake_report cmConfigure.h${_tmp} "#define CMake_USE_MACH_PARSER"
fi
if ${cmake_system_mingw}; then
cmake_report cmConfigure.h${_tmp} "#if defined(_WIN32) && !defined(NOMINMAX)"
cmake_report cmConfigure.h${_tmp} "# define NOMINMAX"