cmMachO.cxx 9.0 KB

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