Przeglądaj źródła

file: Add undocumented READ_MACHO subcommand on macOS

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

Issue: #25952
René Bertin 1 rok temu
rodzic
commit
598bc70474

+ 2 - 0
Source/CMakeLists.txt

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

+ 2 - 0
Source/cmFileCommand.cxx

@@ -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 },

+ 99 - 0
Source/cmFileCommand_ReadMacho.cxx

@@ -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;
+}

+ 11 - 0
Source/cmFileCommand_ReadMacho.h

@@ -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);

+ 51 - 4
Source/cmMachO.cxx

@@ -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;
+}

+ 22 - 0
Source/cmMachO.h

@@ -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;
 };

+ 11 - 0
bootstrap

@@ -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"