OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Crashpad Authors. All rights reserved. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 | |
15 #include "util/mac/mach_o_image_reader.h" | |
16 | |
17 #include <mach-o/loader.h> | |
18 #include <mach-o/nlist.h> | |
19 #include <string.h> | |
20 | |
21 #include <limits> | |
22 #include <vector> | |
23 | |
24 #include "base/logging.h" | |
25 #include "base/strings/stringprintf.h" | |
26 #include "util/mac/checked_mach_address_range.h" | |
27 #include "util/mac/mach_o_image_segment_reader.h" | |
28 #include "util/mac/process_reader.h" | |
29 | |
30 namespace { | |
31 | |
32 const uint32_t kInvalidSegmentIndex = std::numeric_limits<uint32_t>::max(); | |
33 | |
34 } // namespace | |
35 | |
36 namespace crashpad { | |
37 | |
38 MachOImageReader::MachOImageReader() | |
39 : segments_(), | |
40 segment_map_(), | |
41 module_info_(), | |
42 dylinker_name_(), | |
43 uuid_(), | |
44 address_(0), | |
45 size_(0), | |
46 slide_(0), | |
47 source_version_(0), | |
48 symtab_command_(), | |
49 dysymtab_command_(), | |
50 id_dylib_command_(), | |
51 process_reader_(NULL), | |
52 file_type_(0), | |
53 initialized_() { | |
54 } | |
55 | |
56 MachOImageReader::~MachOImageReader() { | |
57 } | |
58 | |
59 bool MachOImageReader::Initialize(ProcessReader* process_reader, | |
60 mach_vm_address_t address, | |
61 const std::string& name) { | |
62 INITIALIZATION_STATE_SET_INITIALIZING(initialized_); | |
63 | |
64 process_reader_ = process_reader; | |
65 address_ = address; | |
66 | |
67 module_info_ = | |
68 base::StringPrintf(", module %s, address 0x%llx", name.c_str(), address); | |
69 | |
70 process_types::mach_header mach_header; | |
71 if (!mach_header.Read(process_reader, address)) { | |
72 LOG(WARNING) << "could not read mach_header" << module_info_; | |
73 return false; | |
74 } | |
75 | |
76 const bool is_64_bit = process_reader->Is64Bit(); | |
77 const uint32_t kExpectedMagic = is_64_bit ? MH_MAGIC_64 : MH_MAGIC; | |
78 if (mach_header.magic != kExpectedMagic) { | |
79 LOG(WARNING) << base::StringPrintf("unexpected mach_header::magic 0x%08x", | |
80 mach_header.magic) << module_info_; | |
81 return false; | |
82 } | |
83 | |
84 file_type_ = mach_header.filetype; | |
85 | |
86 const uint32_t kExpectedSegmentCommand = | |
87 is_64_bit ? LC_SEGMENT_64 : LC_SEGMENT; | |
88 const uint32_t kUnexpectedSegmentCommand = | |
89 is_64_bit ? LC_SEGMENT : LC_SEGMENT_64; | |
90 | |
91 const struct { | |
92 // Which method to call when encountering a load command matching |command|. | |
93 bool (MachOImageReader::*function)(mach_vm_address_t, const std::string&); | |
94 | |
95 // The minimum size that may be allotted to store the load command. | |
96 size_t size; | |
97 | |
98 // The load command to match. | |
99 uint32_t command; | |
100 | |
101 // True if the load command must not appear more than one time. | |
102 bool singleton; | |
103 } kLoadCommandReaders[] = { | |
104 { | |
105 &MachOImageReader::ReadSegmentCommand, | |
106 process_types::segment_command::ExpectedSize(process_reader), | |
107 kExpectedSegmentCommand, | |
108 false, | |
109 }, | |
110 { | |
111 &MachOImageReader::ReadSymTabCommand, | |
112 process_types::symtab_command::ExpectedSize(process_reader), | |
113 LC_SYMTAB, | |
114 true, | |
115 }, | |
116 { | |
117 &MachOImageReader::ReadDySymTabCommand, | |
118 process_types::symtab_command::ExpectedSize(process_reader), | |
119 LC_DYSYMTAB, | |
120 true, | |
121 }, | |
122 { | |
123 &MachOImageReader::ReadIdDylibCommand, | |
124 process_types::dylib_command::ExpectedSize(process_reader), | |
125 LC_ID_DYLIB, | |
126 true, | |
127 }, | |
128 { | |
129 &MachOImageReader::ReadDylinkerCommand, | |
130 process_types::dylinker_command::ExpectedSize(process_reader), | |
131 LC_LOAD_DYLINKER, | |
132 true, | |
133 }, | |
134 { | |
135 &MachOImageReader::ReadDylinkerCommand, | |
136 process_types::dylinker_command::ExpectedSize(process_reader), | |
137 LC_ID_DYLINKER, | |
138 true, | |
139 }, | |
140 { | |
141 &MachOImageReader::ReadUUIDCommand, | |
142 process_types::uuid_command::ExpectedSize(process_reader), | |
143 LC_UUID, | |
144 true, | |
145 }, | |
146 { | |
147 &MachOImageReader::ReadSourceVersionCommand, | |
148 process_types::source_version_command::ExpectedSize(process_reader), | |
149 LC_SOURCE_VERSION, | |
150 true, | |
151 }, | |
152 | |
153 // When reading a 64-bit process, no 32-bit segment commands should be | |
154 // present, and vice-versa. | |
155 { | |
156 &MachOImageReader::ReadUnexpectedCommand, | |
157 process_types::load_command::ExpectedSize(process_reader), | |
158 kUnexpectedSegmentCommand, | |
159 false, | |
160 }, | |
161 }; | |
162 | |
163 // This vector is parallel to the kLoadCommandReaders array, and tracks | |
164 // whether a singleton load command matching the |command| field has been | |
165 // found yet. | |
166 std::vector<uint32_t> singleton_indices(arraysize(kLoadCommandReaders), | |
167 kInvalidSegmentIndex); | |
168 | |
169 size_t offset = mach_header.Size(); | |
170 const mach_vm_address_t kLoadCommandAddressLimit = | |
171 address + offset + mach_header.sizeofcmds; | |
172 | |
173 for (uint32_t load_command_index = 0; | |
174 load_command_index < mach_header.ncmds; | |
175 ++load_command_index) { | |
176 mach_vm_address_t load_command_address = address + offset; | |
177 std::string load_command_info = base::StringPrintf(", load command %u/%u%s", | |
178 load_command_index, | |
179 mach_header.ncmds, | |
180 module_info_.c_str()); | |
181 | |
182 process_types::load_command load_command; | |
183 | |
184 // Make sure that the basic load command structure doesn’t overflow the | |
185 // space allotted for load commands. | |
186 if (load_command_address + load_command.ExpectedSize(process_reader) > | |
187 kLoadCommandAddressLimit) { | |
Robert Sesek
2014/09/04 13:18:41
nit: indent +4
| |
188 LOG(WARNING) << base::StringPrintf( | |
189 "load_command at 0x%llx exceeds sizeofcmds 0x%x", | |
190 load_command_address, | |
191 mach_header.sizeofcmds) << load_command_info; | |
192 return false; | |
193 } | |
194 | |
195 if (!load_command.Read(process_reader, load_command_address)) { | |
196 LOG(WARNING) << "could not read load_command" << load_command_info; | |
197 return false; | |
198 } | |
199 | |
200 load_command_info = base::StringPrintf(", load command 0x%x %u/%u%s", | |
201 load_command.cmd, | |
202 load_command_index, | |
203 mach_header.ncmds, | |
204 module_info_.c_str()); | |
205 | |
206 // Now that the load command’s stated size is known, make sure that it | |
207 // doesn’t overflow the space allotted for load commands. | |
208 if (load_command_address + load_command.cmdsize > | |
209 kLoadCommandAddressLimit) { | |
Robert Sesek
2014/09/04 13:18:41
+4
| |
210 LOG(WARNING) | |
211 << base::StringPrintf( | |
212 "load_command at 0x%llx cmdsize 0x%x exceeds sizeofcmds 0x%x", | |
213 load_command_address, | |
214 load_command.cmdsize, | |
215 mach_header.sizeofcmds) << load_command_info; | |
216 return false; | |
217 } | |
218 | |
219 for (size_t reader_index = 0; | |
220 reader_index < arraysize(kLoadCommandReaders); | |
221 ++reader_index) { | |
222 if (load_command.cmd == kLoadCommandReaders[reader_index].command) { | |
Robert Sesek
2014/09/04 13:18:41
Flip this to a != and continue instead? Would redu
| |
223 if (load_command.cmdsize < kLoadCommandReaders[reader_index].size) { | |
224 LOG(WARNING) | |
225 << base::StringPrintf( | |
226 "load command cmdsize 0x%x insufficient for 0x%zx", | |
227 load_command.cmdsize, | |
228 kLoadCommandReaders[reader_index].size) | |
229 << load_command_info; | |
230 return false; | |
231 } | |
232 | |
233 if (kLoadCommandReaders[reader_index].singleton) { | |
234 if (singleton_indices[reader_index] != kInvalidSegmentIndex) { | |
235 LOG(WARNING) << "duplicate load command at " | |
236 << singleton_indices[reader_index] | |
237 << load_command_info; | |
238 return false; | |
239 } | |
240 | |
241 singleton_indices[reader_index] = load_command_index; | |
242 } | |
243 | |
244 if (!((this)->*(kLoadCommandReaders[reader_index].function))( | |
245 load_command_address, load_command_info)) { | |
246 return false; | |
247 } | |
248 | |
249 break; | |
250 } | |
251 } | |
252 | |
253 offset += load_command.cmdsize; | |
254 } | |
255 | |
256 // This was already checked for the unslid values while the segments were | |
257 // read, but now that the slide is known, check the slid values too. The | |
258 // individual sections don’t need to be checked because they were verified to | |
259 // be contained within their respective segments when the segments were read. | |
260 for (const MachOImageSegmentReader* segment : segments_) { | |
261 mach_vm_address_t slid_segment_address = segment->vmaddr(); | |
262 mach_vm_size_t slid_segment_size = segment->vmsize(); | |
263 if (segment->SegmentSlides()) { | |
264 slid_segment_address += slide_; | |
265 } else { | |
266 // The non-sliding __PAGEZERO segment extends instead of slides. | |
267 slid_segment_size += slide_; | |
268 } | |
269 CheckedMachAddressRange slid_segment_range( | |
270 process_reader_, slid_segment_address, slid_segment_size); | |
271 if (!slid_segment_range.IsValid()) { | |
272 LOG(WARNING) << base::StringPrintf( | |
273 "invalid slid segment range 0x%llx + 0x%llx, " | |
274 "segment ", | |
275 slid_segment_address, | |
276 slid_segment_size) << segment->Name() << module_info_; | |
277 return false; | |
278 } | |
279 } | |
280 | |
281 if (!segment_map_.count(SEG_TEXT)) { | |
282 // The __TEXT segment is required. Even a module with no executable code | |
283 // will have a __TEXT segment encompassing the Mach-O header and load | |
284 // commands. Without a __TEXT segment, |size_| will not have been computed. | |
285 LOG(WARNING) << "no " SEG_TEXT " segment" << module_info_; | |
286 return false; | |
287 } | |
288 | |
289 if (mach_header.filetype == MH_DYLIB && !id_dylib_command_) { | |
290 // This doesn’t render a module unusable, it’s just weird and worth noting. | |
291 LOG(INFO) << "no LC_ID_DYLIB" << module_info_; | |
292 } | |
293 | |
294 INITIALIZATION_STATE_SET_VALID(initialized_); | |
295 return true; | |
296 } | |
297 | |
298 const MachOImageSegmentReader* MachOImageReader::GetSegmentByName( | |
299 const std::string& segment_name, | |
300 mach_vm_address_t* address, | |
301 mach_vm_size_t* size) const { | |
302 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | |
303 | |
304 const auto& iterator = segment_map_.find(segment_name); | |
305 if (iterator == segment_map_.end()) { | |
306 return NULL; | |
307 } | |
308 | |
309 const MachOImageSegmentReader* segment = segments_[iterator->second]; | |
310 if (address) { | |
311 *address = segment->vmaddr() + (segment->SegmentSlides() ? slide_ : 0); | |
312 } | |
313 if (size) { | |
314 *size = segment->vmsize() + (segment->SegmentSlides() ? 0 : slide_); | |
Robert Sesek
2014/09/04 13:18:41
This is because if the segment doesn't slide, it g
| |
315 } | |
316 | |
317 return segment; | |
318 } | |
319 | |
320 const process_types::section* MachOImageReader::GetSectionByName( | |
321 const std::string& segment_name, | |
322 const std::string& section_name, | |
323 mach_vm_address_t* address) const { | |
324 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | |
325 | |
326 const MachOImageSegmentReader* segment = | |
327 GetSegmentByName(segment_name, NULL, NULL); | |
328 if (!segment) { | |
329 return NULL; | |
330 } | |
331 | |
332 const process_types::section* section = | |
333 segment->GetSectionByName(section_name); | |
334 if (!section) { | |
335 return NULL; | |
336 } | |
337 | |
338 if (address) { | |
339 *address = section->addr + (segment->SegmentSlides() ? slide_ : 0); | |
340 } | |
341 | |
342 return section; | |
343 } | |
344 | |
345 const process_types::section* MachOImageReader::GetSectionAtIndex( | |
346 size_t index, | |
347 mach_vm_address_t* address) const { | |
348 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | |
349 | |
350 COMPILE_ASSERT(NO_SECT == 0, no_sect_must_be_zero); | |
351 if (index == NO_SECT) { | |
352 LOG(WARNING) << "section index " << index << " out of range"; | |
353 return NULL; | |
354 } | |
355 | |
356 // Switch to a more comfortable 0-based index. | |
357 size_t local_index = index - 1; | |
358 | |
359 for (const MachOImageSegmentReader* segment : segments_) { | |
360 size_t nsects = segment->nsects(); | |
361 if (local_index < nsects) { | |
362 const process_types::section* section = | |
363 segment->GetSectionAtIndex(local_index); | |
364 | |
365 if (address) { | |
366 *address = section->addr + (segment->SegmentSlides() ? slide_ : 0); | |
367 } | |
368 | |
369 return section; | |
370 } | |
371 | |
372 local_index -= nsects; | |
373 } | |
374 | |
375 LOG(WARNING) << "section index " << index << " out of range"; | |
376 return NULL; | |
377 } | |
378 | |
379 uint32_t MachOImageReader::DylibVersion() const { | |
380 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | |
381 DCHECK_EQ(FileType(), static_cast<uint32_t>(MH_DYLIB)); | |
382 | |
383 if (id_dylib_command_) { | |
384 return id_dylib_command_->dylib_current_version; | |
385 } | |
386 | |
387 // In case this was a weird dylib without an LC_ID_DYLIB command. | |
388 return 0; | |
389 } | |
390 | |
391 void MachOImageReader::UUID(struct UUID* uuid) const { | |
392 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | |
393 memcpy(uuid, &uuid_, sizeof(uuid_)); | |
394 } | |
395 | |
396 template <typename T> | |
397 bool MachOImageReader::ReadLoadCommand(mach_vm_address_t load_command_address, | |
398 const std::string& load_command_info, | |
399 uint32_t expected_load_command_id, | |
400 T* load_command) { | |
401 if (!load_command->Read(process_reader_, load_command_address)) { | |
402 LOG(WARNING) << "could not read load command" << load_command_info; | |
403 return false; | |
404 } | |
405 | |
406 DCHECK_GE(load_command->cmdsize, load_command->Size()); | |
407 DCHECK_EQ(load_command->cmd, expected_load_command_id); | |
408 return true; | |
409 } | |
410 | |
411 bool MachOImageReader::ReadSegmentCommand( | |
412 mach_vm_address_t load_command_address, | |
413 const std::string& load_command_info) { | |
414 MachOImageSegmentReader* segment = new MachOImageSegmentReader(); | |
415 size_t segment_index = segments_.size(); | |
416 segments_.push_back(segment); | |
Robert Sesek
2014/09/04 13:18:41
"// Takes ownership."
| |
417 | |
418 if (!segment->Initialize( | |
419 process_reader_, load_command_address, load_command_info)) { | |
420 segments_.pop_back(); | |
421 return false; | |
422 } | |
423 | |
424 const std::string segment_name = segment->Name(); | |
425 const auto& iterator = segment_map_.find(segment_name); | |
426 if (iterator != segment_map_.end()) { | |
427 LOG(WARNING) << base::StringPrintf("duplicate %s segment at %zu and %zu", | |
428 segment_name.c_str(), | |
429 iterator->second, | |
430 segment_index) << load_command_info; | |
431 return false; | |
Robert Sesek
2014/09/04 13:18:41
But keep it in segments_?
| |
432 } | |
433 segment_map_[segment_name] = segment_index; | |
434 | |
435 mach_vm_size_t vmsize = segment->vmsize(); | |
436 | |
437 if (segment_name == SEG_TEXT) { | |
438 if (vmsize == 0) { | |
439 LOG(WARNING) << "zero-sized " SEG_TEXT " segment" << load_command_info; | |
440 return false; | |
441 } | |
442 | |
443 mach_vm_size_t fileoff = segment->fileoff(); | |
444 if (fileoff != 0) { | |
445 LOG(WARNING) << base::StringPrintf( | |
446 SEG_TEXT " segment has unexpected fileoff 0x%llx", | |
447 fileoff) << load_command_info; | |
448 return false; | |
449 } | |
450 | |
451 size_ = vmsize; | |
452 | |
453 // The slide is computed as the difference between the __TEXT segment’s | |
454 // preferred and actual load addresses. This is the same way that dyld | |
455 // computes slide. See 10.9.2 dyld-239.4/src/dyldInitialization.cpp | |
456 // slideOfMainExecutable(). | |
457 slide_ = address_ - segment->vmaddr(); | |
458 } | |
459 | |
460 return true; | |
461 } | |
462 | |
463 bool MachOImageReader::ReadSymTabCommand(mach_vm_address_t load_command_address, | |
464 const std::string& load_command_info) { | |
465 symtab_command_.reset(new process_types::symtab_command()); | |
466 return ReadLoadCommand(load_command_address, | |
467 load_command_info, | |
468 LC_SYMTAB, | |
469 symtab_command_.get()); | |
470 } | |
471 | |
472 bool MachOImageReader::ReadDySymTabCommand( | |
473 mach_vm_address_t load_command_address, | |
474 const std::string& load_command_info) { | |
475 dysymtab_command_.reset(new process_types::dysymtab_command()); | |
476 return ReadLoadCommand(load_command_address, | |
477 load_command_info, | |
478 LC_DYSYMTAB, | |
479 dysymtab_command_.get()); | |
480 } | |
481 | |
482 bool MachOImageReader::ReadIdDylibCommand( | |
483 mach_vm_address_t load_command_address, | |
484 const std::string& load_command_info) { | |
485 if (file_type_ != MH_DYLIB) { | |
486 LOG(WARNING) << base::StringPrintf( | |
487 "LC_ID_DYLIB inappropriate in non-dylib file type 0x%x", | |
488 file_type_) << load_command_info; | |
489 return false; | |
490 } | |
491 | |
492 DCHECK(!id_dylib_command_); | |
493 id_dylib_command_.reset(new process_types::dylib_command()); | |
494 return ReadLoadCommand(load_command_address, | |
495 load_command_info, | |
496 LC_ID_DYLIB, | |
497 id_dylib_command_.get()); | |
498 } | |
499 | |
500 bool MachOImageReader::ReadDylinkerCommand( | |
501 mach_vm_address_t load_command_address, | |
502 const std::string& load_command_info) { | |
503 if (file_type_ != MH_EXECUTE && file_type_ != MH_DYLINKER) { | |
504 LOG(WARNING) << base::StringPrintf( | |
505 "LC_LOAD_DYLINKER/LC_ID_DYLINKER inappropriate in file " | |
506 "type 0x%x", | |
507 file_type_) << load_command_info; | |
508 return false; | |
509 } | |
510 | |
511 const uint32_t kExpectedCommand = | |
512 file_type_ == MH_DYLINKER ? LC_ID_DYLINKER : LC_LOAD_DYLINKER; | |
513 process_types::dylinker_command dylinker_command; | |
514 if (!ReadLoadCommand(load_command_address, | |
515 load_command_info, | |
516 kExpectedCommand, | |
517 &dylinker_command)) { | |
518 return false; | |
519 } | |
520 | |
521 if (!process_reader_->Memory()->ReadCStringSizeLimited( | |
522 load_command_address + dylinker_command.name, | |
523 dylinker_command.cmdsize - dylinker_command.name, | |
524 &dylinker_name_)) { | |
525 LOG(WARNING) << "could not read dylinker_command name" << load_command_info; | |
526 return false; | |
527 } | |
528 | |
529 return true; | |
530 } | |
531 | |
532 bool MachOImageReader::ReadUUIDCommand(mach_vm_address_t load_command_address, | |
533 const std::string& load_command_info) { | |
534 process_types::uuid_command uuid_command; | |
535 if (!ReadLoadCommand( | |
536 load_command_address, load_command_info, LC_UUID, &uuid_command)) { | |
537 return false; | |
538 } | |
539 | |
540 uuid_.InitializeFromBytes(uuid_command.uuid); | |
541 return true; | |
542 } | |
543 | |
544 bool MachOImageReader::ReadSourceVersionCommand( | |
545 mach_vm_address_t load_command_address, | |
546 const std::string& load_command_info) { | |
547 process_types::source_version_command source_version_command; | |
548 if (!ReadLoadCommand(load_command_address, | |
549 load_command_info, | |
550 LC_SOURCE_VERSION, | |
551 &source_version_command)) { | |
552 return false; | |
553 } | |
554 | |
555 source_version_ = source_version_command.version; | |
556 return true; | |
557 } | |
558 | |
559 bool MachOImageReader::ReadUnexpectedCommand( | |
560 mach_vm_address_t load_command_address, | |
561 const std::string& load_command_info) { | |
562 LOG(WARNING) << "unexpected load command" << load_command_info; | |
563 return false; | |
564 } | |
565 | |
566 } // namespace crashpad | |
OLD | NEW |