1 //===-- hwasan_report.cpp -------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file is a part of HWAddressSanitizer. 10 // 11 // Error reporting. 12 //===----------------------------------------------------------------------===// 13 14 #include "hwasan.h" 15 #include "hwasan_allocator.h" 16 #include "hwasan_mapping.h" 17 #include "hwasan_report.h" 18 #include "hwasan_thread.h" 19 #include "hwasan_thread_list.h" 20 #include "sanitizer_common/sanitizer_allocator_internal.h" 21 #include "sanitizer_common/sanitizer_common.h" 22 #include "sanitizer_common/sanitizer_flags.h" 23 #include "sanitizer_common/sanitizer_mutex.h" 24 #include "sanitizer_common/sanitizer_report_decorator.h" 25 #include "sanitizer_common/sanitizer_stackdepot.h" 26 #include "sanitizer_common/sanitizer_stacktrace_printer.h" 27 #include "sanitizer_common/sanitizer_symbolizer.h" 28 29 using namespace __sanitizer; 30 31 namespace __hwasan { 32 33 class ScopedReport { 34 public: 35 ScopedReport(bool fatal = false) : error_message_(1), fatal(fatal) { 36 BlockingMutexLock lock(&error_message_lock_); 37 error_message_ptr_ = fatal ? &error_message_ : nullptr; 38 ++hwasan_report_count; 39 } 40 41 ~ScopedReport() { 42 { 43 BlockingMutexLock lock(&error_message_lock_); 44 if (fatal) 45 SetAbortMessage(error_message_.data()); 46 error_message_ptr_ = nullptr; 47 } 48 if (common_flags()->print_module_map >= 2 || 49 (fatal && common_flags()->print_module_map)) 50 DumpProcessMap(); 51 if (fatal) 52 Die(); 53 } 54 55 static void MaybeAppendToErrorMessage(const char *msg) { 56 BlockingMutexLock lock(&error_message_lock_); 57 if (!error_message_ptr_) 58 return; 59 uptr len = internal_strlen(msg); 60 uptr old_size = error_message_ptr_->size(); 61 error_message_ptr_->resize(old_size + len); 62 // overwrite old trailing '\0', keep new trailing '\0' untouched. 63 internal_memcpy(&(*error_message_ptr_)[old_size - 1], msg, len); 64 } 65 private: 66 ScopedErrorReportLock error_report_lock_; 67 InternalMmapVector<char> error_message_; 68 bool fatal; 69 70 static InternalMmapVector<char> *error_message_ptr_; 71 static BlockingMutex error_message_lock_; 72 }; 73 74 InternalMmapVector<char> *ScopedReport::error_message_ptr_; 75 BlockingMutex ScopedReport::error_message_lock_; 76 77 // If there is an active ScopedReport, append to its error message. 78 void AppendToErrorMessageBuffer(const char *buffer) { 79 ScopedReport::MaybeAppendToErrorMessage(buffer); 80 } 81 82 static StackTrace GetStackTraceFromId(u32 id) { 83 CHECK(id); 84 StackTrace res = StackDepotGet(id); 85 CHECK(res.trace); 86 return res; 87 } 88 89 // A RAII object that holds a copy of the current thread stack ring buffer. 90 // The actual stack buffer may change while we are iterating over it (for 91 // example, Printf may call syslog() which can itself be built with hwasan). 92 class SavedStackAllocations { 93 public: 94 SavedStackAllocations(StackAllocationsRingBuffer *rb) { 95 uptr size = rb->size() * sizeof(uptr); 96 void *storage = 97 MmapAlignedOrDieOnFatalError(size, size * 2, "saved stack allocations"); 98 new (&rb_) StackAllocationsRingBuffer(*rb, storage); 99 } 100 101 ~SavedStackAllocations() { 102 StackAllocationsRingBuffer *rb = get(); 103 UnmapOrDie(rb->StartOfStorage(), rb->size() * sizeof(uptr)); 104 } 105 106 StackAllocationsRingBuffer *get() { 107 return (StackAllocationsRingBuffer *)&rb_; 108 } 109 110 private: 111 uptr rb_; 112 }; 113 114 class Decorator: public __sanitizer::SanitizerCommonDecorator { 115 public: 116 Decorator() : SanitizerCommonDecorator() { } 117 const char *Access() { return Blue(); } 118 const char *Allocation() const { return Magenta(); } 119 const char *Origin() const { return Magenta(); } 120 const char *Name() const { return Green(); } 121 const char *Location() { return Green(); } 122 const char *Thread() { return Green(); } 123 }; 124 125 // Returns the index of the rb element that matches tagged_addr (plus one), 126 // or zero if found nothing. 127 uptr FindHeapAllocation(HeapAllocationsRingBuffer *rb, 128 uptr tagged_addr, 129 HeapAllocationRecord *har) { 130 if (!rb) return 0; 131 for (uptr i = 0, size = rb->size(); i < size; i++) { 132 auto h = (*rb)[i]; 133 if (h.tagged_addr <= tagged_addr && 134 h.tagged_addr + h.requested_size > tagged_addr) { 135 *har = h; 136 return i + 1; 137 } 138 } 139 return 0; 140 } 141 142 static void PrintStackAllocations(StackAllocationsRingBuffer *sa, 143 tag_t addr_tag, uptr untagged_addr) { 144 uptr frames = Min((uptr)flags()->stack_history_size, sa->size()); 145 bool found_local = false; 146 for (uptr i = 0; i < frames; i++) { 147 const uptr *record_addr = &(*sa)[i]; 148 uptr record = *record_addr; 149 if (!record) 150 break; 151 tag_t base_tag = 152 reinterpret_cast<uptr>(record_addr) >> kRecordAddrBaseTagShift; 153 uptr fp = (record >> kRecordFPShift) << kRecordFPLShift; 154 uptr pc_mask = (1ULL << kRecordFPShift) - 1; 155 uptr pc = record & pc_mask; 156 FrameInfo frame; 157 if (Symbolizer::GetOrInit()->SymbolizeFrame(pc, &frame)) { 158 for (LocalInfo &local : frame.locals) { 159 if (!local.has_frame_offset || !local.has_size || !local.has_tag_offset) 160 continue; 161 tag_t obj_tag = base_tag ^ local.tag_offset; 162 if (obj_tag != addr_tag) 163 continue; 164 // Calculate the offset from the object address to the faulting 165 // address. Because we only store bits 4-19 of FP (bits 0-3 are 166 // guaranteed to be zero), the calculation is performed mod 2^20 and may 167 // harmlessly underflow if the address mod 2^20 is below the object 168 // address. 169 uptr obj_offset = 170 (untagged_addr - fp - local.frame_offset) & (kRecordFPModulus - 1); 171 if (obj_offset >= local.size) 172 continue; 173 if (!found_local) { 174 Printf("Potentially referenced stack objects:\n"); 175 found_local = true; 176 } 177 Printf(" %s in %s %s:%d\n", local.name, local.function_name, 178 local.decl_file, local.decl_line); 179 } 180 frame.Clear(); 181 } 182 } 183 184 if (found_local) 185 return; 186 187 // We didn't find any locals. Most likely we don't have symbols, so dump 188 // the information that we have for offline analysis. 189 InternalScopedString frame_desc(GetPageSizeCached() * 2); 190 Printf("Previously allocated frames:\n"); 191 for (uptr i = 0; i < frames; i++) { 192 const uptr *record_addr = &(*sa)[i]; 193 uptr record = *record_addr; 194 if (!record) 195 break; 196 uptr pc_mask = (1ULL << 48) - 1; 197 uptr pc = record & pc_mask; 198 frame_desc.append(" record_addr:0x%zx record:0x%zx", 199 reinterpret_cast<uptr>(record_addr), record); 200 if (SymbolizedStack *frame = Symbolizer::GetOrInit()->SymbolizePC(pc)) { 201 RenderFrame(&frame_desc, " %F %L\n", 0, frame->info, 202 common_flags()->symbolize_vs_style, 203 common_flags()->strip_path_prefix); 204 frame->ClearAll(); 205 } 206 Printf("%s", frame_desc.data()); 207 frame_desc.clear(); 208 } 209 } 210 211 // Returns true if tag == *tag_ptr, reading tags from short granules if 212 // necessary. This may return a false positive if tags 1-15 are used as a 213 // regular tag rather than a short granule marker. 214 static bool TagsEqual(tag_t tag, tag_t *tag_ptr) { 215 if (tag == *tag_ptr) 216 return true; 217 if (*tag_ptr == 0 || *tag_ptr > kShadowAlignment - 1) 218 return false; 219 uptr mem = ShadowToMem(reinterpret_cast<uptr>(tag_ptr)); 220 tag_t inline_tag = *reinterpret_cast<tag_t *>(mem + kShadowAlignment - 1); 221 return tag == inline_tag; 222 } 223 224 void PrintAddressDescription( 225 uptr tagged_addr, uptr access_size, 226 StackAllocationsRingBuffer *current_stack_allocations) { 227 Decorator d; 228 int num_descriptions_printed = 0; 229 uptr untagged_addr = UntagAddr(tagged_addr); 230 231 // Print some very basic information about the address, if it's a heap. 232 HwasanChunkView chunk = FindHeapChunkByAddress(untagged_addr); 233 if (uptr beg = chunk.Beg()) { 234 uptr size = chunk.ActualSize(); 235 Printf("%s[%p,%p) is a %s %s heap chunk; " 236 "size: %zd offset: %zd\n%s", 237 d.Location(), 238 beg, beg + size, 239 chunk.FromSmallHeap() ? "small" : "large", 240 chunk.IsAllocated() ? "allocated" : "unallocated", 241 size, untagged_addr - beg, 242 d.Default()); 243 } 244 245 // Check if this looks like a heap buffer overflow by scanning 246 // the shadow left and right and looking for the first adjacent 247 // object with a different memory tag. If that tag matches addr_tag, 248 // check the allocator if it has a live chunk there. 249 tag_t addr_tag = GetTagFromPointer(tagged_addr); 250 tag_t *tag_ptr = reinterpret_cast<tag_t*>(MemToShadow(untagged_addr)); 251 tag_t *candidate = nullptr, *left = tag_ptr, *right = tag_ptr; 252 for (int i = 0; i < 1000; i++) { 253 if (TagsEqual(addr_tag, left)) { 254 candidate = left; 255 break; 256 } 257 --left; 258 if (TagsEqual(addr_tag, right)) { 259 candidate = right; 260 break; 261 } 262 ++right; 263 } 264 265 if (candidate) { 266 uptr mem = ShadowToMem(reinterpret_cast<uptr>(candidate)); 267 HwasanChunkView chunk = FindHeapChunkByAddress(mem); 268 if (chunk.IsAllocated()) { 269 Printf("%s", d.Location()); 270 Printf("%p is located %zd bytes to the %s of %zd-byte region [%p,%p)\n", 271 untagged_addr, 272 candidate == left ? untagged_addr - chunk.End() 273 : chunk.Beg() - untagged_addr, 274 candidate == left ? "right" : "left", chunk.UsedSize(), 275 chunk.Beg(), chunk.End()); 276 Printf("%s", d.Allocation()); 277 Printf("allocated here:\n"); 278 Printf("%s", d.Default()); 279 GetStackTraceFromId(chunk.GetAllocStackId()).Print(); 280 num_descriptions_printed++; 281 } else { 282 // Check whether the address points into a loaded library. If so, this is 283 // most likely a global variable. 284 const char *module_name; 285 uptr module_address; 286 Symbolizer *sym = Symbolizer::GetOrInit(); 287 if (sym->GetModuleNameAndOffsetForPC(mem, &module_name, 288 &module_address)) { 289 DataInfo info; 290 if (sym->SymbolizeData(mem, &info) && info.start) { 291 Printf( 292 "%p is located %zd bytes to the %s of %zd-byte global variable " 293 "%s [%p,%p) in %s\n", 294 untagged_addr, 295 candidate == left ? untagged_addr - (info.start + info.size) 296 : info.start - untagged_addr, 297 candidate == left ? "right" : "left", info.size, info.name, 298 info.start, info.start + info.size, module_name); 299 } else { 300 Printf("%p is located to the %s of a global variable in (%s+0x%x)\n", 301 untagged_addr, candidate == left ? "right" : "left", 302 module_name, module_address); 303 } 304 num_descriptions_printed++; 305 } 306 } 307 } 308 309 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) { 310 // Scan all threads' ring buffers to find if it's a heap-use-after-free. 311 HeapAllocationRecord har; 312 if (uptr D = FindHeapAllocation(t->heap_allocations(), tagged_addr, &har)) { 313 Printf("%s", d.Location()); 314 Printf("%p is located %zd bytes inside of %zd-byte region [%p,%p)\n", 315 untagged_addr, untagged_addr - UntagAddr(har.tagged_addr), 316 har.requested_size, UntagAddr(har.tagged_addr), 317 UntagAddr(har.tagged_addr) + har.requested_size); 318 Printf("%s", d.Allocation()); 319 Printf("freed by thread T%zd here:\n", t->unique_id()); 320 Printf("%s", d.Default()); 321 GetStackTraceFromId(har.free_context_id).Print(); 322 323 Printf("%s", d.Allocation()); 324 Printf("previously allocated here:\n", t); 325 Printf("%s", d.Default()); 326 GetStackTraceFromId(har.alloc_context_id).Print(); 327 328 // Print a developer note: the index of this heap object 329 // in the thread's deallocation ring buffer. 330 Printf("hwasan_dev_note_heap_rb_distance: %zd %zd\n", D, 331 flags()->heap_history_size); 332 333 t->Announce(); 334 num_descriptions_printed++; 335 } 336 337 // Very basic check for stack memory. 338 if (t->AddrIsInStack(untagged_addr)) { 339 Printf("%s", d.Location()); 340 Printf("Address %p is located in stack of thread T%zd\n", untagged_addr, 341 t->unique_id()); 342 Printf("%s", d.Default()); 343 t->Announce(); 344 345 auto *sa = (t == GetCurrentThread() && current_stack_allocations) 346 ? current_stack_allocations 347 : t->stack_allocations(); 348 PrintStackAllocations(sa, addr_tag, untagged_addr); 349 num_descriptions_printed++; 350 } 351 }); 352 353 // Print the remaining threads, as an extra information, 1 line per thread. 354 hwasanThreadList().VisitAllLiveThreads([&](Thread *t) { t->Announce(); }); 355 356 if (!num_descriptions_printed) 357 // We exhausted our possibilities. Bail out. 358 Printf("HWAddressSanitizer can not describe address in more detail.\n"); 359 } 360 361 void ReportStats() {} 362 363 static void PrintTagInfoAroundAddr(tag_t *tag_ptr, uptr num_rows, 364 void (*print_tag)(InternalScopedString &s, 365 tag_t *tag)) { 366 const uptr row_len = 16; // better be power of two. 367 tag_t *center_row_beg = reinterpret_cast<tag_t *>( 368 RoundDownTo(reinterpret_cast<uptr>(tag_ptr), row_len)); 369 tag_t *beg_row = center_row_beg - row_len * (num_rows / 2); 370 tag_t *end_row = center_row_beg + row_len * ((num_rows + 1) / 2); 371 InternalScopedString s(GetPageSizeCached() * 8); 372 for (tag_t *row = beg_row; row < end_row; row += row_len) { 373 s.append("%s", row == center_row_beg ? "=>" : " "); 374 s.append("%p:", row); 375 for (uptr i = 0; i < row_len; i++) { 376 s.append("%s", row + i == tag_ptr ? "[" : " "); 377 print_tag(s, &row[i]); 378 s.append("%s", row + i == tag_ptr ? "]" : " "); 379 } 380 s.append("\n"); 381 } 382 Printf("%s", s.data()); 383 } 384 385 static void PrintTagsAroundAddr(tag_t *tag_ptr) { 386 Printf( 387 "Memory tags around the buggy address (one tag corresponds to %zd " 388 "bytes):\n", kShadowAlignment); 389 PrintTagInfoAroundAddr(tag_ptr, 17, [](InternalScopedString &s, tag_t *tag) { 390 s.append("%02x", *tag); 391 }); 392 393 Printf( 394 "Tags for short granules around the buggy address (one tag corresponds " 395 "to %zd bytes):\n", 396 kShadowAlignment); 397 PrintTagInfoAroundAddr(tag_ptr, 3, [](InternalScopedString &s, tag_t *tag) { 398 if (*tag >= 1 && *tag <= kShadowAlignment) { 399 uptr granule_addr = ShadowToMem(reinterpret_cast<uptr>(tag)); 400 s.append("%02x", 401 *reinterpret_cast<u8 *>(granule_addr + kShadowAlignment - 1)); 402 } else { 403 s.append(".."); 404 } 405 }); 406 Printf( 407 "See " 408 "https://clang.llvm.org/docs/" 409 "HardwareAssistedAddressSanitizerDesign.html#short-granules for a " 410 "description of short granule tags\n"); 411 } 412 413 void ReportInvalidFree(StackTrace *stack, uptr tagged_addr) { 414 ScopedReport R(flags()->halt_on_error); 415 416 uptr untagged_addr = UntagAddr(tagged_addr); 417 tag_t ptr_tag = GetTagFromPointer(tagged_addr); 418 tag_t *tag_ptr = reinterpret_cast<tag_t*>(MemToShadow(untagged_addr)); 419 tag_t mem_tag = *tag_ptr; 420 Decorator d; 421 Printf("%s", d.Error()); 422 uptr pc = stack->size ? stack->trace[0] : 0; 423 const char *bug_type = "invalid-free"; 424 Report("ERROR: %s: %s on address %p at pc %p\n", SanitizerToolName, bug_type, 425 untagged_addr, pc); 426 Printf("%s", d.Access()); 427 Printf("tags: %02x/%02x (ptr/mem)\n", ptr_tag, mem_tag); 428 Printf("%s", d.Default()); 429 430 stack->Print(); 431 432 PrintAddressDescription(tagged_addr, 0, nullptr); 433 434 PrintTagsAroundAddr(tag_ptr); 435 436 ReportErrorSummary(bug_type, stack); 437 } 438 439 void ReportTailOverwritten(StackTrace *stack, uptr tagged_addr, uptr orig_size, 440 const u8 *expected) { 441 uptr tail_size = kShadowAlignment - (orig_size % kShadowAlignment); 442 ScopedReport R(flags()->halt_on_error); 443 Decorator d; 444 uptr untagged_addr = UntagAddr(tagged_addr); 445 Printf("%s", d.Error()); 446 const char *bug_type = "allocation-tail-overwritten"; 447 Report("ERROR: %s: %s; heap object [%p,%p) of size %zd\n", SanitizerToolName, 448 bug_type, untagged_addr, untagged_addr + orig_size, orig_size); 449 Printf("\n%s", d.Default()); 450 stack->Print(); 451 HwasanChunkView chunk = FindHeapChunkByAddress(untagged_addr); 452 if (chunk.Beg()) { 453 Printf("%s", d.Allocation()); 454 Printf("allocated here:\n"); 455 Printf("%s", d.Default()); 456 GetStackTraceFromId(chunk.GetAllocStackId()).Print(); 457 } 458 459 InternalScopedString s(GetPageSizeCached() * 8); 460 CHECK_GT(tail_size, 0U); 461 CHECK_LT(tail_size, kShadowAlignment); 462 u8 *tail = reinterpret_cast<u8*>(untagged_addr + orig_size); 463 s.append("Tail contains: "); 464 for (uptr i = 0; i < kShadowAlignment - tail_size; i++) 465 s.append(".. "); 466 for (uptr i = 0; i < tail_size; i++) 467 s.append("%02x ", tail[i]); 468 s.append("\n"); 469 s.append("Expected: "); 470 for (uptr i = 0; i < kShadowAlignment - tail_size; i++) 471 s.append(".. "); 472 for (uptr i = 0; i < tail_size; i++) 473 s.append("%02x ", expected[i]); 474 s.append("\n"); 475 s.append(" "); 476 for (uptr i = 0; i < kShadowAlignment - tail_size; i++) 477 s.append(" "); 478 for (uptr i = 0; i < tail_size; i++) 479 s.append("%s ", expected[i] != tail[i] ? "^^" : " "); 480 481 s.append("\nThis error occurs when a buffer overflow overwrites memory\n" 482 "to the right of a heap object, but within the %zd-byte granule, e.g.\n" 483 " char *x = new char[20];\n" 484 " x[25] = 42;\n" 485 "%s does not detect such bugs in uninstrumented code at the time of write," 486 "\nbut can detect them at the time of free/delete.\n" 487 "To disable this feature set HWASAN_OPTIONS=free_checks_tail_magic=0\n", 488 kShadowAlignment, SanitizerToolName); 489 Printf("%s", s.data()); 490 GetCurrentThread()->Announce(); 491 492 tag_t *tag_ptr = reinterpret_cast<tag_t*>(MemToShadow(untagged_addr)); 493 PrintTagsAroundAddr(tag_ptr); 494 495 ReportErrorSummary(bug_type, stack); 496 } 497 498 void ReportTagMismatch(StackTrace *stack, uptr tagged_addr, uptr access_size, 499 bool is_store, bool fatal, uptr *registers_frame) { 500 ScopedReport R(fatal); 501 SavedStackAllocations current_stack_allocations( 502 GetCurrentThread()->stack_allocations()); 503 504 Decorator d; 505 Printf("%s", d.Error()); 506 uptr untagged_addr = UntagAddr(tagged_addr); 507 // TODO: when possible, try to print heap-use-after-free, etc. 508 const char *bug_type = "tag-mismatch"; 509 uptr pc = stack->size ? stack->trace[0] : 0; 510 Report("ERROR: %s: %s on address %p at pc %p\n", SanitizerToolName, bug_type, 511 untagged_addr, pc); 512 513 Thread *t = GetCurrentThread(); 514 515 sptr offset = 516 __hwasan_test_shadow(reinterpret_cast<void *>(tagged_addr), access_size); 517 CHECK(offset >= 0 && offset < static_cast<sptr>(access_size)); 518 tag_t ptr_tag = GetTagFromPointer(tagged_addr); 519 tag_t *tag_ptr = 520 reinterpret_cast<tag_t *>(MemToShadow(untagged_addr + offset)); 521 tag_t mem_tag = *tag_ptr; 522 523 Printf("%s", d.Access()); 524 Printf("%s of size %zu at %p tags: %02x/%02x (ptr/mem) in thread T%zd\n", 525 is_store ? "WRITE" : "READ", access_size, untagged_addr, ptr_tag, 526 mem_tag, t->unique_id()); 527 if (offset != 0) 528 Printf("Invalid access starting at offset [%zu, %zu)\n", offset, 529 Min(access_size, static_cast<uptr>(offset) + (1 << kShadowScale))); 530 Printf("%s", d.Default()); 531 532 stack->Print(); 533 534 PrintAddressDescription(tagged_addr, access_size, 535 current_stack_allocations.get()); 536 t->Announce(); 537 538 PrintTagsAroundAddr(tag_ptr); 539 540 if (registers_frame) 541 ReportRegisters(registers_frame, pc); 542 543 ReportErrorSummary(bug_type, stack); 544 } 545 546 // See the frame breakdown defined in __hwasan_tag_mismatch (from 547 // hwasan_tag_mismatch_aarch64.S). 548 void ReportRegisters(uptr *frame, uptr pc) { 549 Printf("Registers where the failure occurred (pc %p):\n", pc); 550 551 // We explicitly print a single line (4 registers/line) each iteration to 552 // reduce the amount of logcat error messages printed. Each Printf() will 553 // result in a new logcat line, irrespective of whether a newline is present, 554 // and so we wish to reduce the number of Printf() calls we have to make. 555 Printf(" x0 %016llx x1 %016llx x2 %016llx x3 %016llx\n", 556 frame[0], frame[1], frame[2], frame[3]); 557 Printf(" x4 %016llx x5 %016llx x6 %016llx x7 %016llx\n", 558 frame[4], frame[5], frame[6], frame[7]); 559 Printf(" x8 %016llx x9 %016llx x10 %016llx x11 %016llx\n", 560 frame[8], frame[9], frame[10], frame[11]); 561 Printf(" x12 %016llx x13 %016llx x14 %016llx x15 %016llx\n", 562 frame[12], frame[13], frame[14], frame[15]); 563 Printf(" x16 %016llx x17 %016llx x18 %016llx x19 %016llx\n", 564 frame[16], frame[17], frame[18], frame[19]); 565 Printf(" x20 %016llx x21 %016llx x22 %016llx x23 %016llx\n", 566 frame[20], frame[21], frame[22], frame[23]); 567 Printf(" x24 %016llx x25 %016llx x26 %016llx x27 %016llx\n", 568 frame[24], frame[25], frame[26], frame[27]); 569 Printf(" x28 %016llx x29 %016llx x30 %016llx\n", 570 frame[28], frame[29], frame[30]); 571 } 572 573 } // namespace __hwasan 574