summaryrefslogtreecommitdiff
path: root/Source/cmMachO.cxx
blob: 4fcaedf370cfa3f7582f0f2f04c6e7c852f4c462 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/* 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 <cstddef>
#include <string>
#include <vector>

#include <cm/memory>

#include "cmsys/FStream.hxx"

#include "cmAlgorithms.h"

// 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)
{
  return static_cast<bool>(fin.read(reinterpret_cast<char*>(&v), sizeof(T)));
}

// 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;
  }
  return static_cast<bool>(
    fin.read(reinterpret_cast<char*>(&v[0]), sizeof(T) * v.size()));
}
}

// 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() = default;

  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 <typename 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(const cmMachOInternal&) = delete;
  ~cmMachOInternal();

  cmMachOInternal& operator=(const cmMachOInternal&) = delete;

  // 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<std::unique_ptr<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() = default;

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

  std::unique_ptr<cmMachOHeaderAndLoadCommands> f;
  if (magic == MH_CIGAM || magic == MH_MAGIC) {
    bool swap = false;
    if (magic == MH_CIGAM) {
      swap = true;
    }
    f = cm::make_unique<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 =
      cm::make_unique<cmMachOHeaderAndLoadCommandsImpl<mach_header_64>>(swap);
  }

  if (f && f->read_mach_o(this->Fin)) {
    this->MachOList.push_back(std::move(f));
  } else {
    this->ErrorMessage = "Failed to read Mach-O header.";
    return false;
  }

  return true;
}

//============================================================================
// External class implementation.

cmMachO::cmMachO(const char* fname)
  : Internal(cm::make_unique<cmMachOInternal>(fname))
{
}

cmMachO::~cmMachO() = default;

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
  std::unique_ptr<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 = static_cast<uint32_t>(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
{
}