blob: 6b0646fbcc02e741429399c7da2766546d738618 [file] [log] [blame]
Christopher Ferris723cf9b2017-01-19 20:08:48 -08001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Christopher Ferris723cf9b2017-01-19 20:08:48 -080017#include <stdint.h>
18
19#include <deque>
20#include <string>
21
22#include <android-base/stringprintf.h>
23
Christopher Ferrisd226a512017-07-14 10:37:19 -070024#include <unwindstack/Log.h>
25#include <unwindstack/Memory.h>
Christopher Ferrisd06001d2017-11-30 18:56:01 -080026#include <unwindstack/RegsArm.h>
Christopher Ferrisd226a512017-07-14 10:37:19 -070027
Christopher Ferris723cf9b2017-01-19 20:08:48 -080028#include "ArmExidx.h"
Christopher Ferris94167032017-06-28 18:56:52 -070029#include "Check.h"
Christopher Ferrisd06001d2017-11-30 18:56:01 -080030#include "MachineArm.h"
Christopher Ferrisd226a512017-07-14 10:37:19 -070031
32namespace unwindstack {
Christopher Ferris723cf9b2017-01-19 20:08:48 -080033
34void ArmExidx::LogRawData() {
35 std::string log_str("Raw Data:");
36 for (const uint8_t data : data_) {
37 log_str += android::base::StringPrintf(" 0x%02x", data);
38 }
39 log(log_indent_, log_str.c_str());
40}
41
42bool ArmExidx::ExtractEntryData(uint32_t entry_offset) {
43 data_.clear();
44 status_ = ARM_STATUS_NONE;
45
46 if (entry_offset & 1) {
47 // The offset needs to be at least two byte aligned.
48 status_ = ARM_STATUS_INVALID_ALIGNMENT;
49 return false;
50 }
51
52 // Each entry is a 32 bit prel31 offset followed by 32 bits
53 // of unwind information. If bit 31 of the unwind data is zero,
54 // then this is a prel31 offset to the start of the unwind data.
55 // If the unwind data is 1, then this is a cant unwind entry.
56 // Otherwise, this data is the compact form of the unwind information.
57 uint32_t data;
58 if (!elf_memory_->Read32(entry_offset + 4, &data)) {
59 status_ = ARM_STATUS_READ_FAILED;
60 return false;
61 }
62 if (data == 1) {
63 // This is a CANT UNWIND entry.
64 status_ = ARM_STATUS_NO_UNWIND;
65 if (log_) {
66 log(log_indent_, "Raw Data: 0x00 0x00 0x00 0x01");
67 log(log_indent_, "[cantunwind]");
68 }
69 return false;
70 }
71
72 if (data & (1UL << 31)) {
73 // This is a compact table entry.
74 if ((data >> 24) & 0xf) {
75 // This is a non-zero index, this code doesn't support
76 // other formats.
77 status_ = ARM_STATUS_INVALID_PERSONALITY;
78 return false;
79 }
80 data_.push_back((data >> 16) & 0xff);
81 data_.push_back((data >> 8) & 0xff);
82 uint8_t last_op = data & 0xff;
83 data_.push_back(last_op);
84 if (last_op != ARM_OP_FINISH) {
85 // If this didn't end with a finish op, add one.
86 data_.push_back(ARM_OP_FINISH);
87 }
88 if (log_) {
89 LogRawData();
90 }
91 return true;
92 }
93
94 // Get the address of the ops.
95 // Sign extend the data value if necessary.
96 int32_t signed_data = static_cast<int32_t>(data << 1) >> 1;
97 uint32_t addr = (entry_offset + 4) + signed_data;
98 if (!elf_memory_->Read32(addr, &data)) {
99 status_ = ARM_STATUS_READ_FAILED;
100 return false;
101 }
102
103 size_t num_table_words;
104 if (data & (1UL << 31)) {
105 // Compact model.
106 switch ((data >> 24) & 0xf) {
107 case 0:
108 num_table_words = 0;
109 data_.push_back((data >> 16) & 0xff);
110 break;
111 case 1:
112 case 2:
113 num_table_words = (data >> 16) & 0xff;
114 addr += 4;
115 break;
116 default:
117 // Only a personality of 0, 1, 2 is valid.
118 status_ = ARM_STATUS_INVALID_PERSONALITY;
119 return false;
120 }
121 data_.push_back((data >> 8) & 0xff);
122 data_.push_back(data & 0xff);
123 } else {
124 // Generic model.
125
126 // Skip the personality routine data, it doesn't contain any data
127 // needed to decode the unwind information.
128 addr += 4;
129 if (!elf_memory_->Read32(addr, &data)) {
130 status_ = ARM_STATUS_READ_FAILED;
131 return false;
132 }
133 num_table_words = (data >> 24) & 0xff;
134 data_.push_back((data >> 16) & 0xff);
135 data_.push_back((data >> 8) & 0xff);
136 data_.push_back(data & 0xff);
137 addr += 4;
138 }
139
140 if (num_table_words > 5) {
141 status_ = ARM_STATUS_MALFORMED;
142 return false;
143 }
144
145 for (size_t i = 0; i < num_table_words; i++) {
146 if (!elf_memory_->Read32(addr, &data)) {
147 status_ = ARM_STATUS_READ_FAILED;
148 return false;
149 }
150 data_.push_back((data >> 24) & 0xff);
151 data_.push_back((data >> 16) & 0xff);
152 data_.push_back((data >> 8) & 0xff);
153 data_.push_back(data & 0xff);
154 addr += 4;
155 }
156
157 if (data_.back() != ARM_OP_FINISH) {
158 // If this didn't end with a finish op, add one.
159 data_.push_back(ARM_OP_FINISH);
160 }
161
162 if (log_) {
163 LogRawData();
164 }
165 return true;
166}
167
168inline bool ArmExidx::GetByte(uint8_t* byte) {
169 if (data_.empty()) {
170 status_ = ARM_STATUS_TRUNCATED;
171 return false;
172 }
173 *byte = data_.front();
174 data_.pop_front();
175 return true;
176}
177
178inline bool ArmExidx::DecodePrefix_10_00(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700179 CHECK((byte >> 4) == 0x8);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800180
181 uint16_t registers = (byte & 0xf) << 8;
182 if (!GetByte(&byte)) {
183 return false;
184 }
185
186 registers |= byte;
187 if (registers == 0) {
188 // 10000000 00000000: Refuse to unwind
189 if (log_) {
190 log(log_indent_, "Refuse to unwind");
191 }
192 status_ = ARM_STATUS_NO_UNWIND;
193 return false;
194 }
195 // 1000iiii iiiiiiii: Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}
196 if (log_) {
197 bool add_comma = false;
198 std::string msg = "pop {";
199 for (size_t i = 0; i < 12; i++) {
200 if (registers & (1 << i)) {
201 if (add_comma) {
202 msg += ", ";
203 }
204 msg += android::base::StringPrintf("r%zu", i + 4);
205 add_comma = true;
206 }
207 }
208 log(log_indent_, "%s}", msg.c_str());
209 if (log_skip_execution_) {
210 return true;
211 }
212 }
213
214 registers <<= 4;
215 for (size_t reg = 4; reg < 16; reg++) {
216 if (registers & (1 << reg)) {
217 if (!process_memory_->Read32(cfa_, &(*regs_)[reg])) {
218 status_ = ARM_STATUS_READ_FAILED;
219 return false;
220 }
221 cfa_ += 4;
222 }
223 }
Christopher Ferris3958f802017-02-01 15:44:40 -0800224
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800225 // If the sp register is modified, change the cfa value.
226 if (registers & (1 << ARM_REG_SP)) {
227 cfa_ = (*regs_)[ARM_REG_SP];
228 }
Christopher Ferris3958f802017-02-01 15:44:40 -0800229
230 // Indicate if the pc register was set.
231 if (registers & (1 << ARM_REG_PC)) {
232 pc_set_ = true;
233 }
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800234 return true;
235}
236
237inline bool ArmExidx::DecodePrefix_10_01(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700238 CHECK((byte >> 4) == 0x9);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800239
240 uint8_t bits = byte & 0xf;
241 if (bits == 13 || bits == 15) {
242 // 10011101: Reserved as prefix for ARM register to register moves
243 // 10011111: Reserved as prefix for Intel Wireless MMX register to register moves
244 if (log_) {
245 log(log_indent_, "[Reserved]");
246 }
247 status_ = ARM_STATUS_RESERVED;
248 return false;
249 }
250 // 1001nnnn: Set vsp = r[nnnn] (nnnn != 13, 15)
251 if (log_) {
252 log(log_indent_, "vsp = r%d", bits);
253 if (log_skip_execution_) {
254 return true;
255 }
256 }
257 // It is impossible for bits to be larger than the total number of
258 // arm registers, so don't bother checking if bits is a valid register.
259 cfa_ = (*regs_)[bits];
260 return true;
261}
262
263inline bool ArmExidx::DecodePrefix_10_10(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700264 CHECK((byte >> 4) == 0xa);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800265
266 // 10100nnn: Pop r4-r[4+nnn]
267 // 10101nnn: Pop r4-r[4+nnn], r14
268 if (log_) {
269 std::string msg = "pop {r4";
270 uint8_t end_reg = byte & 0x7;
271 if (end_reg) {
272 msg += android::base::StringPrintf("-r%d", 4 + end_reg);
273 }
274 if (byte & 0x8) {
275 log(log_indent_, "%s, r14}", msg.c_str());
276 } else {
277 log(log_indent_, "%s}", msg.c_str());
278 }
279 if (log_skip_execution_) {
280 return true;
281 }
282 }
283
284 for (size_t i = 4; i <= 4 + (byte & 0x7); i++) {
285 if (!process_memory_->Read32(cfa_, &(*regs_)[i])) {
286 status_ = ARM_STATUS_READ_FAILED;
287 return false;
288 }
289 cfa_ += 4;
290 }
291 if (byte & 0x8) {
292 if (!process_memory_->Read32(cfa_, &(*regs_)[ARM_REG_R14])) {
293 status_ = ARM_STATUS_READ_FAILED;
294 return false;
295 }
296 cfa_ += 4;
297 }
298 return true;
299}
300
301inline bool ArmExidx::DecodePrefix_10_11_0000() {
302 // 10110000: Finish
303 if (log_) {
304 log(log_indent_, "finish");
305 if (log_skip_execution_) {
306 status_ = ARM_STATUS_FINISH;
307 return false;
308 }
309 }
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800310 status_ = ARM_STATUS_FINISH;
311 return false;
312}
313
314inline bool ArmExidx::DecodePrefix_10_11_0001() {
315 uint8_t byte;
316 if (!GetByte(&byte)) {
317 return false;
318 }
319
320 if (byte == 0) {
321 // 10110001 00000000: Spare
322 if (log_) {
323 log(log_indent_, "Spare");
324 }
325 status_ = ARM_STATUS_SPARE;
326 return false;
327 }
328 if (byte >> 4) {
329 // 10110001 xxxxyyyy: Spare (xxxx != 0000)
330 if (log_) {
331 log(log_indent_, "Spare");
332 }
333 status_ = ARM_STATUS_SPARE;
334 return false;
335 }
336
337 // 10110001 0000iiii: Pop integer registers under mask {r3, r2, r1, r0}
338 if (log_) {
339 bool add_comma = false;
340 std::string msg = "pop {";
341 for (size_t i = 0; i < 4; i++) {
342 if (byte & (1 << i)) {
343 if (add_comma) {
344 msg += ", ";
345 }
346 msg += android::base::StringPrintf("r%zu", i);
347 add_comma = true;
348 }
349 }
350 log(log_indent_, "%s}", msg.c_str());
351 if (log_skip_execution_) {
352 return true;
353 }
354 }
355
356 for (size_t reg = 0; reg < 4; reg++) {
357 if (byte & (1 << reg)) {
358 if (!process_memory_->Read32(cfa_, &(*regs_)[reg])) {
359 status_ = ARM_STATUS_READ_FAILED;
360 return false;
361 }
362 cfa_ += 4;
363 }
364 }
365 return true;
366}
367
368inline bool ArmExidx::DecodePrefix_10_11_0010() {
369 // 10110010 uleb128: vsp = vsp + 0x204 + (uleb128 << 2)
370 uint32_t result = 0;
371 uint32_t shift = 0;
372 uint8_t byte;
373 do {
374 if (!GetByte(&byte)) {
375 return false;
376 }
377
378 result |= (byte & 0x7f) << shift;
379 shift += 7;
380 } while (byte & 0x80);
381 result <<= 2;
382 if (log_) {
383 log(log_indent_, "vsp = vsp + %d", 0x204 + result);
384 if (log_skip_execution_) {
385 return true;
386 }
387 }
388 cfa_ += 0x204 + result;
389 return true;
390}
391
392inline bool ArmExidx::DecodePrefix_10_11_0011() {
393 // 10110011 sssscccc: Pop VFP double precision registers D[ssss]-D[ssss+cccc] by FSTMFDX
394 uint8_t byte;
395 if (!GetByte(&byte)) {
396 return false;
397 }
398
399 if (log_) {
400 uint8_t start_reg = byte >> 4;
401 std::string msg = android::base::StringPrintf("pop {d%d", start_reg);
402 uint8_t end_reg = start_reg + (byte & 0xf);
403 if (end_reg) {
404 msg += android::base::StringPrintf("-d%d", end_reg);
405 }
406 log(log_indent_, "%s}", msg.c_str());
407 if (log_skip_execution_) {
408 return true;
409 }
410 }
411 cfa_ += (byte & 0xf) * 8 + 12;
412 return true;
413}
414
415inline bool ArmExidx::DecodePrefix_10_11_01nn() {
416 // 101101nn: Spare
417 if (log_) {
418 log(log_indent_, "Spare");
419 }
420 status_ = ARM_STATUS_SPARE;
421 return false;
422}
423
424inline bool ArmExidx::DecodePrefix_10_11_1nnn(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700425 CHECK((byte & ~0x07) == 0xb8);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800426
427 // 10111nnn: Pop VFP double-precision registers D[8]-D[8+nnn] by FSTMFDX
428 if (log_) {
429 std::string msg = "pop {d8";
430 uint8_t last_reg = (byte & 0x7);
431 if (last_reg) {
432 msg += android::base::StringPrintf("-d%d", last_reg + 8);
433 }
434 log(log_indent_, "%s}", msg.c_str());
435 if (log_skip_execution_) {
436 return true;
437 }
438 }
439 // Only update the cfa.
440 cfa_ += (byte & 0x7) * 8 + 12;
441 return true;
442}
443
444inline bool ArmExidx::DecodePrefix_10(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700445 CHECK((byte >> 6) == 0x2);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800446
447 switch ((byte >> 4) & 0x3) {
448 case 0:
449 return DecodePrefix_10_00(byte);
450 case 1:
451 return DecodePrefix_10_01(byte);
452 case 2:
453 return DecodePrefix_10_10(byte);
454 default:
455 switch (byte & 0xf) {
456 case 0:
457 return DecodePrefix_10_11_0000();
458 case 1:
459 return DecodePrefix_10_11_0001();
460 case 2:
461 return DecodePrefix_10_11_0010();
462 case 3:
463 return DecodePrefix_10_11_0011();
464 default:
465 if (byte & 0x8) {
466 return DecodePrefix_10_11_1nnn(byte);
467 } else {
468 return DecodePrefix_10_11_01nn();
469 }
470 }
471 }
472}
473
474inline bool ArmExidx::DecodePrefix_11_000(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700475 CHECK((byte & ~0x07) == 0xc0);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800476
477 uint8_t bits = byte & 0x7;
478 if (bits == 6) {
479 if (!GetByte(&byte)) {
480 return false;
481 }
482
483 // 11000110 sssscccc: Intel Wireless MMX pop wR[ssss]-wR[ssss+cccc]
484 if (log_) {
485 uint8_t start_reg = byte >> 4;
486 std::string msg = android::base::StringPrintf("pop {wR%d", start_reg);
487 uint8_t end_reg = byte & 0xf;
488 if (end_reg) {
489 msg += android::base::StringPrintf("-wR%d", start_reg + end_reg);
490 }
491 log(log_indent_, "%s}", msg.c_str());
492 if (log_skip_execution_) {
493 return true;
494 }
495 }
496 // Only update the cfa.
497 cfa_ += (byte & 0xf) * 8 + 8;
498 } else if (bits == 7) {
499 if (!GetByte(&byte)) {
500 return false;
501 }
502
503 if (byte == 0) {
504 // 11000111 00000000: Spare
505 if (log_) {
506 log(log_indent_, "Spare");
507 }
508 status_ = ARM_STATUS_SPARE;
509 return false;
510 } else if ((byte >> 4) == 0) {
511 // 11000111 0000iiii: Intel Wireless MMX pop wCGR registers {wCGR0,1,2,3}
512 if (log_) {
513 bool add_comma = false;
514 std::string msg = "pop {";
515 for (size_t i = 0; i < 4; i++) {
516 if (byte & (1 << i)) {
517 if (add_comma) {
518 msg += ", ";
519 }
520 msg += android::base::StringPrintf("wCGR%zu", i);
521 add_comma = true;
522 }
523 }
524 log(log_indent_, "%s}", msg.c_str());
525 }
526 // Only update the cfa.
527 cfa_ += __builtin_popcount(byte) * 4;
528 } else {
529 // 11000111 xxxxyyyy: Spare (xxxx != 0000)
530 if (log_) {
531 log(log_indent_, "Spare");
532 }
533 status_ = ARM_STATUS_SPARE;
534 return false;
535 }
536 } else {
537 // 11000nnn: Intel Wireless MMX pop wR[10]-wR[10+nnn] (nnn != 6, 7)
538 if (log_) {
539 std::string msg = "pop {wR10";
540 uint8_t nnn = byte & 0x7;
541 if (nnn) {
542 msg += android::base::StringPrintf("-wR%d", 10 + nnn);
543 }
544 log(log_indent_, "%s}", msg.c_str());
545 if (log_skip_execution_) {
546 return true;
547 }
548 }
549 // Only update the cfa.
550 cfa_ += (byte & 0x7) * 8 + 8;
551 }
552 return true;
553}
554
555inline bool ArmExidx::DecodePrefix_11_001(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700556 CHECK((byte & ~0x07) == 0xc8);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800557
558 uint8_t bits = byte & 0x7;
559 if (bits == 0) {
560 // 11001000 sssscccc: Pop VFP double precision registers D[16+ssss]-D[16+ssss+cccc] by VPUSH
561 if (!GetByte(&byte)) {
562 return false;
563 }
564
565 if (log_) {
566 uint8_t start_reg = byte >> 4;
567 std::string msg = android::base::StringPrintf("pop {d%d", 16 + start_reg);
568 uint8_t end_reg = byte & 0xf;
569 if (end_reg) {
570 msg += android::base::StringPrintf("-d%d", 16 + start_reg + end_reg);
571 }
572 log(log_indent_, "%s}", msg.c_str());
573 if (log_skip_execution_) {
574 return true;
575 }
576 }
577 // Only update the cfa.
578 cfa_ += (byte & 0xf) * 8 + 8;
579 } else if (bits == 1) {
580 // 11001001 sssscccc: Pop VFP double precision registers D[ssss]-D[ssss+cccc] by VPUSH
581 if (!GetByte(&byte)) {
582 return false;
583 }
584
585 if (log_) {
586 uint8_t start_reg = byte >> 4;
587 std::string msg = android::base::StringPrintf("pop {d%d", start_reg);
588 uint8_t end_reg = byte & 0xf;
589 if (end_reg) {
590 msg += android::base::StringPrintf("-d%d", start_reg + end_reg);
591 }
592 log(log_indent_, "%s}", msg.c_str());
593 if (log_skip_execution_) {
594 return true;
595 }
596 }
597 // Only update the cfa.
598 cfa_ += (byte & 0xf) * 8 + 8;
599 } else {
600 // 11001yyy: Spare (yyy != 000, 001)
601 if (log_) {
602 log(log_indent_, "Spare");
603 }
604 status_ = ARM_STATUS_SPARE;
605 return false;
606 }
607 return true;
608}
609
610inline bool ArmExidx::DecodePrefix_11_010(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700611 CHECK((byte & ~0x07) == 0xd0);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800612
613 // 11010nnn: Pop VFP double precision registers D[8]-D[8+nnn] by VPUSH
614 if (log_) {
615 std::string msg = "pop {d8";
616 uint8_t end_reg = byte & 0x7;
617 if (end_reg) {
618 msg += android::base::StringPrintf("-d%d", 8 + end_reg);
619 }
620 log(log_indent_, "%s}", msg.c_str());
621 if (log_skip_execution_) {
622 return true;
623 }
624 }
625 cfa_ += (byte & 0x7) * 8 + 8;
626 return true;
627}
628
629inline bool ArmExidx::DecodePrefix_11(uint8_t byte) {
Christopher Ferris94167032017-06-28 18:56:52 -0700630 CHECK((byte >> 6) == 0x3);
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800631
632 switch ((byte >> 3) & 0x7) {
633 case 0:
634 return DecodePrefix_11_000(byte);
635 case 1:
636 return DecodePrefix_11_001(byte);
637 case 2:
638 return DecodePrefix_11_010(byte);
639 default:
640 // 11xxxyyy: Spare (xxx != 000, 001, 010)
641 if (log_) {
642 log(log_indent_, "Spare");
643 }
644 status_ = ARM_STATUS_SPARE;
645 return false;
646 }
647}
648
649bool ArmExidx::Decode() {
650 status_ = ARM_STATUS_NONE;
651 uint8_t byte;
652 if (!GetByte(&byte)) {
653 return false;
654 }
655
656 switch (byte >> 6) {
657 case 0:
658 // 00xxxxxx: vsp = vsp + (xxxxxxx << 2) + 4
659 if (log_) {
660 log(log_indent_, "vsp = vsp + %d", ((byte & 0x3f) << 2) + 4);
661 if (log_skip_execution_) {
662 break;
663 }
664 }
665 cfa_ += ((byte & 0x3f) << 2) + 4;
666 break;
667 case 1:
668 // 01xxxxxx: vsp = vsp - (xxxxxxx << 2) + 4
669 if (log_) {
670 log(log_indent_, "vsp = vsp - %d", ((byte & 0x3f) << 2) + 4);
671 if (log_skip_execution_) {
672 break;
673 }
674 }
675 cfa_ -= ((byte & 0x3f) << 2) + 4;
676 break;
677 case 2:
678 return DecodePrefix_10(byte);
679 default:
680 return DecodePrefix_11(byte);
681 }
682 return true;
683}
684
685bool ArmExidx::Eval() {
Christopher Ferris3958f802017-02-01 15:44:40 -0800686 pc_set_ = false;
Christopher Ferris723cf9b2017-01-19 20:08:48 -0800687 while (Decode());
688 return status_ == ARM_STATUS_FINISH;
689}
Christopher Ferrisd226a512017-07-14 10:37:19 -0700690
691} // namespace unwindstack