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

Side by Side Diff: src/common/mac/dump_syms.mm

Issue 1340543002: Fix Mac Breakpad host tools to build in Linux cross-compile (Closed) Base URL: https://chromium.googlesource.com/breakpad/breakpad.git@master
Patch Set: Move mac-headers to mac_headers Created 5 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 // -*- mode: c++ -*-
2
3 // Copyright (c) 2011, Google Inc.
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 // * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 // * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 // * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 // Author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
33
34 // dump_syms.mm: Create a symbol file for use with minidumps
35
36 #include "common/mac/dump_syms.h"
37
38 #include <assert.h>
39 #include <Foundation/Foundation.h>
40 #include <mach-o/arch.h>
41 #include <mach-o/fat.h>
42 #include <stdio.h>
43
44 #include <ostream>
45 #include <string>
46 #include <vector>
47
48 #include "common/dwarf/bytereader-inl.h"
49 #include "common/dwarf/dwarf2reader.h"
50 #include "common/dwarf_cfi_to_module.h"
51 #include "common/dwarf_cu_to_module.h"
52 #include "common/dwarf_line_to_module.h"
53 #include "common/mac/file_id.h"
54 #include "common/mac/arch_utilities.h"
55 #include "common/mac/macho_reader.h"
56 #include "common/module.h"
57 #include "common/scoped_ptr.h"
58 #include "common/stabs_reader.h"
59 #include "common/stabs_to_module.h"
60 #include "common/symbol_data.h"
61
62 #ifndef CPU_TYPE_ARM
63 #define CPU_TYPE_ARM (static_cast<cpu_type_t>(12))
64 #endif // CPU_TYPE_ARM
65
66 #ifndef CPU_TYPE_ARM64
67 #define CPU_TYPE_ARM64 (static_cast<cpu_type_t>(16777228))
68 #endif // CPU_TYPE_ARM64
69
70 using dwarf2reader::ByteReader;
71 using google_breakpad::DwarfCUToModule;
72 using google_breakpad::DwarfLineToModule;
73 using google_breakpad::FileID;
74 using google_breakpad::mach_o::FatReader;
75 using google_breakpad::mach_o::Section;
76 using google_breakpad::mach_o::Segment;
77 using google_breakpad::Module;
78 using google_breakpad::StabsReader;
79 using google_breakpad::StabsToModule;
80 using google_breakpad::scoped_ptr;
81 using std::make_pair;
82 using std::pair;
83 using std::string;
84 using std::vector;
85
86 namespace google_breakpad {
87
88 bool DumpSymbols::Read(NSString *filename) {
89 if (![[NSFileManager defaultManager] fileExistsAtPath:filename]) {
90 fprintf(stderr, "Object file does not exist: %s\n",
91 [filename fileSystemRepresentation]);
92 return false;
93 }
94
95 input_pathname_ = [filename retain];
96
97 // Does this filename refer to a dSYM bundle?
98 NSBundle *bundle = [NSBundle bundleWithPath:input_pathname_];
99
100 if (bundle) {
101 // Filenames referring to bundles usually have names of the form
102 // "<basename>.dSYM"; however, if the user has specified a wrapper
103 // suffix (the WRAPPER_SUFFIX and WRAPPER_EXTENSION build settings),
104 // then the name may have the form "<basename>.<extension>.dSYM". In
105 // either case, the resource name for the file containing the DWARF
106 // info within the bundle is <basename>.
107 //
108 // Since there's no way to tell how much to strip off, remove one
109 // extension at a time, and use the first one that
110 // pathForResource:ofType:inDirectory likes.
111 NSString *base_name = [input_pathname_ lastPathComponent];
112 NSString *dwarf_resource;
113
114 do {
115 NSString *new_base_name = [base_name stringByDeletingPathExtension];
116
117 // If stringByDeletingPathExtension returned the name unchanged, then
118 // there's nothing more for us to strip off --- lose.
119 if ([new_base_name isEqualToString:base_name]) {
120 fprintf(stderr, "Unable to find DWARF-bearing file in bundle: %s\n",
121 [input_pathname_ fileSystemRepresentation]);
122 return false;
123 }
124
125 // Take the shortened result as our new base_name.
126 base_name = new_base_name;
127
128 // Try to find a DWARF resource in the bundle under the new base_name.
129 dwarf_resource = [bundle pathForResource:base_name
130 ofType:nil inDirectory:@"DWARF"];
131 } while (!dwarf_resource);
132
133 object_filename_ = [dwarf_resource retain];
134 } else {
135 object_filename_ = [input_pathname_ retain];
136 }
137
138 // Read the file's contents into memory.
139 //
140 // The documentation for dataWithContentsOfMappedFile says:
141 //
142 // Because of file mapping restrictions, this method should only be
143 // used if the file is guaranteed to exist for the duration of the
144 // data object’s existence. It is generally safer to use the
145 // dataWithContentsOfFile: method.
146 //
147 // I gather this means that OS X doesn't have (or at least, that method
148 // doesn't use) a form of mapping like Linux's MAP_PRIVATE, where the
149 // process appears to get its own copy of the data, and changes to the
150 // file don't affect memory and vice versa).
151 NSError *error;
152 contents_ = [NSData dataWithContentsOfFile:object_filename_
153 options:0
154 error:&error];
155 if (!contents_) {
156 fprintf(stderr, "Error reading object file: %s: %s\n",
157 [object_filename_ fileSystemRepresentation],
158 [[error localizedDescription] UTF8String]);
159 return false;
160 }
161 [contents_ retain];
162
163 // Get the list of object files present in the file.
164 FatReader::Reporter fat_reporter([object_filename_
165 fileSystemRepresentation]);
166 FatReader fat_reader(&fat_reporter);
167 if (!fat_reader.Read(reinterpret_cast<const uint8_t *>([contents_ bytes]),
168 [contents_ length])) {
169 return false;
170 }
171
172 // Get our own copy of fat_reader's object file list.
173 size_t object_files_count;
174 const SuperFatArch *object_files =
175 fat_reader.object_files(&object_files_count);
176 if (object_files_count == 0) {
177 fprintf(stderr, "Fat binary file contains *no* architectures: %s\n",
178 [object_filename_ fileSystemRepresentation]);
179 return false;
180 }
181 object_files_.resize(object_files_count);
182 memcpy(&object_files_[0], object_files,
183 sizeof(SuperFatArch) * object_files_count);
184
185 return true;
186 }
187
188 bool DumpSymbols::SetArchitecture(cpu_type_t cpu_type,
189 cpu_subtype_t cpu_subtype) {
190 // Find the best match for the architecture the user requested.
191 const SuperFatArch *best_match = FindBestMatchForArchitecture(
192 cpu_type, cpu_subtype);
193 if (!best_match) return false;
194
195 // Record the selected object file.
196 selected_object_file_ = best_match;
197 return true;
198 }
199
200 bool DumpSymbols::SetArchitecture(const std::string &arch_name) {
201 bool arch_set = false;
202 const NXArchInfo *arch_info =
203 google_breakpad::BreakpadGetArchInfoFromName(arch_name.c_str());
204 if (arch_info) {
205 arch_set = SetArchitecture(arch_info->cputype, arch_info->cpusubtype);
206 }
207 return arch_set;
208 }
209
210 SuperFatArch* DumpSymbols::FindBestMatchForArchitecture(
211 cpu_type_t cpu_type, cpu_subtype_t cpu_subtype) {
212 // Check if all the object files can be converted to struct fat_arch.
213 bool can_convert_to_fat_arch = true;
214 vector<struct fat_arch> fat_arch_vector;
215 for (vector<SuperFatArch>::const_iterator it = object_files_.begin();
216 it != object_files_.end();
217 ++it) {
218 struct fat_arch arch;
219 bool success = it->ConvertToFatArch(&arch);
220 if (!success) {
221 can_convert_to_fat_arch = false;
222 break;
223 }
224 fat_arch_vector.push_back(arch);
225 }
226
227 // If all the object files can be converted to struct fat_arch, use
228 // NXFindBestFatArch.
229 if (can_convert_to_fat_arch) {
230 const struct fat_arch *best_match
231 = NXFindBestFatArch(cpu_type, cpu_subtype, &fat_arch_vector[0],
232 static_cast<uint32_t>(fat_arch_vector.size()));
233
234 for (size_t i = 0; i < fat_arch_vector.size(); ++i) {
235 if (best_match == &fat_arch_vector[i])
236 return &object_files_[i];
237 }
238 assert(best_match == NULL);
239 return NULL;
240 }
241
242 // Check for an exact match with cpu_type and cpu_subtype.
243 for (vector<SuperFatArch>::iterator it = object_files_.begin();
244 it != object_files_.end();
245 ++it) {
246 if (it->cputype == cpu_type && it->cpusubtype == cpu_subtype)
247 return &*it;
248 }
249
250 // No exact match found.
251 // TODO(erikchen): If it becomes necessary, we can copy the implementation of
252 // NXFindBestFatArch, located at
253 // http://web.mit.edu/darwin/src/modules/cctools/libmacho/arch.c.
254 fprintf(stderr, "Failed to find an exact match for an object file with cpu "
255 "type: %d and cpu subtype: %d. Furthermore, at least one object file is "
256 "larger than 2**32.\n", cpu_type, cpu_subtype);
257 return NULL;
258 }
259
260 string DumpSymbols::Identifier() {
261 FileID file_id([object_filename_ fileSystemRepresentation]);
262 unsigned char identifier_bytes[16];
263 cpu_type_t cpu_type = selected_object_file_->cputype;
264 cpu_subtype_t cpu_subtype = selected_object_file_->cpusubtype;
265 if (!file_id.MachoIdentifier(cpu_type, cpu_subtype, identifier_bytes)) {
266 fprintf(stderr, "Unable to calculate UUID of mach-o binary %s!\n",
267 [object_filename_ fileSystemRepresentation]);
268 return "";
269 }
270
271 char identifier_string[40];
272 FileID::ConvertIdentifierToString(identifier_bytes, identifier_string,
273 sizeof(identifier_string));
274
275 string compacted(identifier_string);
276 for(size_t i = compacted.find('-'); i != string::npos;
277 i = compacted.find('-', i))
278 compacted.erase(i, 1);
279
280 return compacted;
281 }
282
283 // A line-to-module loader that accepts line number info parsed by
284 // dwarf2reader::LineInfo and populates a Module and a line vector
285 // with the results.
286 class DumpSymbols::DumperLineToModule:
287 public DwarfCUToModule::LineToModuleHandler {
288 public:
289 // Create a line-to-module converter using BYTE_READER.
290 DumperLineToModule(dwarf2reader::ByteReader *byte_reader)
291 : byte_reader_(byte_reader) { }
292
293 void StartCompilationUnit(const string& compilation_dir) {
294 compilation_dir_ = compilation_dir;
295 }
296
297 void ReadProgram(const char *program, uint64 length,
298 Module *module, vector<Module::Line> *lines) {
299 DwarfLineToModule handler(module, compilation_dir_, lines);
300 dwarf2reader::LineInfo parser(program, length, byte_reader_, &handler);
301 parser.Start();
302 }
303 private:
304 string compilation_dir_;
305 dwarf2reader::ByteReader *byte_reader_; // WEAK
306 };
307
308 bool DumpSymbols::ReadDwarf(google_breakpad::Module *module,
309 const mach_o::Reader &macho_reader,
310 const mach_o::SectionMap &dwarf_sections,
311 bool handle_inter_cu_refs) const {
312 // Build a byte reader of the appropriate endianness.
313 ByteReader byte_reader(macho_reader.big_endian()
314 ? dwarf2reader::ENDIANNESS_BIG
315 : dwarf2reader::ENDIANNESS_LITTLE);
316
317 // Construct a context for this file.
318 DwarfCUToModule::FileContext file_context(selected_object_name_,
319 module,
320 handle_inter_cu_refs);
321
322 // Build a dwarf2reader::SectionMap from our mach_o::SectionMap.
323 for (mach_o::SectionMap::const_iterator it = dwarf_sections.begin();
324 it != dwarf_sections.end(); ++it) {
325 file_context.AddSectionToSectionMap(
326 it->first,
327 reinterpret_cast<const char *>(it->second.contents.start),
328 it->second.contents.Size());
329 }
330
331 // Find the __debug_info section.
332 dwarf2reader::SectionMap::const_iterator debug_info_entry =
333 file_context.section_map().find("__debug_info");
334 assert(debug_info_entry != file_context.section_map().end());
335 const std::pair<const char*, uint64>& debug_info_section =
336 debug_info_entry->second;
337 // There had better be a __debug_info section!
338 if (!debug_info_section.first) {
339 fprintf(stderr, "%s: __DWARF segment of file has no __debug_info section\n",
340 selected_object_name_.c_str());
341 return false;
342 }
343
344 // Build a line-to-module loader for the root handler to use.
345 DumperLineToModule line_to_module(&byte_reader);
346
347 // Walk the __debug_info section, one compilation unit at a time.
348 uint64 debug_info_length = debug_info_section.second;
349 for (uint64 offset = 0; offset < debug_info_length;) {
350 // Make a handler for the root DIE that populates MODULE with the
351 // debug info.
352 DwarfCUToModule::WarningReporter reporter(selected_object_name_,
353 offset);
354 DwarfCUToModule root_handler(&file_context, &line_to_module, &reporter);
355 // Make a Dwarf2Handler that drives our DIEHandler.
356 dwarf2reader::DIEDispatcher die_dispatcher(&root_handler);
357 // Make a DWARF parser for the compilation unit at OFFSET.
358 dwarf2reader::CompilationUnit dwarf_reader(file_context.section_map(),
359 offset,
360 &byte_reader,
361 &die_dispatcher);
362 // Process the entire compilation unit; get the offset of the next.
363 offset += dwarf_reader.Start();
364 }
365
366 return true;
367 }
368
369 bool DumpSymbols::ReadCFI(google_breakpad::Module *module,
370 const mach_o::Reader &macho_reader,
371 const mach_o::Section &section,
372 bool eh_frame) const {
373 // Find the appropriate set of register names for this file's
374 // architecture.
375 vector<string> register_names;
376 switch (macho_reader.cpu_type()) {
377 case CPU_TYPE_X86:
378 register_names = DwarfCFIToModule::RegisterNames::I386();
379 break;
380 case CPU_TYPE_X86_64:
381 register_names = DwarfCFIToModule::RegisterNames::X86_64();
382 break;
383 case CPU_TYPE_ARM:
384 register_names = DwarfCFIToModule::RegisterNames::ARM();
385 break;
386 case CPU_TYPE_ARM64:
387 register_names = DwarfCFIToModule::RegisterNames::ARM64();
388 break;
389 default: {
390 const NXArchInfo *arch = google_breakpad::BreakpadGetArchInfoFromCpuType(
391 macho_reader.cpu_type(), macho_reader.cpu_subtype());
392 fprintf(stderr, "%s: cannot convert DWARF call frame information for ",
393 selected_object_name_.c_str());
394 if (arch)
395 fprintf(stderr, "architecture '%s'", arch->name);
396 else
397 fprintf(stderr, "architecture %d,%d",
398 macho_reader.cpu_type(), macho_reader.cpu_subtype());
399 fprintf(stderr, " to Breakpad symbol file: no register name table\n");
400 return false;
401 }
402 }
403
404 // Find the call frame information and its size.
405 const char *cfi = reinterpret_cast<const char *>(section.contents.start);
406 size_t cfi_size = section.contents.Size();
407
408 // Plug together the parser, handler, and their entourages.
409 DwarfCFIToModule::Reporter module_reporter(selected_object_name_,
410 section.section_name);
411 DwarfCFIToModule handler(module, register_names, &module_reporter);
412 dwarf2reader::ByteReader byte_reader(macho_reader.big_endian() ?
413 dwarf2reader::ENDIANNESS_BIG :
414 dwarf2reader::ENDIANNESS_LITTLE);
415 byte_reader.SetAddressSize(macho_reader.bits_64() ? 8 : 4);
416 // At the moment, according to folks at Apple and some cursory
417 // investigation, Mac OS X only uses DW_EH_PE_pcrel-based pointers, so
418 // this is the only base address the CFI parser will need.
419 byte_reader.SetCFIDataBase(section.address, cfi);
420
421 dwarf2reader::CallFrameInfo::Reporter dwarf_reporter(selected_object_name_,
422 section.section_name);
423 dwarf2reader::CallFrameInfo parser(cfi, cfi_size,
424 &byte_reader, &handler, &dwarf_reporter,
425 eh_frame);
426 parser.Start();
427 return true;
428 }
429
430 // A LoadCommandHandler that loads whatever debugging data it finds into a
431 // Module.
432 class DumpSymbols::LoadCommandDumper:
433 public mach_o::Reader::LoadCommandHandler {
434 public:
435 // Create a load command dumper handling load commands from READER's
436 // file, and adding data to MODULE.
437 LoadCommandDumper(const DumpSymbols &dumper,
438 google_breakpad::Module *module,
439 const mach_o::Reader &reader,
440 SymbolData symbol_data,
441 bool handle_inter_cu_refs)
442 : dumper_(dumper),
443 module_(module),
444 reader_(reader),
445 symbol_data_(symbol_data),
446 handle_inter_cu_refs_(handle_inter_cu_refs) { }
447
448 bool SegmentCommand(const mach_o::Segment &segment);
449 bool SymtabCommand(const ByteBuffer &entries, const ByteBuffer &strings);
450
451 private:
452 const DumpSymbols &dumper_;
453 google_breakpad::Module *module_; // WEAK
454 const mach_o::Reader &reader_;
455 const SymbolData symbol_data_;
456 const bool handle_inter_cu_refs_;
457 };
458
459 bool DumpSymbols::LoadCommandDumper::SegmentCommand(const Segment &segment) {
460 mach_o::SectionMap section_map;
461 if (!reader_.MapSegmentSections(segment, &section_map))
462 return false;
463
464 if (segment.name == "__TEXT") {
465 module_->SetLoadAddress(segment.vmaddr);
466 if (symbol_data_ != NO_CFI) {
467 mach_o::SectionMap::const_iterator eh_frame =
468 section_map.find("__eh_frame");
469 if (eh_frame != section_map.end()) {
470 // If there is a problem reading this, don't treat it as a fatal error.
471 dumper_.ReadCFI(module_, reader_, eh_frame->second, true);
472 }
473 }
474 return true;
475 }
476
477 if (segment.name == "__DWARF") {
478 if (symbol_data_ != ONLY_CFI) {
479 if (!dumper_.ReadDwarf(module_, reader_, section_map,
480 handle_inter_cu_refs_)) {
481 return false;
482 }
483 }
484 if (symbol_data_ != NO_CFI) {
485 mach_o::SectionMap::const_iterator debug_frame
486 = section_map.find("__debug_frame");
487 if (debug_frame != section_map.end()) {
488 // If there is a problem reading this, don't treat it as a fatal error.
489 dumper_.ReadCFI(module_, reader_, debug_frame->second, false);
490 }
491 }
492 }
493
494 return true;
495 }
496
497 bool DumpSymbols::LoadCommandDumper::SymtabCommand(const ByteBuffer &entries,
498 const ByteBuffer &strings) {
499 StabsToModule stabs_to_module(module_);
500 // Mac OS X STABS are never "unitized", and the size of the 'value' field
501 // matches the address size of the executable.
502 StabsReader stabs_reader(entries.start, entries.Size(),
503 strings.start, strings.Size(),
504 reader_.big_endian(),
505 reader_.bits_64() ? 8 : 4,
506 true,
507 &stabs_to_module);
508 if (!stabs_reader.Process())
509 return false;
510 stabs_to_module.Finalize();
511 return true;
512 }
513
514 bool DumpSymbols::ReadSymbolData(Module** out_module) {
515 // Select an object file, if SetArchitecture hasn't been called to set one
516 // explicitly.
517 if (!selected_object_file_) {
518 // If there's only one architecture, that's the one.
519 if (object_files_.size() == 1)
520 selected_object_file_ = &object_files_[0];
521 else {
522 // Look for an object file whose architecture matches our own.
523 const NXArchInfo *local_arch = NXGetLocalArchInfo();
524 if (!SetArchitecture(local_arch->cputype, local_arch->cpusubtype)) {
525 fprintf(stderr, "%s: object file contains more than one"
526 " architecture, none of which match the current"
527 " architecture; specify an architecture explicitly"
528 " with '-a ARCH' to resolve the ambiguity\n",
529 [object_filename_ fileSystemRepresentation]);
530 return false;
531 }
532 }
533 }
534
535 assert(selected_object_file_);
536
537 // Find the name of the selected file's architecture, to appear in
538 // the MODULE record and in error messages.
539 const NXArchInfo *selected_arch_info =
540 google_breakpad::BreakpadGetArchInfoFromCpuType(
541 selected_object_file_->cputype, selected_object_file_->cpusubtype);
542
543 const char *selected_arch_name = selected_arch_info->name;
544 if (strcmp(selected_arch_name, "i386") == 0)
545 selected_arch_name = "x86";
546
547 // Produce a name to use in error messages that includes the
548 // filename, and the architecture, if there is more than one.
549 selected_object_name_ = [object_filename_ UTF8String];
550 if (object_files_.size() > 1) {
551 selected_object_name_ += ", architecture ";
552 selected_object_name_ + selected_arch_name;
553 }
554
555 // Compute a module name, to appear in the MODULE record.
556 NSString *module_name = [object_filename_ lastPathComponent];
557
558 // Choose an identifier string, to appear in the MODULE record.
559 string identifier = Identifier();
560 if (identifier.empty())
561 return false;
562 identifier += "0";
563
564 // Create a module to hold the debugging information.
565 scoped_ptr<Module> module(new Module([module_name UTF8String],
566 "mac",
567 selected_arch_name,
568 identifier));
569
570 // Parse the selected object file.
571 mach_o::Reader::Reporter reporter(selected_object_name_);
572 mach_o::Reader reader(&reporter);
573 if (!reader.Read(reinterpret_cast<const uint8_t *>([contents_ bytes])
574 + selected_object_file_->offset,
575 selected_object_file_->size,
576 selected_object_file_->cputype,
577 selected_object_file_->cpusubtype))
578 return false;
579
580 // Walk its load commands, and deal with whatever is there.
581 LoadCommandDumper load_command_dumper(*this, module.get(), reader,
582 symbol_data_, handle_inter_cu_refs_);
583 if (!reader.WalkLoadCommands(&load_command_dumper))
584 return false;
585
586 *out_module = module.release();
587
588 return true;
589 }
590
591 bool DumpSymbols::WriteSymbolFile(std::ostream &stream) {
592 Module* module = NULL;
593
594 if (ReadSymbolData(&module) && module) {
595 bool res = module->Write(stream, symbol_data_);
596 delete module;
597 return res;
598 }
599
600 return false;
601 }
602
603 } // namespace google_breakpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698