OLD | NEW |
---|---|
1 // Copyright (c) 2013 Google Inc. | 1 // Copyright (c) 2013 Google Inc. |
2 // All rights reserved. | 2 // All rights reserved. |
3 // | 3 // |
4 // Redistribution and use in source and binary forms, with or without | 4 // Redistribution and use in source and binary forms, with or without |
5 // modification, are permitted provided that the following conditions are | 5 // modification, are permitted provided that the following conditions are |
6 // met: | 6 // met: |
7 // | 7 // |
8 // * Redistributions of source code must retain the above copyright | 8 // * Redistributions of source code must retain the above copyright |
9 // notice, this list of conditions and the following disclaimer. | 9 // notice, this list of conditions and the following disclaimer. |
10 // * Redistributions in binary form must reproduce the above | 10 // * Redistributions in binary form must reproduce the above |
(...skipping 18 matching lines...) Expand all Loading... | |
29 | 29 |
30 // exploitability_linux.cc: Linux specific exploitability engine. | 30 // exploitability_linux.cc: Linux specific exploitability engine. |
31 // | 31 // |
32 // Provides a guess at the exploitability of the crash for the Linux | 32 // Provides a guess at the exploitability of the crash for the Linux |
33 // platform given a minidump and process_state. | 33 // platform given a minidump and process_state. |
34 // | 34 // |
35 // Author: Matthew Riley | 35 // Author: Matthew Riley |
36 | 36 |
37 #include "processor/exploitability_linux.h" | 37 #include "processor/exploitability_linux.h" |
38 | 38 |
39 #ifndef _WIN32 | |
40 #include <regex.h> | |
41 #include <stdio.h> | |
42 #include <stdlib.h> | |
43 #include <string.h> | |
44 | |
45 #include <sstream> | |
46 #include <iterator> | |
47 #endif // _WIN32 | |
48 | |
39 #include "google_breakpad/common/minidump_exception_linux.h" | 49 #include "google_breakpad/common/minidump_exception_linux.h" |
40 #include "google_breakpad/processor/call_stack.h" | 50 #include "google_breakpad/processor/call_stack.h" |
41 #include "google_breakpad/processor/process_state.h" | 51 #include "google_breakpad/processor/process_state.h" |
42 #include "google_breakpad/processor/stack_frame.h" | 52 #include "google_breakpad/processor/stack_frame.h" |
43 #include "processor/logging.h" | 53 #include "processor/logging.h" |
44 | 54 |
45 namespace { | 55 namespace { |
46 | 56 |
47 // This function in libc is called if the program was compiled with | 57 // This function in libc is called if the program was compiled with |
48 // -fstack-protector and a function's stack canary changes. | 58 // -fstack-protector and a function's stack canary changes. |
49 const char kStackCheckFailureFunction[] = "__stack_chk_fail"; | 59 const char kStackCheckFailureFunction[] = "__stack_chk_fail"; |
50 | 60 |
51 // This function in libc is called if the program was compiled with | 61 // This function in libc is called if the program was compiled with |
52 // -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime | 62 // -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime |
53 // can determine that the call would overflow the target buffer. | 63 // can determine that the call would overflow the target buffer. |
54 const char kBoundsCheckFailureFunction[] = "__chk_fail"; | 64 const char kBoundsCheckFailureFunction[] = "__chk_fail"; |
55 | 65 |
66 #ifndef _WIN32 | |
67 const unsigned int MAX_INSTRUCTION_LEN = 15; | |
68 const unsigned int MAX_OBJDUMP_BUFFER_LEN = 4096; | |
69 #endif // _WIN32 | |
70 | |
56 } // namespace | 71 } // namespace |
57 | 72 |
58 namespace google_breakpad { | 73 namespace google_breakpad { |
59 | 74 |
60 ExploitabilityLinux::ExploitabilityLinux(Minidump *dump, | 75 ExploitabilityLinux::ExploitabilityLinux(Minidump *dump, |
61 ProcessState *process_state) | 76 ProcessState *process_state) |
62 : Exploitability(dump, process_state) { } | 77 : Exploitability(dump, process_state), |
78 enable_objdump_(false) { } | |
79 | |
80 ExploitabilityLinux::ExploitabilityLinux(Minidump *dump, | |
81 ProcessState *process_state, | |
82 bool enable_objdump) | |
83 : Exploitability(dump, process_state), | |
84 enable_objdump_(enable_objdump) { } | |
85 | |
63 | 86 |
64 ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() { | 87 ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() { |
65 // Check the crashing thread for functions suggesting a buffer overflow or | 88 // Check the crashing thread for functions suggesting a buffer overflow or |
66 // stack smash. | 89 // stack smash. |
67 if (process_state_->requesting_thread() != -1) { | 90 if (process_state_->requesting_thread() != -1) { |
68 CallStack* crashing_thread = | 91 CallStack* crashing_thread = |
69 process_state_->threads()->at(process_state_->requesting_thread()); | 92 process_state_->threads()->at(process_state_->requesting_thread()); |
70 const vector<StackFrame*>& crashing_thread_frames = | 93 const vector<StackFrame*>& crashing_thread_frames = |
71 *crashing_thread->frames(); | 94 *crashing_thread->frames(); |
72 for (size_t i = 0; i < crashing_thread_frames.size(); ++i) { | 95 for (size_t i = 0; i < crashing_thread_frames.size(); ++i) { |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
115 BPLOG(INFO) << "Failed to retrieve instruction pointer."; | 138 BPLOG(INFO) << "Failed to retrieve instruction pointer."; |
116 return EXPLOITABILITY_ERR_PROCESSING; | 139 return EXPLOITABILITY_ERR_PROCESSING; |
117 } | 140 } |
118 | 141 |
119 // Getting the stack pointer. | 142 // Getting the stack pointer. |
120 if (!context->GetStackPointer(&stack_ptr)) { | 143 if (!context->GetStackPointer(&stack_ptr)) { |
121 BPLOG(INFO) << "Failed to retrieve stack pointer."; | 144 BPLOG(INFO) << "Failed to retrieve stack pointer."; |
122 return EXPLOITABILITY_ERR_PROCESSING; | 145 return EXPLOITABILITY_ERR_PROCESSING; |
123 } | 146 } |
124 | 147 |
125 // Checking for the instruction pointer in a valid instruction region. | 148 // Checking for the instruction pointer in a valid instruction region, |
149 // a misplaced stack pointer, and an executable stack or heap. | |
126 if (!this->InstructionPointerInCode(instruction_ptr) || | 150 if (!this->InstructionPointerInCode(instruction_ptr) || |
127 this->StackPointerOffStack(stack_ptr) || | 151 this->StackPointerOffStack(stack_ptr) || |
128 this->ExecutableStackOrHeap()) { | 152 this->ExecutableStackOrHeap()) { |
129 return EXPLOITABILITY_HIGH; | 153 return EXPLOITABILITY_HIGH; |
130 } | 154 } |
131 | 155 |
156 // Check for write to read only memory or invalid memory, shelling out | |
157 // to objdump is enabled. | |
158 if (enable_objdump_ && this->EndedOnIllegalWrite(instruction_ptr)) { | |
159 return EXPLOITABILITY_HIGH; | |
160 } | |
161 | |
132 // There was no strong evidence suggesting exploitability, but the minidump | 162 // There was no strong evidence suggesting exploitability, but the minidump |
133 // does not appear totally benign either. | 163 // does not appear totally benign either. |
134 return EXPLOITABILITY_INTERESTING; | 164 return EXPLOITABILITY_INTERESTING; |
135 } | 165 } |
136 | 166 |
167 bool ExploitabilityLinux::EndedOnIllegalWrite(uint64_t instruction_ptr) { | |
168 #ifdef _WIN32 | |
169 BPLOG(INFO) << "MinGW does not support fork and exec. Terminating method."; | |
170 #else | |
171 // Get memory region containing instruction pointer. | |
172 MinidumpMemoryList *memory_list = dump_->GetMemoryList(); | |
173 MinidumpMemoryRegion *memory_region = | |
174 memory_list ? | |
175 memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL; | |
176 if (!memory_region) { | |
177 BPLOG(INFO) << "No memory region around instruction pointer."; | |
178 return false; | |
179 } | |
180 | |
181 // Get exception data to find architecture. | |
182 string architecture = ""; | |
183 MinidumpException *exception = dump_->GetException(); | |
184 // This should never evaluate to true, since this should not be reachable | |
185 // without checking for exception data earlier. | |
186 if (!exception) { | |
187 BPLOG(INFO) << "No exception data."; | |
188 return false; | |
189 } | |
190 const MDRawExceptionStream *raw_exception_stream = exception->exception(); | |
191 const MinidumpContext *context = exception->GetContext(); | |
192 // This should not evaluate to true, for the same reason mentioned above. | |
193 if (!raw_exception_stream || !context) { | |
194 BPLOG(INFO) << "No exception or architecture data."; | |
195 return false; | |
196 } | |
197 // Check architecture and set architecture variable to corresponding flag | |
198 // in objdump. | |
199 switch (context->GetContextCPU()) { | |
200 case MD_CONTEXT_X86: | |
201 architecture = "i386"; | |
202 break; | |
203 case MD_CONTEXT_AMD64: | |
204 architecture = "i386:x86-64"; | |
205 break; | |
206 default: | |
207 // Unsupported architecture. Note that ARM architectures are not | |
208 // supported because objdump does not support ARM. | |
209 return false; | |
210 break; | |
211 } | |
212 | |
213 // Get memory region around instruction pointer and the number of bytes | |
214 // before and after the instruction pointer in the memory region. | |
215 const uint8_t *raw_memory = memory_region->GetMemory(); | |
216 const uint64_t base = memory_region->GetBase(); | |
217 if (base > instruction_ptr) { | |
218 BPLOG(ERROR) << "Memory region base value exceeds instruction pointer."; | |
219 return false; | |
220 } | |
221 const uint64_t offset = instruction_ptr - base; | |
222 if (memory_region->GetSize() < MAX_INSTRUCTION_LEN + offset) { | |
223 BPLOG(INFO) << "Not enough bytes left to guarantee complete instruction."; | |
224 return false; | |
225 } | |
226 | |
227 // Convert bytes into objdump output. | |
228 char objdump_output_buffer[MAX_OBJDUMP_BUFFER_LEN] = {0}; | |
229 DisassembleBytes(architecture, | |
230 raw_memory + offset, | |
231 MAX_OBJDUMP_BUFFER_LEN, | |
232 objdump_output_buffer); | |
233 | |
234 // Put buffer data into stream to output line-by-line. | |
235 std::stringstream objdump_stream; | |
236 objdump_stream.str(string(objdump_output_buffer)); | |
237 string line; | |
238 | |
239 // Pipe each output line into the string until the string contains | |
240 // the first instruction from objdump. | |
241 // Loop until the line matches the regex or there are no lines left. | |
ivanpe
2015/08/20 21:17:51
Please, update the comment.
liuandrew
2015/08/20 21:59:57
Done.
| |
242 do { | |
243 if (!getline(objdump_stream, line)) { | |
244 BPLOG(INFO) << "Objdump instructions not found"; | |
245 return false; | |
246 } | |
247 } while (line.find("0:") == string::npos); | |
248 // This first instruction contains the above substring. | |
249 | |
250 // Convert objdump instruction line into the operation and operands. | |
251 string instruction = ""; | |
252 string dest = ""; | |
253 string src = ""; | |
254 TokenizeObjdumpInstruction(line, &instruction, &dest, &src); | |
255 | |
256 // Check if the operation is a write to memory. First, the instruction | |
257 // must one that can write to memory. Second, the write destination | |
258 // must be a spot in memory rather than a register. Since there are no | |
259 // symbols from objdump, the destination will be enclosed by brackets. | |
260 if (dest.size() > 2 && dest.at(0) == '[' && dest.at(dest.size() - 1) == ']' && | |
261 (!instruction.compare("mov") || !instruction.compare("inc") || | |
262 !instruction.compare("dec") || !instruction.compare("and") || | |
263 !instruction.compare("or") || !instruction.compare("xor") || | |
264 !instruction.compare("not") || !instruction.compare("neg") || | |
265 !instruction.compare("add") || !instruction.compare("sub") || | |
266 !instruction.compare("shl") || !instruction.compare("shr"))) { | |
267 // Strip away enclosing brackets from the destination address. | |
268 dest = dest.substr(1, dest.size() - 2); | |
269 uint64_t write_address = 0; | |
270 CalculateAddress(dest, *context, &write_address); | |
271 | |
272 // If the program crashed as a result of a write, the destination of | |
273 // the write must have been an address that did not permit writing. | |
274 // However, if the address is under 4k, due to program protections, | |
275 // the crash does not suggest exploitability for writes with such a | |
276 // low target address. | |
277 return write_address > 4096; | |
278 } | |
279 #endif // _WIN32 | |
280 return false; | |
281 } | |
282 | |
283 #ifndef _WIN32 | |
284 bool ExploitabilityLinux::CalculateAddress(const string &address_expression, | |
285 const DumpContext &context, | |
286 uint64_t *write_address) { | |
287 // The destination should be the format reg+a or reg-a, where reg | |
288 // is a register and a is a hexadecimal constant. Although more complex | |
289 // expressions can make valid instructions, objdump's disassembly outputs | |
290 // it in this simpler format. | |
291 // TODO(liuandrew): Handle more complex formats, should they arise. | |
292 | |
293 if (!write_address) { | |
294 BPLOG(ERROR) << "Null parameter."; | |
295 return false; | |
296 } | |
297 | |
298 // Clone parameter into a non-const string. | |
299 string expression = address_expression; | |
300 | |
301 // Parse out the constant that is added to the address (if it exists). | |
302 size_t delim = expression.find('+'); | |
303 bool positive_add_constant = true; | |
304 // Check if constant is subtracted instead of added. | |
305 if (delim == string::npos) { | |
306 positive_add_constant = false; | |
307 delim = expression.find('-'); | |
308 } | |
309 uint32_t add_constant = 0; | |
310 // Save constant and remove it from the expression. | |
311 if (delim != string::npos) { | |
312 if (!sscanf(expression.substr(delim + 1).c_str(), "%x", &add_constant)) { | |
313 BPLOG(ERROR) << "Failed to scan constant."; | |
314 return false; | |
315 } | |
316 expression = expression.substr(0, delim); | |
317 } | |
318 | |
319 // Set the the write address to the corresponding register. | |
320 // TODO(liuandrew): Add support for partial registers, such as | |
321 // the rax/eax/ax/ah/al chain. | |
322 switch (context.GetContextCPU()) { | |
323 case MD_CONTEXT_X86: | |
324 if (!expression.compare("eax")) { | |
325 *write_address = context.GetContextX86()->eax; | |
326 } else if (!expression.compare("ebx")) { | |
327 *write_address = context.GetContextX86()->ebx; | |
328 } else if (!expression.compare("ecx")) { | |
329 *write_address = context.GetContextX86()->ecx; | |
330 } else if (!expression.compare("edx")) { | |
331 *write_address = context.GetContextX86()->edx; | |
332 } else if (!expression.compare("edi")) { | |
333 *write_address = context.GetContextX86()->edi; | |
334 } else if (!expression.compare("esi")) { | |
335 *write_address = context.GetContextX86()->esi; | |
336 } else if (!expression.compare("ebp")) { | |
337 *write_address = context.GetContextX86()->ebp; | |
338 } else if (!expression.compare("esp")) { | |
339 *write_address = context.GetContextX86()->esp; | |
340 } else if (!expression.compare("eip")) { | |
341 *write_address = context.GetContextX86()->eip; | |
342 } else { | |
343 BPLOG(ERROR) << "Unsupported register"; | |
344 return false; | |
345 } | |
346 break; | |
347 case MD_CONTEXT_AMD64: | |
348 if (!expression.compare("rax")) { | |
349 *write_address = context.GetContextAMD64()->rax; | |
350 } else if (!expression.compare("rbx")) { | |
351 *write_address = context.GetContextAMD64()->rbx; | |
352 } else if (!expression.compare("rcx")) { | |
353 *write_address = context.GetContextAMD64()->rcx; | |
354 } else if (!expression.compare("rdx")) { | |
355 *write_address = context.GetContextAMD64()->rdx; | |
356 } else if (!expression.compare("rdi")) { | |
357 *write_address = context.GetContextAMD64()->rdi; | |
358 } else if (!expression.compare("rsi")) { | |
359 *write_address = context.GetContextAMD64()->rsi; | |
360 } else if (!expression.compare("rbp")) { | |
361 *write_address = context.GetContextAMD64()->rbp; | |
362 } else if (!expression.compare("rsp")) { | |
363 *write_address = context.GetContextAMD64()->rsp; | |
364 } else if (!expression.compare("rip")) { | |
365 *write_address = context.GetContextAMD64()->rip; | |
366 } else if (!expression.compare("r8")) { | |
367 *write_address = context.GetContextAMD64()->r8; | |
368 } else if (!expression.compare("r9")) { | |
369 *write_address = context.GetContextAMD64()->r9; | |
370 } else if (!expression.compare("r10")) { | |
371 *write_address = context.GetContextAMD64()->r10; | |
372 } else if (!expression.compare("r11")) { | |
373 *write_address = context.GetContextAMD64()->r11; | |
374 } else if (!expression.compare("r12")) { | |
375 *write_address = context.GetContextAMD64()->r12; | |
376 } else if (!expression.compare("r13")) { | |
377 *write_address = context.GetContextAMD64()->r13; | |
378 } else if (!expression.compare("r14")) { | |
379 *write_address = context.GetContextAMD64()->r14; | |
380 } else if (!expression.compare("r15")) { | |
381 *write_address = context.GetContextAMD64()->r15; | |
382 } else { | |
383 BPLOG(ERROR) << "Unsupported register"; | |
384 return false; | |
385 } | |
386 break; | |
387 default: | |
388 // This should not occur since the same switch condition | |
389 // should have terminated this method. | |
390 return false; | |
391 break; | |
392 } | |
393 | |
394 // Add or subtract constant from write address (if applicable). | |
395 *write_address = | |
396 positive_add_constant ? | |
397 *write_address + add_constant : *write_address - add_constant; | |
398 | |
399 return true; | |
400 } | |
401 | |
402 bool ExploitabilityLinux::TokenizeObjdumpInstruction(const string &line, | |
403 string *operation, | |
404 string *dest, | |
405 string *src) { | |
406 if (!operation || !dest || !src) { | |
407 BPLOG(ERROR) << "Null parameters passed."; | |
408 return false; | |
409 } | |
410 | |
411 // Set all pointer values to empty strings. | |
412 *operation = ""; | |
413 *dest = ""; | |
414 *src = ""; | |
415 | |
416 // Tokenize the objdump line. | |
417 vector<string> tokens; | |
418 std::istringstream line_stream(line); | |
419 copy(std::istream_iterator<string>(line_stream), | |
420 std::istream_iterator<string>(), | |
421 std::back_inserter(tokens)); | |
422 | |
423 // Regex for the data in hex form. Each byte is two hex digits. | |
424 regex_t regex; | |
425 regcomp(®ex, "^[[:xdigit:]]{2}$", REG_EXTENDED | REG_NOSUB); | |
426 | |
427 // Find and set the location of the operator. The operator appears | |
428 // directly after the chain of bytes that define the instruction. The | |
429 // operands will be the last token, given that the instruction has operands. | |
430 // If not, the operator is the last token. The loop skips the first token | |
431 // because the first token is the instruction number (namely "0:"). | |
432 string operands = ""; | |
433 for (size_t i = 1; i < tokens.size(); i++) { | |
434 // Check if current token no longer is in byte format. | |
435 if (regexec(®ex, tokens[i].c_str(), 0, NULL, 0)) { | |
436 // instruction = tokens[i]; | |
437 *operation = tokens[i]; | |
438 // If the operator is the last token, there are no operands. | |
439 if (i != tokens.size() - 1) { | |
440 operands = tokens[tokens.size() - 1]; | |
441 } | |
442 break; | |
443 } | |
444 } | |
445 regfree(®ex); | |
446 | |
447 if (operation->empty()) { | |
448 BPLOG(ERROR) << "Failed to parse out operation from objdump instruction."; | |
449 return false; | |
450 } | |
451 | |
452 // Split operands into source and destination (if applicable). | |
453 if (!operands.empty()) { | |
454 size_t delim = operands.find(','); | |
455 if (delim == string::npos) { | |
456 *dest = operands; | |
457 } else { | |
458 *dest = operands.substr(0, delim); | |
459 *src = operands.substr(delim + 1); | |
460 } | |
461 } | |
462 return true; | |
463 } | |
464 | |
465 bool ExploitabilityLinux::DisassembleBytes(const string &architecture, | |
466 const uint8_t *raw_bytes, | |
467 const unsigned int buffer_len, | |
468 char *objdump_output_buffer) { | |
469 if (!raw_bytes || !objdump_output_buffer) { | |
470 BPLOG(ERROR) << "Bad input parameters."; | |
471 return false; | |
472 } | |
473 | |
474 // Write raw bytes around instruction pointer to a temporary file to | |
475 // pass as an argument to objdump. | |
476 char raw_bytes_tmpfile[] = "/tmp/breakpad_mem_region-raw_bytes-XXXXXX"; | |
477 int raw_bytes_fd = mkstemp(raw_bytes_tmpfile); | |
478 if (raw_bytes_fd < 0) { | |
479 BPLOG(ERROR) << "Failed to create tempfile."; | |
480 unlink(raw_bytes_tmpfile); | |
481 return false; | |
482 } | |
483 if (write(raw_bytes_fd, raw_bytes, MAX_INSTRUCTION_LEN) | |
484 != MAX_INSTRUCTION_LEN) { | |
485 BPLOG(ERROR) << "Writing of raw bytes failed."; | |
486 unlink(raw_bytes_tmpfile); | |
487 return false; | |
488 } | |
489 | |
490 char cmd[1024] = {0}; | |
491 snprintf(cmd, | |
492 1024, | |
493 "objdump -D -b binary -M intel -m %s %s", | |
494 architecture.c_str(), | |
495 raw_bytes_tmpfile); | |
496 FILE *objdump_fp = popen(cmd, "r"); | |
497 if (!objdump_fp) { | |
498 fclose(objdump_fp); | |
499 unlink(raw_bytes_tmpfile); | |
500 BPLOG(ERROR) << "Failed to call objdump."; | |
501 return false; | |
502 } | |
503 if (fread(objdump_output_buffer, 1, buffer_len, objdump_fp) <= 0) { | |
504 fclose(objdump_fp); | |
505 unlink(raw_bytes_tmpfile); | |
506 BPLOG(ERROR) << "Failed to read objdump output."; | |
507 return false; | |
508 } | |
509 fclose(objdump_fp); | |
510 unlink(raw_bytes_tmpfile); | |
511 return true; | |
512 } | |
513 #endif // _WIN32 | |
514 | |
137 bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) { | 515 bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) { |
138 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); | 516 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); |
139 // Inconclusive if there are no mappings available. | 517 // Inconclusive if there are no mappings available. |
140 if (!linux_maps_list) { | 518 if (!linux_maps_list) { |
141 return false; | 519 return false; |
142 } | 520 } |
143 const MinidumpLinuxMaps *linux_maps = | 521 const MinidumpLinuxMaps *linux_maps = |
144 linux_maps_list->GetLinuxMapsForAddress(stack_ptr); | 522 linux_maps_list->GetLinuxMapsForAddress(stack_ptr); |
145 // Checks if the stack pointer maps to a valid mapping and if the mapping | 523 // Checks if the stack pointer maps to a valid mapping and if the mapping |
146 // is not the stack. If the mapping has no name, it is inconclusive whether | 524 // is not the stack. If the mapping has no name, it is inconclusive whether |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
217 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: | 595 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: |
218 return true; | 596 return true; |
219 break; | 597 break; |
220 default: | 598 default: |
221 return false; | 599 return false; |
222 break; | 600 break; |
223 } | 601 } |
224 } | 602 } |
225 | 603 |
226 } // namespace google_breakpad | 604 } // namespace google_breakpad |
OLD | NEW |