cmMachO.cxx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmMachO.h"
  4. #include <cstddef>
  5. #include <string>
  6. #include <vector>
  7. #include <cm/memory>
  8. #include "cmsys/FStream.hxx"
  9. #include "cmAlgorithms.h"
  10. // Include the Mach-O format information system header.
  11. #include <mach-o/fat.h>
  12. #include <mach-o/loader.h>
  13. /**
  14. https://developer.apple.com/library/mac/documentation/
  15. DeveloperTools/Conceptual/MachORuntime/index.html
  16. A Mach-O file has 3 major regions: header, load commands and segments.
  17. Data Structures are provided from <mach-o/loader.h> which
  18. correspond to the file structure.
  19. The header can be either a struct mach_header or struct mach_header_64.
  20. One can peek at the first 4 bytes to identify the type of header.
  21. Following is the load command region which starts with
  22. struct load_command, and is followed by n number of load commands.
  23. In the case of a universal binary (an archive of multiple Mach-O files),
  24. the file begins with a struct fat_header and is followed by multiple
  25. struct fat_arch instances. The struct fat_arch indicates the offset
  26. for each Mach-O file.
  27. */
  28. namespace {
  29. // peek in the file
  30. template <typename T>
  31. bool peek(cmsys::ifstream& fin, T& v)
  32. {
  33. std::streampos p = fin.tellg();
  34. if (!fin.read(reinterpret_cast<char*>(&v), sizeof(T))) {
  35. return false;
  36. }
  37. fin.seekg(p);
  38. return fin.good();
  39. }
  40. // read from the file and fill a data structure
  41. template <typename T>
  42. bool read(cmsys::ifstream& fin, T& v)
  43. {
  44. return !!fin.read(reinterpret_cast<char*>(&v), sizeof(T));
  45. }
  46. // read from the file and fill multiple data structures where
  47. // the vector has been resized
  48. template <typename T>
  49. bool read(cmsys::ifstream& fin, std::vector<T>& v)
  50. {
  51. // nothing to read
  52. if (v.empty()) {
  53. return true;
  54. }
  55. return !!fin.read(reinterpret_cast<char*>(&v[0]), sizeof(T) * v.size());
  56. }
  57. }
  58. // Contains header and load commands for a single Mach-O file
  59. class cmMachOHeaderAndLoadCommands
  60. {
  61. public:
  62. // A load_command and its associated data
  63. struct RawLoadCommand
  64. {
  65. uint32_t type(const cmMachOHeaderAndLoadCommands& m) const
  66. {
  67. if (this->LoadCommand.size() < sizeof(load_command)) {
  68. return 0;
  69. }
  70. const load_command* cmd =
  71. reinterpret_cast<const load_command*>(&this->LoadCommand[0]);
  72. return m.swap(cmd->cmd);
  73. }
  74. std::vector<char> LoadCommand;
  75. };
  76. cmMachOHeaderAndLoadCommands(bool _swap)
  77. : Swap(_swap)
  78. {
  79. }
  80. virtual ~cmMachOHeaderAndLoadCommands() = default;
  81. virtual bool read_mach_o(cmsys::ifstream& fin) = 0;
  82. const std::vector<RawLoadCommand>& load_commands() const
  83. {
  84. return this->LoadCommands;
  85. }
  86. uint32_t swap(uint32_t v) const
  87. {
  88. if (this->Swap) {
  89. char* c = reinterpret_cast<char*>(&v);
  90. std::swap(c[0], c[3]);
  91. std::swap(c[1], c[2]);
  92. }
  93. return v;
  94. }
  95. protected:
  96. bool read_load_commands(uint32_t ncmds, uint32_t sizeofcmds,
  97. cmsys::ifstream& fin);
  98. bool Swap;
  99. std::vector<RawLoadCommand> LoadCommands;
  100. };
  101. // Implementation for reading Mach-O header and load commands.
  102. // This is 32 or 64 bit arch specific.
  103. template <typename T>
  104. class cmMachOHeaderAndLoadCommandsImpl : public cmMachOHeaderAndLoadCommands
  105. {
  106. public:
  107. cmMachOHeaderAndLoadCommandsImpl(bool _swap)
  108. : cmMachOHeaderAndLoadCommands(_swap)
  109. {
  110. }
  111. bool read_mach_o(cmsys::ifstream& fin) override
  112. {
  113. if (!read(fin, this->Header)) {
  114. return false;
  115. }
  116. this->Header.cputype = swap(this->Header.cputype);
  117. this->Header.cpusubtype = swap(this->Header.cpusubtype);
  118. this->Header.filetype = swap(this->Header.filetype);
  119. this->Header.ncmds = swap(this->Header.ncmds);
  120. this->Header.sizeofcmds = swap(this->Header.sizeofcmds);
  121. this->Header.flags = swap(this->Header.flags);
  122. return read_load_commands(this->Header.ncmds, this->Header.sizeofcmds,
  123. fin);
  124. }
  125. protected:
  126. T Header;
  127. };
  128. bool cmMachOHeaderAndLoadCommands::read_load_commands(uint32_t ncmds,
  129. uint32_t sizeofcmds,
  130. cmsys::ifstream& fin)
  131. {
  132. uint32_t size_read = 0;
  133. this->LoadCommands.resize(ncmds);
  134. for (uint32_t i = 0; i < ncmds; i++) {
  135. load_command lc;
  136. if (!peek(fin, lc)) {
  137. return false;
  138. }
  139. lc.cmd = swap(lc.cmd);
  140. lc.cmdsize = swap(lc.cmdsize);
  141. size_read += lc.cmdsize;
  142. RawLoadCommand& c = this->LoadCommands[i];
  143. c.LoadCommand.resize(lc.cmdsize);
  144. if (!read(fin, c.LoadCommand)) {
  145. return false;
  146. }
  147. }
  148. if (size_read != sizeofcmds) {
  149. this->LoadCommands.clear();
  150. return false;
  151. }
  152. return true;
  153. }
  154. class cmMachOInternal
  155. {
  156. public:
  157. cmMachOInternal(const char* fname);
  158. cmMachOInternal(const cmMachOInternal&) = delete;
  159. ~cmMachOInternal();
  160. cmMachOInternal& operator=(const cmMachOInternal&) = delete;
  161. // read a Mach-O file
  162. bool read_mach_o(uint32_t file_offset);
  163. // the file we are reading
  164. cmsys::ifstream Fin;
  165. // The archs in the universal binary
  166. // If the binary is not a universal binary, this will be empty.
  167. std::vector<fat_arch> FatArchs;
  168. // the error message while parsing
  169. std::string ErrorMessage;
  170. // the list of Mach-O's
  171. std::vector<std::unique_ptr<cmMachOHeaderAndLoadCommands>> MachOList;
  172. };
  173. cmMachOInternal::cmMachOInternal(const char* fname)
  174. : Fin(fname)
  175. {
  176. // Quit now if the file could not be opened.
  177. if (!this->Fin || !this->Fin.get()) {
  178. this->ErrorMessage = "Error opening input file.";
  179. return;
  180. }
  181. if (!this->Fin.seekg(0)) {
  182. this->ErrorMessage = "Error seeking to beginning of file.";
  183. return;
  184. }
  185. // Read the binary identification block.
  186. uint32_t magic = 0;
  187. if (!peek(this->Fin, magic)) {
  188. this->ErrorMessage = "Error reading Mach-O identification.";
  189. return;
  190. }
  191. // Verify the binary identification.
  192. if (!(magic == MH_CIGAM || magic == MH_MAGIC || magic == MH_CIGAM_64 ||
  193. magic == MH_MAGIC_64 || magic == FAT_CIGAM || magic == FAT_MAGIC)) {
  194. this->ErrorMessage = "File does not have a valid Mach-O identification.";
  195. return;
  196. }
  197. if (magic == FAT_MAGIC || magic == FAT_CIGAM) {
  198. // this is a universal binary
  199. fat_header header;
  200. if (!read(this->Fin, header)) {
  201. this->ErrorMessage = "Error reading fat header.";
  202. return;
  203. }
  204. // read fat_archs
  205. this->FatArchs.resize(OSSwapBigToHostInt32(header.nfat_arch));
  206. if (!read(this->Fin, this->FatArchs)) {
  207. this->ErrorMessage = "Error reading fat header archs.";
  208. return;
  209. }
  210. // parse each Mach-O file
  211. for (const auto& arch : this->FatArchs) {
  212. if (!this->read_mach_o(OSSwapBigToHostInt32(arch.offset))) {
  213. return;
  214. }
  215. }
  216. } else {
  217. // parse Mach-O file at the beginning of the file
  218. this->read_mach_o(0);
  219. }
  220. }
  221. cmMachOInternal::~cmMachOInternal() = default;
  222. bool cmMachOInternal::read_mach_o(uint32_t file_offset)
  223. {
  224. if (!this->Fin.seekg(file_offset)) {
  225. this->ErrorMessage = "Failed to locate Mach-O content.";
  226. return false;
  227. }
  228. uint32_t magic;
  229. if (!peek(this->Fin, magic)) {
  230. this->ErrorMessage = "Error reading Mach-O identification.";
  231. return false;
  232. }
  233. std::unique_ptr<cmMachOHeaderAndLoadCommands> f;
  234. if (magic == MH_CIGAM || magic == MH_MAGIC) {
  235. bool swap = false;
  236. if (magic == MH_CIGAM) {
  237. swap = true;
  238. }
  239. f = cm::make_unique<cmMachOHeaderAndLoadCommandsImpl<mach_header>>(swap);
  240. } else if (magic == MH_CIGAM_64 || magic == MH_MAGIC_64) {
  241. bool swap = false;
  242. if (magic == MH_CIGAM_64) {
  243. swap = true;
  244. }
  245. f =
  246. cm::make_unique<cmMachOHeaderAndLoadCommandsImpl<mach_header_64>>(swap);
  247. }
  248. if (f && f->read_mach_o(this->Fin)) {
  249. this->MachOList.push_back(std::move(f));
  250. } else {
  251. this->ErrorMessage = "Failed to read Mach-O header.";
  252. return false;
  253. }
  254. return true;
  255. }
  256. //============================================================================
  257. // External class implementation.
  258. cmMachO::cmMachO(const char* fname)
  259. : Internal(cm::make_unique<cmMachOInternal>(fname))
  260. {
  261. }
  262. cmMachO::~cmMachO() = default;
  263. std::string const& cmMachO::GetErrorMessage() const
  264. {
  265. return this->Internal->ErrorMessage;
  266. }
  267. bool cmMachO::Valid() const
  268. {
  269. return !this->Internal->MachOList.empty();
  270. }
  271. bool cmMachO::GetInstallName(std::string& install_name)
  272. {
  273. if (this->Internal->MachOList.empty()) {
  274. return false;
  275. }
  276. // grab the first Mach-O and get the install name from that one
  277. std::unique_ptr<cmMachOHeaderAndLoadCommands>& macho =
  278. this->Internal->MachOList[0];
  279. for (size_t i = 0; i < macho->load_commands().size(); i++) {
  280. const cmMachOHeaderAndLoadCommands::RawLoadCommand& cmd =
  281. macho->load_commands()[i];
  282. uint32_t lc_cmd = cmd.type(*macho);
  283. if (lc_cmd == LC_ID_DYLIB || lc_cmd == LC_LOAD_WEAK_DYLIB ||
  284. lc_cmd == LC_LOAD_DYLIB) {
  285. if (sizeof(dylib_command) < cmd.LoadCommand.size()) {
  286. uint32_t namelen = cmd.LoadCommand.size() - sizeof(dylib_command);
  287. install_name.assign(&cmd.LoadCommand[sizeof(dylib_command)], namelen);
  288. return true;
  289. }
  290. }
  291. }
  292. return false;
  293. }
  294. void cmMachO::PrintInfo(std::ostream& /*os*/) const
  295. {
  296. }