/ src / common / mac / macho_walker.cc
macho_walker.cc
  1  // Copyright 2006 Google LLC
  2  //
  3  // Redistribution and use in source and binary forms, with or without
  4  // modification, are permitted provided that the following conditions are
  5  // met:
  6  //
  7  //     * Redistributions of source code must retain the above copyright
  8  // notice, this list of conditions and the following disclaimer.
  9  //     * Redistributions in binary form must reproduce the above
 10  // copyright notice, this list of conditions and the following disclaimer
 11  // in the documentation and/or other materials provided with the
 12  // distribution.
 13  //     * Neither the name of Google LLC nor the names of its
 14  // contributors may be used to endorse or promote products derived from
 15  // this software without specific prior written permission.
 16  //
 17  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 21  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 22  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 23  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 24  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 25  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 27  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28  
 29  // macho_walker.cc: Iterate over the load commands in a mach-o file
 30  //
 31  // See macho_walker.h for documentation
 32  //
 33  // Author: Dan Waylonis
 34  
 35  #ifdef HAVE_CONFIG_H
 36  #include <config.h>  // Must come first
 37  #endif
 38  
 39  #include <assert.h>
 40  #include <fcntl.h>
 41  #include <mach-o/fat.h>
 42  #include <mach-o/loader.h>
 43  #include <string.h>
 44  #include <unistd.h>
 45  
 46  #include "common/mac/arch_utilities.h"
 47  #include "common/mac/byteswap.h"
 48  #include "common/mac/macho_utilities.h"
 49  #include "common/mac/macho_walker.h"
 50  
 51  namespace MacFileUtilities {
 52  
 53  MachoWalker::MachoWalker(const char* path, LoadCommandCallback callback,
 54                           void* context)
 55      : file_(-1),
 56        memory_(NULL),
 57        memory_size_(0),
 58        callback_(callback),
 59        callback_context_(context),
 60        current_header_(NULL),
 61        current_header_size_(0),
 62        current_header_offset_(0) {
 63    file_ = open(path, O_RDONLY);
 64  }
 65  
 66  MachoWalker::MachoWalker(void* memory, size_t size,
 67                           LoadCommandCallback callback, void* context)
 68      : file_(-1),
 69        memory_(memory),
 70        memory_size_(size),
 71        callback_(callback),
 72        callback_context_(context),
 73        current_header_(NULL),
 74        current_header_size_(0),
 75        current_header_offset_(0) {
 76  }
 77  
 78  MachoWalker::~MachoWalker() {
 79    if (file_ != -1)
 80      close(file_);
 81  }
 82  
 83  bool MachoWalker::WalkHeader(cpu_type_t cpu_type, cpu_subtype_t cpu_subtype) {
 84    cpu_type_t valid_cpu_type = cpu_type;
 85    cpu_subtype_t valid_cpu_subtype = cpu_subtype;
 86    // if |cpu_type| is 0, use the native cpu type.
 87    if (cpu_type == 0) {
 88      ArchInfo arch = GetLocalArchInfo();
 89      valid_cpu_type = arch.cputype;
 90      valid_cpu_subtype = CPU_SUBTYPE_MULTIPLE;
 91    }
 92    off_t offset;
 93    if (FindHeader(valid_cpu_type, valid_cpu_subtype, offset)) {
 94      if (cpu_type & CPU_ARCH_ABI64)
 95        return WalkHeader64AtOffset(offset);
 96  
 97      return WalkHeaderAtOffset(offset);
 98    }
 99  
100    return false;
101  }
102  
103  bool MachoWalker::ReadBytes(void* buffer, size_t size, off_t offset) {
104    if (memory_) {
105      if (offset < 0)
106        return false;
107      bool result = true;
108      if (offset + size > memory_size_) {
109        if (static_cast<size_t>(offset) >= memory_size_)
110          return false;
111        size = memory_size_ - static_cast<size_t>(offset);
112        result = false;
113      }
114      memcpy(buffer, static_cast<char*>(memory_) + offset, size);
115      return result;
116    } else {
117      return pread(file_, buffer, size, offset) == (ssize_t)size;
118    }
119  }
120  
121  bool MachoWalker::CurrentHeader(struct mach_header_64* header, off_t* offset) {
122    if (current_header_) {
123      memcpy(header, current_header_, sizeof(mach_header_64));
124      *offset = current_header_offset_;
125      return true;
126    }
127  
128    return false;
129  }
130  
131  bool MachoWalker::FindHeader(cpu_type_t cpu_type,
132                               cpu_subtype_t cpu_subtype,
133                               off_t& offset) {
134    // Read the magic bytes that's common amongst all mach-o files
135    uint32_t magic;
136    if (!ReadBytes(&magic, sizeof(magic), 0))
137      return false;
138  
139    offset = sizeof(magic);
140  
141    // Figure out what type of file we've got
142    bool is_fat = false;
143    if (magic == FAT_MAGIC || magic == FAT_CIGAM) {
144      is_fat = true;
145    }
146    else if (magic != MH_MAGIC && magic != MH_CIGAM && magic != MH_MAGIC_64 &&
147             magic != MH_CIGAM_64) {
148      return false;
149    }
150  
151    if (!is_fat) {
152      // If we don't have a fat header, check if the cpu type matches the single
153      // header
154      struct mach_header header;
155      if (!ReadBytes(&header, sizeof(header), 0))
156        return false;
157  
158      if (magic == MH_CIGAM || magic == MH_CIGAM_64)
159        breakpad_swap_mach_header(&header);
160  
161      if (cpu_type != header.cputype ||
162          (cpu_subtype != CPU_SUBTYPE_MULTIPLE &&
163           cpu_subtype != header.cpusubtype)) {
164        return false;
165      }
166  
167      offset = 0;
168      return true;
169    } else {
170      // Read the fat header and find an appropriate architecture
171      offset = 0;
172      struct fat_header fat;
173      if (!ReadBytes(&fat, sizeof(fat), offset))
174        return false;
175  
176      if (NXHostByteOrder() != NX_BigEndian)
177        breakpad_swap_fat_header(&fat);
178  
179      offset += sizeof(fat);
180  
181      // Search each architecture for the desired one
182      struct fat_arch arch;
183      for (uint32_t i = 0; i < fat.nfat_arch; ++i) {
184        if (!ReadBytes(&arch, sizeof(arch), offset))
185          return false;
186  
187        if (NXHostByteOrder() != NX_BigEndian)
188          breakpad_swap_fat_arch(&arch, 1);
189  
190        if (arch.cputype == cpu_type &&
191            (cpu_subtype == CPU_SUBTYPE_MULTIPLE ||
192             arch.cpusubtype == cpu_subtype)) {
193          offset = arch.offset;
194          return true;
195        }
196  
197        offset += sizeof(arch);
198      }
199    }
200  
201    return false;
202  }
203  
204  bool MachoWalker::WalkHeaderAtOffset(off_t offset) {
205    struct mach_header header;
206    if (!ReadBytes(&header, sizeof(header), offset))
207      return false;
208  
209    bool swap = (header.magic == MH_CIGAM);
210    if (swap)
211      breakpad_swap_mach_header(&header);
212  
213    // Copy the data into the mach_header_64 structure.  Since the 32-bit and
214    // 64-bit only differ in the last field (reserved), this is safe to do.
215    struct mach_header_64 header64;
216    memcpy((void*)&header64, (const void*)&header, sizeof(header));
217    header64.reserved = 0;
218  
219    current_header_ = &header64;
220    current_header_size_ = sizeof(header); // 32-bit, not 64-bit
221    current_header_offset_ = offset;
222    offset += current_header_size_;
223    bool result = WalkHeaderCore(offset, header.ncmds, swap);
224    current_header_ = NULL;
225    current_header_size_ = 0;
226    current_header_offset_ = 0;
227    return result;
228  }
229  
230  bool MachoWalker::WalkHeader64AtOffset(off_t offset) {
231    struct mach_header_64 header;
232    if (!ReadBytes(&header, sizeof(header), offset))
233      return false;
234  
235    bool swap = (header.magic == MH_CIGAM_64);
236    if (swap)
237      breakpad_swap_mach_header_64(&header);
238  
239    current_header_ = &header;
240    current_header_size_ = sizeof(header);
241    current_header_offset_ = offset;
242    offset += current_header_size_;
243    bool result = WalkHeaderCore(offset, header.ncmds, swap);
244    current_header_ = NULL;
245    current_header_size_ = 0;
246    current_header_offset_ = 0;
247    return result;
248  }
249  
250  bool MachoWalker::WalkHeaderCore(off_t offset, uint32_t number_of_commands,
251                                   bool swap) {
252    for (uint32_t i = 0; i < number_of_commands; ++i) {
253      struct load_command cmd;
254      if (!ReadBytes(&cmd, sizeof(cmd), offset))
255        return false;
256  
257      if (swap)
258        breakpad_swap_load_command(&cmd);
259  
260      // Call the user callback
261      if (callback_ && !callback_(this, &cmd, offset, swap, callback_context_))
262        break;
263  
264      offset += cmd.cmdsize;
265    }
266  
267    return true;
268  }
269  
270  }  // namespace MacFileUtilities