Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(494)

Side by Side Diff: util/mac/mach_o_image_reader.cc

Issue 535343004: Add MachOImageReader and its test (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Minor comment updates Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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) {
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) {
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) {
223 continue;
224 }
225
226 if (load_command.cmdsize < kLoadCommandReaders[reader_index].size) {
227 LOG(WARNING)
228 << base::StringPrintf(
229 "load command cmdsize 0x%x insufficient for 0x%zx",
230 load_command.cmdsize,
231 kLoadCommandReaders[reader_index].size)
232 << load_command_info;
233 return false;
234 }
235
236 if (kLoadCommandReaders[reader_index].singleton) {
237 if (singleton_indices[reader_index] != kInvalidSegmentIndex) {
238 LOG(WARNING) << "duplicate load command at "
239 << singleton_indices[reader_index]
240 << load_command_info;
241 return false;
242 }
243
244 singleton_indices[reader_index] = load_command_index;
245 }
246
247 if (!((this)->*(kLoadCommandReaders[reader_index].function))(
248 load_command_address, load_command_info)) {
249 return false;
250 }
251
252 break;
253 }
254
255 offset += load_command.cmdsize;
256 }
257
258 // This was already checked for the unslid values while the segments were
259 // read, but now that the slide is known, check the slid values too. The
260 // individual sections don’t need to be checked because they were verified to
261 // be contained within their respective segments when the segments were read.
262 for (const MachOImageSegmentReader* segment : segments_) {
263 mach_vm_address_t slid_segment_address = segment->vmaddr();
264 mach_vm_size_t slid_segment_size = segment->vmsize();
265 if (segment->SegmentSlides()) {
266 slid_segment_address += slide_;
267 } else {
268 // The non-sliding __PAGEZERO segment extends instead of slides. See
269 // MachOImageSegmentReader::SegmentSlides().
270 slid_segment_size += slide_;
271 }
272 CheckedMachAddressRange slid_segment_range(
273 process_reader_, slid_segment_address, slid_segment_size);
274 if (!slid_segment_range.IsValid()) {
275 LOG(WARNING) << base::StringPrintf(
276 "invalid slid segment range 0x%llx + 0x%llx, "
277 "segment ",
278 slid_segment_address,
279 slid_segment_size) << segment->Name() << module_info_;
280 return false;
281 }
282 }
283
284 if (!segment_map_.count(SEG_TEXT)) {
285 // The __TEXT segment is required. Even a module with no executable code
286 // will have a __TEXT segment encompassing the Mach-O header and load
287 // commands. Without a __TEXT segment, |size_| will not have been computed.
288 LOG(WARNING) << "no " SEG_TEXT " segment" << module_info_;
289 return false;
290 }
291
292 if (mach_header.filetype == MH_DYLIB && !id_dylib_command_) {
293 // This doesn’t render a module unusable, it’s just weird and worth noting.
294 LOG(INFO) << "no LC_ID_DYLIB" << module_info_;
295 }
296
297 INITIALIZATION_STATE_SET_VALID(initialized_);
298 return true;
299 }
300
301 const MachOImageSegmentReader* MachOImageReader::GetSegmentByName(
302 const std::string& segment_name,
303 mach_vm_address_t* address,
304 mach_vm_size_t* size) const {
305 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
306
307 const auto& iterator = segment_map_.find(segment_name);
308 if (iterator == segment_map_.end()) {
309 return NULL;
310 }
311
312 const MachOImageSegmentReader* segment = segments_[iterator->second];
313 if (address) {
314 *address = segment->vmaddr() + (segment->SegmentSlides() ? slide_ : 0);
315 }
316 if (size) {
317 // The non-sliding __PAGEZERO segment extends instead of slides. See
318 // MachOImageSegmentReader::SegmentSlides().
319 *size = segment->vmsize() + (segment->SegmentSlides() ? 0 : slide_);
320 }
321
322 return segment;
323 }
324
325 const process_types::section* MachOImageReader::GetSectionByName(
326 const std::string& segment_name,
327 const std::string& section_name,
328 mach_vm_address_t* address) const {
329 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
330
331 const MachOImageSegmentReader* segment =
332 GetSegmentByName(segment_name, NULL, NULL);
333 if (!segment) {
334 return NULL;
335 }
336
337 const process_types::section* section =
338 segment->GetSectionByName(section_name);
339 if (!section) {
340 return NULL;
341 }
342
343 if (address) {
344 *address = section->addr + (segment->SegmentSlides() ? slide_ : 0);
345 }
346
347 return section;
348 }
349
350 const process_types::section* MachOImageReader::GetSectionAtIndex(
351 size_t index,
352 mach_vm_address_t* address) const {
353 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
354
355 COMPILE_ASSERT(NO_SECT == 0, no_sect_must_be_zero);
356 if (index == NO_SECT) {
357 LOG(WARNING) << "section index " << index << " out of range";
358 return NULL;
359 }
360
361 // Switch to a more comfortable 0-based index.
362 size_t local_index = index - 1;
363
364 for (const MachOImageSegmentReader* segment : segments_) {
365 size_t nsects = segment->nsects();
366 if (local_index < nsects) {
367 const process_types::section* section =
368 segment->GetSectionAtIndex(local_index);
369
370 if (address) {
371 *address = section->addr + (segment->SegmentSlides() ? slide_ : 0);
372 }
373
374 return section;
375 }
376
377 local_index -= nsects;
378 }
379
380 LOG(WARNING) << "section index " << index << " out of range";
381 return NULL;
382 }
383
384 uint32_t MachOImageReader::DylibVersion() const {
385 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
386 DCHECK_EQ(FileType(), static_cast<uint32_t>(MH_DYLIB));
387
388 if (id_dylib_command_) {
389 return id_dylib_command_->dylib_current_version;
390 }
391
392 // In case this was a weird dylib without an LC_ID_DYLIB command.
393 return 0;
394 }
395
396 void MachOImageReader::UUID(crashpad::UUID* uuid) const {
397 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
398 memcpy(uuid, &uuid_, sizeof(uuid_));
399 }
400
401 template <typename T>
402 bool MachOImageReader::ReadLoadCommand(mach_vm_address_t load_command_address,
403 const std::string& load_command_info,
404 uint32_t expected_load_command_id,
405 T* load_command) {
406 if (!load_command->Read(process_reader_, load_command_address)) {
407 LOG(WARNING) << "could not read load command" << load_command_info;
408 return false;
409 }
410
411 DCHECK_GE(load_command->cmdsize, load_command->Size());
412 DCHECK_EQ(load_command->cmd, expected_load_command_id);
413 return true;
414 }
415
416 bool MachOImageReader::ReadSegmentCommand(
417 mach_vm_address_t load_command_address,
418 const std::string& load_command_info) {
419 MachOImageSegmentReader* segment = new MachOImageSegmentReader();
420 size_t segment_index = segments_.size();
421 segments_.push_back(segment); // Takes ownership.
422
423 if (!segment->Initialize(
424 process_reader_, load_command_address, load_command_info)) {
425 segments_.pop_back();
426 return false;
427 }
428
429 // At this point, the segment itself is considered valid, but if one of the
430 // next checks fails, it will render the module invalid. If any of the next
431 // checks fail, this method should return false, but it doesn’t need to bother
432 // removing the segment from segments_. The segment will be properly released
433 // when the image is destroyed, and the image won’t be usable because
434 // initialization won’t have completed. Most importantly, leaving the segment
435 // in segments_ means that no other structures (such as perhaps segment_map_)
436 // become inconsistent or require cleanup.
437
438 const std::string segment_name = segment->Name();
439 const auto& iterator = segment_map_.find(segment_name);
440 if (iterator != segment_map_.end()) {
441 LOG(WARNING) << base::StringPrintf("duplicate %s segment at %zu and %zu",
442 segment_name.c_str(),
443 iterator->second,
444 segment_index) << load_command_info;
445 return false;
446 }
447 segment_map_[segment_name] = segment_index;
448
449 mach_vm_size_t vmsize = segment->vmsize();
450
451 if (segment_name == SEG_TEXT) {
452 if (vmsize == 0) {
453 LOG(WARNING) << "zero-sized " SEG_TEXT " segment" << load_command_info;
454 return false;
455 }
456
457 mach_vm_size_t fileoff = segment->fileoff();
458 if (fileoff != 0) {
459 LOG(WARNING) << base::StringPrintf(
460 SEG_TEXT " segment has unexpected fileoff 0x%llx",
461 fileoff) << load_command_info;
462 return false;
463 }
464
465 size_ = vmsize;
466
467 // The slide is computed as the difference between the __TEXT segment’s
468 // preferred and actual load addresses. This is the same way that dyld
469 // computes slide. See 10.9.2 dyld-239.4/src/dyldInitialization.cpp
470 // slideOfMainExecutable().
471 slide_ = address_ - segment->vmaddr();
472 }
473
474 return true;
475 }
476
477 bool MachOImageReader::ReadSymTabCommand(mach_vm_address_t load_command_address,
478 const std::string& load_command_info) {
479 symtab_command_.reset(new process_types::symtab_command());
480 return ReadLoadCommand(load_command_address,
481 load_command_info,
482 LC_SYMTAB,
483 symtab_command_.get());
484 }
485
486 bool MachOImageReader::ReadDySymTabCommand(
487 mach_vm_address_t load_command_address,
488 const std::string& load_command_info) {
489 dysymtab_command_.reset(new process_types::dysymtab_command());
490 return ReadLoadCommand(load_command_address,
491 load_command_info,
492 LC_DYSYMTAB,
493 dysymtab_command_.get());
494 }
495
496 bool MachOImageReader::ReadIdDylibCommand(
497 mach_vm_address_t load_command_address,
498 const std::string& load_command_info) {
499 if (file_type_ != MH_DYLIB) {
500 LOG(WARNING) << base::StringPrintf(
501 "LC_ID_DYLIB inappropriate in non-dylib file type 0x%x",
502 file_type_) << load_command_info;
503 return false;
504 }
505
506 DCHECK(!id_dylib_command_);
507 id_dylib_command_.reset(new process_types::dylib_command());
508 return ReadLoadCommand(load_command_address,
509 load_command_info,
510 LC_ID_DYLIB,
511 id_dylib_command_.get());
512 }
513
514 bool MachOImageReader::ReadDylinkerCommand(
515 mach_vm_address_t load_command_address,
516 const std::string& load_command_info) {
517 if (file_type_ != MH_EXECUTE && file_type_ != MH_DYLINKER) {
518 LOG(WARNING) << base::StringPrintf(
519 "LC_LOAD_DYLINKER/LC_ID_DYLINKER inappropriate in file "
520 "type 0x%x",
521 file_type_) << load_command_info;
522 return false;
523 }
524
525 const uint32_t kExpectedCommand =
526 file_type_ == MH_DYLINKER ? LC_ID_DYLINKER : LC_LOAD_DYLINKER;
527 process_types::dylinker_command dylinker_command;
528 if (!ReadLoadCommand(load_command_address,
529 load_command_info,
530 kExpectedCommand,
531 &dylinker_command)) {
532 return false;
533 }
534
535 if (!process_reader_->Memory()->ReadCStringSizeLimited(
536 load_command_address + dylinker_command.name,
537 dylinker_command.cmdsize - dylinker_command.name,
538 &dylinker_name_)) {
539 LOG(WARNING) << "could not read dylinker_command name" << load_command_info;
540 return false;
541 }
542
543 return true;
544 }
545
546 bool MachOImageReader::ReadUUIDCommand(mach_vm_address_t load_command_address,
547 const std::string& load_command_info) {
548 process_types::uuid_command uuid_command;
549 if (!ReadLoadCommand(
550 load_command_address, load_command_info, LC_UUID, &uuid_command)) {
551 return false;
552 }
553
554 uuid_.InitializeFromBytes(uuid_command.uuid);
555 return true;
556 }
557
558 bool MachOImageReader::ReadSourceVersionCommand(
559 mach_vm_address_t load_command_address,
560 const std::string& load_command_info) {
561 process_types::source_version_command source_version_command;
562 if (!ReadLoadCommand(load_command_address,
563 load_command_info,
564 LC_SOURCE_VERSION,
565 &source_version_command)) {
566 return false;
567 }
568
569 source_version_ = source_version_command.version;
570 return true;
571 }
572
573 bool MachOImageReader::ReadUnexpectedCommand(
574 mach_vm_address_t load_command_address,
575 const std::string& load_command_info) {
576 LOG(WARNING) << "unexpected load command" << load_command_info;
577 return false;
578 }
579
580 } // namespace crashpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698