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

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

Issue 666483002: Create snapshot/mac and move some files from snapshot and util to there (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad/+/master
Patch Set: Move process_reader, process_types, and mach_o_image*_reader from util/mac to snapshot/mac Created 6 years, 2 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
« no previous file with comments | « util/mac/mach_o_image_reader.h ('k') | util/mac/mach_o_image_reader_test.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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/mach_o_image_symbol_table_reader.h"
29 #include "util/mac/process_reader.h"
30
31 namespace {
32
33 const uint32_t kInvalidSegmentIndex = std::numeric_limits<uint32_t>::max();
34
35 } // namespace
36
37 namespace crashpad {
38
39 MachOImageReader::MachOImageReader()
40 : segments_(),
41 segment_map_(),
42 module_info_(),
43 dylinker_name_(),
44 uuid_(),
45 address_(0),
46 size_(0),
47 slide_(0),
48 source_version_(0),
49 symtab_command_(),
50 dysymtab_command_(),
51 symbol_table_(),
52 id_dylib_command_(),
53 process_reader_(nullptr),
54 file_type_(0),
55 initialized_(),
56 symbol_table_initialized_() {
57 }
58
59 MachOImageReader::~MachOImageReader() {
60 }
61
62 bool MachOImageReader::Initialize(ProcessReader* process_reader,
63 mach_vm_address_t address,
64 const std::string& name) {
65 INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
66
67 process_reader_ = process_reader;
68 address_ = address;
69
70 module_info_ =
71 base::StringPrintf(", module %s, address 0x%llx", name.c_str(), address);
72
73 process_types::mach_header mach_header;
74 if (!mach_header.Read(process_reader, address)) {
75 LOG(WARNING) << "could not read mach_header" << module_info_;
76 return false;
77 }
78
79 const bool is_64_bit = process_reader->Is64Bit();
80 const uint32_t kExpectedMagic = is_64_bit ? MH_MAGIC_64 : MH_MAGIC;
81 if (mach_header.magic != kExpectedMagic) {
82 LOG(WARNING) << base::StringPrintf("unexpected mach_header::magic 0x%08x",
83 mach_header.magic) << module_info_;
84 return false;
85 }
86
87 file_type_ = mach_header.filetype;
88
89 const uint32_t kExpectedSegmentCommand =
90 is_64_bit ? LC_SEGMENT_64 : LC_SEGMENT;
91 const uint32_t kUnexpectedSegmentCommand =
92 is_64_bit ? LC_SEGMENT : LC_SEGMENT_64;
93
94 const struct {
95 // Which method to call when encountering a load command matching |command|.
96 bool (MachOImageReader::*function)(mach_vm_address_t, const std::string&);
97
98 // The minimum size that may be allotted to store the load command.
99 size_t size;
100
101 // The load command to match.
102 uint32_t command;
103
104 // True if the load command must not appear more than one time.
105 bool singleton;
106 } kLoadCommandReaders[] = {
107 {
108 &MachOImageReader::ReadSegmentCommand,
109 process_types::segment_command::ExpectedSize(process_reader),
110 kExpectedSegmentCommand,
111 false,
112 },
113 {
114 &MachOImageReader::ReadSymTabCommand,
115 process_types::symtab_command::ExpectedSize(process_reader),
116 LC_SYMTAB,
117 true,
118 },
119 {
120 &MachOImageReader::ReadDySymTabCommand,
121 process_types::symtab_command::ExpectedSize(process_reader),
122 LC_DYSYMTAB,
123 true,
124 },
125 {
126 &MachOImageReader::ReadIdDylibCommand,
127 process_types::dylib_command::ExpectedSize(process_reader),
128 LC_ID_DYLIB,
129 true,
130 },
131 {
132 &MachOImageReader::ReadDylinkerCommand,
133 process_types::dylinker_command::ExpectedSize(process_reader),
134 LC_LOAD_DYLINKER,
135 true,
136 },
137 {
138 &MachOImageReader::ReadDylinkerCommand,
139 process_types::dylinker_command::ExpectedSize(process_reader),
140 LC_ID_DYLINKER,
141 true,
142 },
143 {
144 &MachOImageReader::ReadUUIDCommand,
145 process_types::uuid_command::ExpectedSize(process_reader),
146 LC_UUID,
147 true,
148 },
149 {
150 &MachOImageReader::ReadSourceVersionCommand,
151 process_types::source_version_command::ExpectedSize(process_reader),
152 LC_SOURCE_VERSION,
153 true,
154 },
155
156 // When reading a 64-bit process, no 32-bit segment commands should be
157 // present, and vice-versa.
158 {
159 &MachOImageReader::ReadUnexpectedCommand,
160 process_types::load_command::ExpectedSize(process_reader),
161 kUnexpectedSegmentCommand,
162 false,
163 },
164 };
165
166 // This vector is parallel to the kLoadCommandReaders array, and tracks
167 // whether a singleton load command matching the |command| field has been
168 // found yet.
169 std::vector<uint32_t> singleton_indices(arraysize(kLoadCommandReaders),
170 kInvalidSegmentIndex);
171
172 size_t offset = mach_header.Size();
173 const mach_vm_address_t kLoadCommandAddressLimit =
174 address + offset + mach_header.sizeofcmds;
175
176 for (uint32_t load_command_index = 0;
177 load_command_index < mach_header.ncmds;
178 ++load_command_index) {
179 mach_vm_address_t load_command_address = address + offset;
180 std::string load_command_info = base::StringPrintf(", load command %u/%u%s",
181 load_command_index,
182 mach_header.ncmds,
183 module_info_.c_str());
184
185 process_types::load_command load_command;
186
187 // Make sure that the basic load command structure doesn’t overflow the
188 // space allotted for load commands.
189 if (load_command_address + load_command.ExpectedSize(process_reader) >
190 kLoadCommandAddressLimit) {
191 LOG(WARNING) << base::StringPrintf(
192 "load_command at 0x%llx exceeds sizeofcmds 0x%x",
193 load_command_address,
194 mach_header.sizeofcmds) << load_command_info;
195 return false;
196 }
197
198 if (!load_command.Read(process_reader, load_command_address)) {
199 LOG(WARNING) << "could not read load_command" << load_command_info;
200 return false;
201 }
202
203 load_command_info = base::StringPrintf(", load command 0x%x %u/%u%s",
204 load_command.cmd,
205 load_command_index,
206 mach_header.ncmds,
207 module_info_.c_str());
208
209 // Now that the load command’s stated size is known, make sure that it
210 // doesn’t overflow the space allotted for load commands.
211 if (load_command_address + load_command.cmdsize >
212 kLoadCommandAddressLimit) {
213 LOG(WARNING)
214 << base::StringPrintf(
215 "load_command at 0x%llx cmdsize 0x%x exceeds sizeofcmds 0x%x",
216 load_command_address,
217 load_command.cmdsize,
218 mach_header.sizeofcmds) << load_command_info;
219 return false;
220 }
221
222 for (size_t reader_index = 0;
223 reader_index < arraysize(kLoadCommandReaders);
224 ++reader_index) {
225 if (load_command.cmd != kLoadCommandReaders[reader_index].command) {
226 continue;
227 }
228
229 if (load_command.cmdsize < kLoadCommandReaders[reader_index].size) {
230 LOG(WARNING) << base::StringPrintf(
231 "load command cmdsize 0x%x insufficient for 0x%zx",
232 load_command.cmdsize,
233 kLoadCommandReaders[reader_index].size)
234 << load_command_info;
235 return false;
236 }
237
238 if (kLoadCommandReaders[reader_index].singleton) {
239 if (singleton_indices[reader_index] != kInvalidSegmentIndex) {
240 LOG(WARNING) << "duplicate load command at "
241 << singleton_indices[reader_index] << load_command_info;
242 return false;
243 }
244
245 singleton_indices[reader_index] = load_command_index;
246 }
247
248 if (!((this)->*(kLoadCommandReaders[reader_index].function))(
249 load_command_address, load_command_info)) {
250 return false;
251 }
252
253 break;
254 }
255
256 offset += load_command.cmdsize;
257 }
258
259 // Now that the slide is known, push it into the segments.
260 for (MachOImageSegmentReader* segment : segments_) {
261 segment->SetSlide(slide_);
262
263 // This was already checked for the unslid values while the segments were
264 // read, but now it’s possible to check the slid values too. The individual
265 // sections don’t need to be checked because they were verified to be
266 // contained within their respective segments when the segments were read.
267 mach_vm_address_t slid_segment_address = segment->Address();
268 mach_vm_size_t slid_segment_size = segment->Size();
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) const {
300 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
301
302 const auto& iterator = segment_map_.find(segment_name);
303 if (iterator == segment_map_.end()) {
304 return nullptr;
305 }
306
307 const MachOImageSegmentReader* segment = segments_[iterator->second];
308 return segment;
309 }
310
311 const process_types::section* MachOImageReader::GetSectionByName(
312 const std::string& segment_name,
313 const std::string& section_name,
314 mach_vm_address_t* address) const {
315 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
316
317 const MachOImageSegmentReader* segment = GetSegmentByName(segment_name);
318 if (!segment) {
319 return nullptr;
320 }
321
322 return segment->GetSectionByName(section_name, address);
323 }
324
325 const process_types::section* MachOImageReader::GetSectionAtIndex(
326 size_t index,
327 const MachOImageSegmentReader** containing_segment,
328 mach_vm_address_t* address) const {
329 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
330
331 static_assert(NO_SECT == 0, "NO_SECT must be zero");
332 if (index == NO_SECT) {
333 LOG(WARNING) << "section index " << index << " out of range";
334 return nullptr;
335 }
336
337 // Switch to a more comfortable 0-based index.
338 size_t local_index = index - 1;
339
340 for (const MachOImageSegmentReader* segment : segments_) {
341 size_t nsects = segment->nsects();
342 if (local_index < nsects) {
343 const process_types::section* section =
344 segment->GetSectionAtIndex(local_index, address);
345
346 if (containing_segment) {
347 *containing_segment = segment;
348 }
349
350 return section;
351 }
352
353 local_index -= nsects;
354 }
355
356 LOG(WARNING) << "section index " << index << " out of range";
357 return nullptr;
358 }
359
360 bool MachOImageReader::LookUpExternalDefinedSymbol(
361 const std::string& name,
362 mach_vm_address_t* value) const {
363 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
364
365 if (symbol_table_initialized_.is_uninitialized()) {
366 InitializeSymbolTable();
367 }
368
369 if (!symbol_table_initialized_.is_valid() || !symbol_table_) {
370 return false;
371 }
372
373 const MachOImageSymbolTableReader::SymbolInformation* symbol_info =
374 symbol_table_->LookUpExternalDefinedSymbol(name);
375 if (!symbol_info) {
376 return false;
377 }
378
379 if (symbol_info->section == NO_SECT) {
380 // This is an absolute (N_ABS) symbol, which requires no further validation
381 // or processing.
382 *value = symbol_info->value;
383 return true;
384 }
385
386 // This is a symbol defined in a particular section, so make sure that it’s
387 // valid for that section and fix it up for any “slide” as needed.
388
389 mach_vm_address_t section_address;
390 const MachOImageSegmentReader* segment;
391 const process_types::section* section =
392 GetSectionAtIndex(symbol_info->section, &segment, &section_address);
393 if (!section) {
394 return false;
395 }
396
397 mach_vm_address_t slid_value =
398 symbol_info->value + (segment->SegmentSlides() ? slide_ : 0);
399
400 // The __mh_execute_header (_MH_EXECUTE_SYM) symbol is weird. In
401 // position-independent executables, it shows up in the symbol table as a
402 // symbol in section 1, although it’s not really in that section. It points to
403 // the mach_header[_64], which is the beginning of the __TEXT segment, and the
404 // __text section normally begins after the load commands in the __TEXT
405 // segment. The range check below will fail for this symbol, because it’s not
406 // really in the section it claims to be in. See Xcode 5.1
407 // ld64-236.3/src/ld/OutputFile.cpp ld::tool::OutputFile::buildSymbolTable().
408 // There, ld takes symbols that refer to anything in the mach_header[_64] and
409 // marks them as being in section 1. Here, section 1 is treated in this same
410 // special way as long as it’s in the __TEXT segment that begins at the start
411 // of the image, which is normally the case, and as long as the symbol’s value
412 // is the base of the image.
413 //
414 // This only happens for PIE executables, because __mh_execute_header needs
415 // to slide. In non-PIE executables, __mh_execute_header is an absolute
416 // symbol.
417 CheckedMachAddressRange section_range(
418 process_reader_, section_address, section->size);
419 if (!section_range.ContainsValue(slid_value) &&
420 !(symbol_info->section == 1 && segment->Name() == SEG_TEXT &&
421 slid_value == Address())) {
422 std::string section_name_full =
423 MachOImageSegmentReader::SegmentAndSectionNameString(section->segname,
424 section->sectname);
425 LOG(WARNING) << base::StringPrintf(
426 "symbol %s (0x%llx) outside of section %s (0x%llx + "
427 "0x%llx)",
428 name.c_str(),
429 slid_value,
430 section_name_full.c_str(),
431 section_address,
432 section->size) << module_info_;
433 return false;
434 }
435
436 *value = slid_value;
437 return true;
438 }
439
440 uint32_t MachOImageReader::DylibVersion() const {
441 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
442 DCHECK_EQ(FileType(), static_cast<uint32_t>(MH_DYLIB));
443
444 if (id_dylib_command_) {
445 return id_dylib_command_->dylib_current_version;
446 }
447
448 // In case this was a weird dylib without an LC_ID_DYLIB command.
449 return 0;
450 }
451
452 void MachOImageReader::UUID(crashpad::UUID* uuid) const {
453 INITIALIZATION_STATE_DCHECK_VALID(initialized_);
454 memcpy(uuid, &uuid_, sizeof(uuid_));
455 }
456
457 template <typename T>
458 bool MachOImageReader::ReadLoadCommand(mach_vm_address_t load_command_address,
459 const std::string& load_command_info,
460 uint32_t expected_load_command_id,
461 T* load_command) {
462 if (!load_command->Read(process_reader_, load_command_address)) {
463 LOG(WARNING) << "could not read load command" << load_command_info;
464 return false;
465 }
466
467 DCHECK_GE(load_command->cmdsize, load_command->Size());
468 DCHECK_EQ(load_command->cmd, expected_load_command_id);
469 return true;
470 }
471
472 bool MachOImageReader::ReadSegmentCommand(
473 mach_vm_address_t load_command_address,
474 const std::string& load_command_info) {
475 MachOImageSegmentReader* segment = new MachOImageSegmentReader();
476 size_t segment_index = segments_.size();
477 segments_.push_back(segment); // Takes ownership.
478
479 if (!segment->Initialize(
480 process_reader_, load_command_address, load_command_info)) {
481 segments_.pop_back();
482 return false;
483 }
484
485 // At this point, the segment itself is considered valid, but if one of the
486 // next checks fails, it will render the module invalid. If any of the next
487 // checks fail, this method should return false, but it doesn’t need to bother
488 // removing the segment from segments_. The segment will be properly released
489 // when the image is destroyed, and the image won’t be usable because
490 // initialization won’t have completed. Most importantly, leaving the segment
491 // in segments_ means that no other structures (such as perhaps segment_map_)
492 // become inconsistent or require cleanup.
493
494 const std::string segment_name = segment->Name();
495 const auto& iterator = segment_map_.find(segment_name);
496 if (iterator != segment_map_.end()) {
497 LOG(WARNING) << base::StringPrintf("duplicate %s segment at %zu and %zu",
498 segment_name.c_str(),
499 iterator->second,
500 segment_index) << load_command_info;
501 return false;
502 }
503 segment_map_[segment_name] = segment_index;
504
505 mach_vm_size_t vmsize = segment->vmsize();
506
507 if (segment_name == SEG_TEXT) {
508 if (vmsize == 0) {
509 LOG(WARNING) << "zero-sized " SEG_TEXT " segment" << load_command_info;
510 return false;
511 }
512
513 mach_vm_size_t fileoff = segment->fileoff();
514 if (fileoff != 0) {
515 LOG(WARNING) << base::StringPrintf(
516 SEG_TEXT " segment has unexpected fileoff 0x%llx",
517 fileoff) << load_command_info;
518 return false;
519 }
520
521 size_ = vmsize;
522
523 // The slide is computed as the difference between the __TEXT segment’s
524 // preferred and actual load addresses. This is the same way that dyld
525 // computes slide. See 10.9.2 dyld-239.4/src/dyldInitialization.cpp
526 // slideOfMainExecutable().
527 slide_ = address_ - segment->vmaddr();
528 }
529
530 return true;
531 }
532
533 bool MachOImageReader::ReadSymTabCommand(mach_vm_address_t load_command_address,
534 const std::string& load_command_info) {
535 symtab_command_.reset(new process_types::symtab_command());
536 return ReadLoadCommand(load_command_address,
537 load_command_info,
538 LC_SYMTAB,
539 symtab_command_.get());
540 }
541
542 bool MachOImageReader::ReadDySymTabCommand(
543 mach_vm_address_t load_command_address,
544 const std::string& load_command_info) {
545 dysymtab_command_.reset(new process_types::dysymtab_command());
546 return ReadLoadCommand(load_command_address,
547 load_command_info,
548 LC_DYSYMTAB,
549 dysymtab_command_.get());
550 }
551
552 bool MachOImageReader::ReadIdDylibCommand(
553 mach_vm_address_t load_command_address,
554 const std::string& load_command_info) {
555 if (file_type_ != MH_DYLIB) {
556 LOG(WARNING) << base::StringPrintf(
557 "LC_ID_DYLIB inappropriate in non-dylib file type 0x%x",
558 file_type_) << load_command_info;
559 return false;
560 }
561
562 DCHECK(!id_dylib_command_);
563 id_dylib_command_.reset(new process_types::dylib_command());
564 return ReadLoadCommand(load_command_address,
565 load_command_info,
566 LC_ID_DYLIB,
567 id_dylib_command_.get());
568 }
569
570 bool MachOImageReader::ReadDylinkerCommand(
571 mach_vm_address_t load_command_address,
572 const std::string& load_command_info) {
573 if (file_type_ != MH_EXECUTE && file_type_ != MH_DYLINKER) {
574 LOG(WARNING) << base::StringPrintf(
575 "LC_LOAD_DYLINKER/LC_ID_DYLINKER inappropriate in file "
576 "type 0x%x",
577 file_type_) << load_command_info;
578 return false;
579 }
580
581 const uint32_t kExpectedCommand =
582 file_type_ == MH_DYLINKER ? LC_ID_DYLINKER : LC_LOAD_DYLINKER;
583 process_types::dylinker_command dylinker_command;
584 if (!ReadLoadCommand(load_command_address,
585 load_command_info,
586 kExpectedCommand,
587 &dylinker_command)) {
588 return false;
589 }
590
591 if (!process_reader_->Memory()->ReadCStringSizeLimited(
592 load_command_address + dylinker_command.name,
593 dylinker_command.cmdsize - dylinker_command.name,
594 &dylinker_name_)) {
595 LOG(WARNING) << "could not read dylinker_command name" << load_command_info;
596 return false;
597 }
598
599 return true;
600 }
601
602 bool MachOImageReader::ReadUUIDCommand(mach_vm_address_t load_command_address,
603 const std::string& load_command_info) {
604 process_types::uuid_command uuid_command;
605 if (!ReadLoadCommand(
606 load_command_address, load_command_info, LC_UUID, &uuid_command)) {
607 return false;
608 }
609
610 uuid_.InitializeFromBytes(uuid_command.uuid);
611 return true;
612 }
613
614 bool MachOImageReader::ReadSourceVersionCommand(
615 mach_vm_address_t load_command_address,
616 const std::string& load_command_info) {
617 process_types::source_version_command source_version_command;
618 if (!ReadLoadCommand(load_command_address,
619 load_command_info,
620 LC_SOURCE_VERSION,
621 &source_version_command)) {
622 return false;
623 }
624
625 source_version_ = source_version_command.version;
626 return true;
627 }
628
629 bool MachOImageReader::ReadUnexpectedCommand(
630 mach_vm_address_t load_command_address,
631 const std::string& load_command_info) {
632 LOG(WARNING) << "unexpected load command" << load_command_info;
633 return false;
634 }
635
636 void MachOImageReader::InitializeSymbolTable() const {
637 DCHECK(symbol_table_initialized_.is_uninitialized());
638 symbol_table_initialized_.set_invalid();
639
640 if (!symtab_command_) {
641 // It’s technically valid for there to be no LC_SYMTAB, and in that case,
642 // any symbol lookups should fail. Mark the symbol table as valid, and
643 // LookUpExternalDefinedSymbol() will understand what it means when this is
644 // valid but symbol_table_ is not present.
645 symbol_table_initialized_.set_valid();
646 return;
647 }
648
649 // Find the __LINKEDIT segment. Technically, the symbol table can be in any
650 // mapped segment, but by convention, it’s in the one named __LINKEDIT.
651 const MachOImageSegmentReader* linkedit_segment =
652 GetSegmentByName(SEG_LINKEDIT);
653 if (!linkedit_segment) {
654 LOG(WARNING) << "no " SEG_LINKEDIT " segment";
655 return;
656 }
657
658 symbol_table_.reset(new MachOImageSymbolTableReader());
659 if (!symbol_table_->Initialize(process_reader_,
660 symtab_command_.get(),
661 dysymtab_command_.get(),
662 linkedit_segment,
663 module_info_)) {
664 symbol_table_.reset();
665 return;
666 }
667
668 symbol_table_initialized_.set_valid();
669 }
670
671 } // namespace crashpad
OLDNEW
« no previous file with comments | « util/mac/mach_o_image_reader.h ('k') | util/mac/mach_o_image_reader_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698