1// Copyright (c) 2006, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30// macho_id.cc: Functions to gather identifying information from a macho file
31//
32// See macho_id.h for documentation
33//
34// Author: Dan Waylonis
35
36
37#include <fcntl.h>
38#include <mach-o/loader.h>
39#include <stdio.h>
40#include <string.h>
41
42#include "common/mac/macho_id.h"
43#include "common/mac/macho_walker.h"
44#include "common/mac/macho_utilities.h"
45
46namespace MacFileUtilities {
47
48using google_breakpad::MD5Init;
49using google_breakpad::MD5Update;
50using google_breakpad::MD5Final;
51
52MachoID::MachoID(const char* path)
53 : memory_(0), memory_size_(0), md5_context_(), update_function_(NULL) {
54 snprintf(path_, sizeof(path_), "%s", path);
55}
56
57MachoID::MachoID(void* memory, size_t size)
58 : path_(),
59 memory_(memory),
60 memory_size_(size),
61 md5_context_(),
62 update_function_(NULL) {}
63
64MachoID::~MachoID() {}
65
66void MachoID::UpdateMD5(unsigned char* bytes, size_t size) {
67 MD5Update(&md5_context_, bytes, static_cast<unsigned>(size));
68}
69
70void MachoID::Update(MachoWalker* walker, off_t offset, size_t size) {
71 if (!update_function_ || !size)
72 return;
73
74 // Read up to 4k bytes at a time
75 unsigned char buffer[4096];
76 size_t buffer_size;
77 off_t file_offset = offset;
78 while (size > 0) {
79 if (size > sizeof(buffer)) {
80 buffer_size = sizeof(buffer);
81 size -= buffer_size;
82 } else {
83 buffer_size = size;
84 size = 0;
85 }
86
87 if (!walker->ReadBytes(buffer, buffer_size, file_offset))
88 return;
89
90 (this->*update_function_)(buffer, buffer_size);
91 file_offset += buffer_size;
92 }
93}
94
95bool MachoID::UUIDCommand(cpu_type_t cpu_type,
96 cpu_subtype_t cpu_subtype,
97 unsigned char bytes[16]) {
98 struct breakpad_uuid_command uuid_cmd;
99 uuid_cmd.cmd = 0;
100 if (!WalkHeader(cpu_type, cpu_subtype, UUIDWalkerCB, &uuid_cmd))
101 return false;
102
103 // If we found the command, we'll have initialized the uuid_command
104 // structure
105 if (uuid_cmd.cmd == LC_UUID) {
106 memcpy(bytes, uuid_cmd.uuid, sizeof(uuid_cmd.uuid));
107 return true;
108 }
109
110 return false;
111}
112
113bool MachoID::MD5(cpu_type_t cpu_type, cpu_subtype_t cpu_subtype, unsigned char identifier[16]) {
114 update_function_ = &MachoID::UpdateMD5;
115
116 MD5Init(&md5_context_);
117
118 if (!WalkHeader(cpu_type, cpu_subtype, WalkerCB, this))
119 return false;
120
121 MD5Final(identifier, &md5_context_);
122 return true;
123}
124
125bool MachoID::WalkHeader(cpu_type_t cpu_type,
126 cpu_subtype_t cpu_subtype,
127 MachoWalker::LoadCommandCallback callback,
128 void* context) {
129 if (memory_) {
130 MachoWalker walker(memory_, memory_size_, callback, context);
131 return walker.WalkHeader(cpu_type, cpu_subtype);
132 } else {
133 MachoWalker walker(path_, callback, context);
134 return walker.WalkHeader(cpu_type, cpu_subtype);
135 }
136}
137
138// static
139bool MachoID::WalkerCB(MachoWalker* walker, load_command* cmd, off_t offset,
140 bool swap, void* context) {
141 MachoID* macho_id = (MachoID*)context;
142
143 if (cmd->cmd == LC_SEGMENT) {
144 struct segment_command seg;
145
146 if (!walker->ReadBytes(&seg, sizeof(seg), offset))
147 return false;
148
149 if (swap)
150 breakpad_swap_segment_command(&seg);
151
152 struct mach_header_64 header;
153 off_t header_offset;
154
155 if (!walker->CurrentHeader(&header, &header_offset))
156 return false;
157
158 // Process segments that have sections:
159 // (e.g., __TEXT, __DATA, __IMPORT, __OBJC)
160 offset += sizeof(struct segment_command);
161 struct section sec;
162 for (unsigned long i = 0; i < seg.nsects; ++i) {
163 if (!walker->ReadBytes(&sec, sizeof(sec), offset))
164 return false;
165
166 if (swap)
167 breakpad_swap_section(&sec, 1);
168
169 // sections of type S_ZEROFILL are "virtual" and contain no data
170 // in the file itself
171 if ((sec.flags & SECTION_TYPE) != S_ZEROFILL && sec.offset != 0)
172 macho_id->Update(walker, header_offset + sec.offset, sec.size);
173
174 offset += sizeof(struct section);
175 }
176 } else if (cmd->cmd == LC_SEGMENT_64) {
177 struct segment_command_64 seg64;
178
179 if (!walker->ReadBytes(&seg64, sizeof(seg64), offset))
180 return false;
181
182 if (swap)
183 breakpad_swap_segment_command_64(&seg64);
184
185 struct mach_header_64 header;
186 off_t header_offset;
187
188 if (!walker->CurrentHeader(&header, &header_offset))
189 return false;
190
191 // Process segments that have sections:
192 // (e.g., __TEXT, __DATA, __IMPORT, __OBJC)
193 offset += sizeof(struct segment_command_64);
194 struct section_64 sec64;
195 for (unsigned long i = 0; i < seg64.nsects; ++i) {
196 if (!walker->ReadBytes(&sec64, sizeof(sec64), offset))
197 return false;
198
199 if (swap)
200 breakpad_swap_section_64(&sec64, 1);
201
202 // sections of type S_ZEROFILL are "virtual" and contain no data
203 // in the file itself
204 if ((sec64.flags & SECTION_TYPE) != S_ZEROFILL && sec64.offset != 0)
205 macho_id->Update(walker,
206 header_offset + sec64.offset,
207 (size_t)sec64.size);
208
209 offset += sizeof(struct section_64);
210 }
211 }
212
213 // Continue processing
214 return true;
215}
216
217// static
218bool MachoID::UUIDWalkerCB(MachoWalker* walker, load_command* cmd, off_t offset,
219 bool swap, void* context) {
220 if (cmd->cmd == LC_UUID) {
221 struct breakpad_uuid_command* uuid_cmd =
222 (struct breakpad_uuid_command*)context;
223
224 if (!walker->ReadBytes(uuid_cmd, sizeof(struct breakpad_uuid_command),
225 offset))
226 return false;
227
228 if (swap)
229 breakpad_swap_uuid_command(uuid_cmd);
230
231 return false;
232 }
233
234 // Continue processing
235 return true;
236}
237} // namespace MacFileUtilities
238