Index: lib/hwasan/CMakeLists.txt =================================================================== --- lib/hwasan/CMakeLists.txt +++ lib/hwasan/CMakeLists.txt @@ -11,6 +11,7 @@ hwasan_memintrinsics.cpp hwasan_poisoning.cpp hwasan_report.cpp + hwasan_tag_mismatch_aarch64.S hwasan_thread.cpp hwasan_thread_list.cpp ) Index: lib/hwasan/hwasan_interface_internal.h =================================================================== --- lib/hwasan/hwasan_interface_internal.h +++ lib/hwasan/hwasan_interface_internal.h @@ -100,6 +100,9 @@ uptr __hwasan_tag_pointer(uptr p, u8 tag); SANITIZER_INTERFACE_ATTRIBUTE +void __hwasan_tag_mismatch(uptr addr, u8 ts); + +SANITIZER_INTERFACE_ATTRIBUTE u8 __hwasan_generate_tag(); // Returns the offset of the first tag mismatch or -1 if the whole range is Index: lib/hwasan/hwasan_linux.cpp =================================================================== --- lib/hwasan/hwasan_linux.cpp +++ lib/hwasan/hwasan_linux.cpp @@ -374,14 +374,25 @@ } static void HandleTagMismatch(AccessInfo ai, uptr pc, uptr frame, - ucontext_t *uc) { + ucontext_t *uc, uptr *registers_frame = nullptr) { InternalMmapVector stack_buffer(1); BufferedStackTrace *stack = stack_buffer.data(); stack->Reset(); stack->Unwind(pc, frame, uc, common_flags()->fast_unwind_on_fatal); + // The second stack frame contains the failure __hwasan_check function, as + // we have a stack frame for the registers saved in __hwasan_tag_mismatch that + // we wish to ignore. This (currently) only occurs on AArch64, as x64 + // implementations use SIGTRAP to implement the failure, and thus do not go + // through the stack saver. + if (registers_frame && stack->trace && stack->size > 0) { + stack->trace++; + stack->size--; + } + bool fatal = flags()->halt_on_error || !ai.recover; - ReportTagMismatch(stack, ai.addr, ai.size, ai.is_store, fatal); + ReportTagMismatch(stack, ai.addr, ai.size, ai.is_store, fatal, + registers_frame); } static bool HwasanOnSIGTRAP(int signo, siginfo_t *info, ucontext_t *uc) { @@ -401,8 +412,10 @@ return true; } -extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __hwasan_tag_mismatch( - uptr addr, uptr access_info) { +// Entry point stub for interoperability between __hwasan_tag_mismatch (ASM) and +// the rest of the mismatch handling code (C++). +extern "C" void __hwasan_tag_mismatch_stub(uptr addr, uptr access_info, + uptr *registers_frame) { AccessInfo ai; ai.is_store = access_info & 0x10; ai.recover = false; @@ -410,7 +423,7 @@ ai.size = 1 << (access_info & 0xf); HandleTagMismatch(ai, (uptr)__builtin_return_address(0), - (uptr)__builtin_frame_address(0), nullptr); + (uptr)__builtin_frame_address(0), nullptr, registers_frame); __builtin_unreachable(); } Index: lib/hwasan/hwasan_report.h =================================================================== --- lib/hwasan/hwasan_report.h +++ lib/hwasan/hwasan_report.h @@ -22,11 +22,11 @@ void ReportStats(); void ReportTagMismatch(StackTrace *stack, uptr addr, uptr access_size, - bool is_store, bool fatal); + bool is_store, bool fatal, uptr *registers_frame); void ReportInvalidFree(StackTrace *stack, uptr addr); void ReportTailOverwritten(StackTrace *stack, uptr addr, uptr orig_size, uptr tail_size, const u8 *expected); - +void ReportRegisters(uptr *registers_frame, uptr pc); void ReportAtExitStatistics(); Index: lib/hwasan/hwasan_report.cpp =================================================================== --- lib/hwasan/hwasan_report.cpp +++ lib/hwasan/hwasan_report.cpp @@ -14,6 +14,7 @@ #include "hwasan.h" #include "hwasan_allocator.h" #include "hwasan_mapping.h" +#include "hwasan_report.h" #include "hwasan_thread.h" #include "hwasan_thread_list.h" #include "sanitizer_common/sanitizer_allocator_internal.h" @@ -389,7 +390,7 @@ } void ReportTagMismatch(StackTrace *stack, uptr tagged_addr, uptr access_size, - bool is_store, bool fatal) { + bool is_store, bool fatal, uptr *registers_frame) { ScopedReport R(fatal); SavedStackAllocations current_stack_allocations( GetCurrentThread()->stack_allocations()); @@ -430,7 +431,31 @@ PrintTagsAroundAddr(tag_ptr); + if (registers_frame) + ReportRegisters(registers_frame, pc); + ReportErrorSummary(bug_type, stack); } +// See the frame breakdown defined in __hwasan_tag_mismatch (from +// hwasan_tag_mismatch_aarch64.S). +static const char *kDoubleSpace = " "; +static const char *kSingleSpace = " "; +void ReportRegisters(uptr *frame, uptr pc) { + Printf("Registers where the failure occurred (pc %p):", pc); + + for (unsigned i = 0; i <= 30; i++) { + if (i % 4 == 0) + Printf("\n "); + + // Note - manually inserting a double or single space here based on the + // number of digits in the register name, as our sanitizer Printf does not + // support padding where the content is left aligned (i.e. the format + // specifier "%-2d" will CHECK fail). + Printf(" x%d%s%016llx", i, (i < 10) ? kDoubleSpace : kSingleSpace, + frame[i]); + } + Printf("\n"); +} + } // namespace __hwasan Index: lib/hwasan/hwasan_tag_mismatch_aarch64.S =================================================================== --- lib/hwasan/hwasan_tag_mismatch_aarch64.S +++ lib/hwasan/hwasan_tag_mismatch_aarch64.S @@ -0,0 +1,108 @@ +#include "sanitizer_common/sanitizer_asm.h" + +// The content of this file is AArch64-only: +#if defined(__aarch64__) + +// The responsibility of the HWASan entry point in compiler-rt is to primarily +// readjust the stack from the callee and save the current register values to +// the stack. +// This entry point function should be called from a __hwasan_check_* symbol. +// These are generated during a lowering pass in the backend, and are found in +// AArch64AsmPrinter::EmitHwasanMemaccessSymbols(). Please look there for +// further information. +// The __hwasan_check_* caller of this function should have expanded the stack +// and saved the previous values of x0, x1, x29, and x30. This function will +// "consume" these saved values and treats it as part of its own stack frame. +// In this sense, the __hwasan_check_* callee and this function "share" a stack +// frame. This allows us to omit having unwinding information (.cfi_*) present +// in every __hwasan_check_* function, therefore reducing binary size. This is +// particularly important as hwasan_check_* instances are duplicated in every +// translation unit where HWASan is enabled. +// This function calls HwasanTagMismatch to step back into the C++ code that +// completes the stack unwinding and error printing. This function is is not +// permitted to return. + + +// Frame from __hwasan_check_: +// | ... | +// | ... | +// | Previous stack frames... | +// +=================================+ +// | Unused 8-bytes for maintaining | +// | 16-byte SP alignment. | +// +---------------------------------+ +// | Return address (x30) for caller | +// | of __hwasan_check_*. | +// +---------------------------------+ +// | Frame address (x29) for caller | +// | of __hwasan_check_* | +// +---------------------------------+ <-- [SP + 232] +// | ... | +// | | +// | Stack frame space for x2 - x28. | +// | | +// | ... | +// +---------------------------------+ <-- [SP + 16] +// | | +// | Saved x1, as __hwasan_check_* | +// | clobbers it. | +// +---------------------------------+ +// | Saved x0, likewise above. | +// +---------------------------------+ <-- [x30 / SP] + +// This function takes two arguments: +// * x0: The address of read/write instruction that caused HWASan check fail. +// * x1: The tag size. + +.section .text +.file "hwasan_tag_mismatch_aarch64.S" +.global __hwasan_tag_mismatch +.type __hwasan_tag_mismatch, %function +__hwasan_tag_mismatch: + CFI_STARTPROC + + // Set the CFA to be the return address for caller of __hwasan_check_*. Note + // that we do not emit CFI predicates to describe the contents of this stack + // frame, as this proxy entry point should never be debugged. The contents + // are static and are handled by the unwinder after calling + // __hwasan_tag_mismatch. The frame pointer is already correctly setup + // by __hwasan_check_*. + add x29, sp, #232 + CFI_DEF_CFA(w29, 16) + CFI_OFFSET(w30, -8) + CFI_OFFSET(w29, -16) + + // Save the rest of the registers into the preallocated space left by + // __hwasan_check. + str x28, [sp, #224] + stp x26, x27, [sp, #208] + stp x24, x25, [sp, #192] + stp x22, x23, [sp, #176] + stp x20, x21, [sp, #160] + stp x18, x19, [sp, #144] + stp x16, x17, [sp, #128] + stp x14, x15, [sp, #112] + stp x12, x13, [sp, #96] + stp x10, x11, [sp, #80] + stp x8, x9, [sp, #64] + stp x6, x7, [sp, #48] + stp x4, x5, [sp, #32] + stp x2, x3, [sp, #16] + + // Pass the address of the frame to __hwasan_tag_mismatch_stub, so that it can + // extract the saved registers from this frame without having to worry about + // finding this frame. + mov x2, sp + + bl __hwasan_tag_mismatch_stub + CFI_ENDPROC + +.Lfunc_end0: + .size __hwasan_tag_mismatch, .Lfunc_end0-__hwasan_tag_mismatch + +.addrsig + +#endif // defined(__aarch64__) + +// We do not need executable stack. +NO_EXEC_STACK_DIRECTIVE Index: test/hwasan/TestCases/register-dump-read.c =================================================================== --- test/hwasan/TestCases/register-dump-read.c +++ test/hwasan/TestCases/register-dump-read.c @@ -0,0 +1,43 @@ +// RUN: %clang_hwasan -ffixed-x10 -ffixed-x20 -ffixed-x27 -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK +// RUN: %clang_hwasan -ffixed-x10 -ffixed-x20 -ffixed-x27 -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK +// RUN: %clang_hwasan -ffixed-x10 -ffixed-x20 -ffixed-x27 -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK +// RUN: %clang_hwasan -ffixed-x10 -ffixed-x20 -ffixed-x27 -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK +// REQUIRES: aarch64-target-arch + +// RUN: %clang_hwasan -ffixed-x10 -ffixed-x20 -ffixed-x27 -O2 %s -o %t && not %env_hwasan_opts=fast_unwind_on_fatal=true %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK +// RUN: %clang_hwasan -ffixed-x10 -ffixed-x20 -ffixed-x27 -O2 %s -o %t && not %env_hwasan_opts=fast_unwind_on_fatal=false %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK + +#include +#include +#include + +int main() { + __hwasan_enable_allocator_tagging(); + char * volatile x = (char*) malloc(10); + asm volatile("mov x10, #0x2222\n" + "mov x20, #0x3333\n" + "mov x27, #0x4444\n"); + return x[16]; + + // CHECK: ERROR: HWAddressSanitizer: + // CHECK: #0 {{.*}} in main {{.*}}register-dump-read.c:[[@LINE-3]] + + // Developer note: FileCheck really doesn't like when you have a regex that + // ends with a '}' character, e.g. the regex "[0-9]{10}" will fail, because + // the closing '}' fails as an "unbalanced regex". We work around this by + // encasing the trailing space after a register, or the end-of-line specifier. + + // CHECK: Registers where the failure occurred + // CHECK-NEXT: x0{{[ ]+[0-9a-f]{16}[ ]}}x1{{[ ]+[0-9a-f]{16}[ ]}}x2{{[ ]+[0-9a-f]{16}[ ]}}x3{{[ ]+[0-9a-f]{16}$}} + // CHECK-NEXT: x4{{[ ]+[0-9a-f]{16}[ ]}}x5{{[ ]+[0-9a-f]{16}[ ]}}x6{{[ ]+[0-9a-f]{16}[ ]}}x7{{[ ]+[0-9a-f]{16}$}} + // CHECK-NEXT: x8{{[ ]+[0-9a-f]{16}[ ]}}x9{{[ ]+[0-9a-f]{16}[ ]}} + // CHECK-SAME: x10 0000000000002222 + // CHECK-SAME: x11{{[ ]+[0-9a-f]{16}$}} + // CHECK-NEXT: x12{{[ ]+[0-9a-f]{16}[ ]}}x13{{[ ]+[0-9a-f]{16}[ ]}}x14{{[ ]+[0-9a-f]{16}[ ]}}x15{{[ ]+[0-9a-f]{16}$}} + // CHECK-NEXT: x16{{[ ]+[0-9a-f]{16}[ ]}}x17{{[ ]+[0-9a-f]{16}[ ]}}x18{{[ ]+[0-9a-f]{16}[ ]}}x19{{[ ]+[0-9a-f]{16}$}} + // CHECK-NEXT: x20 0000000000003333 + // CHECK-SAME: x21{{[ ]+[0-9a-f]{16}[ ]}}x22{{[ ]+[0-9a-f]{16}[ ]}}x23{{[ ]+[0-9a-f]{16}$}} + // CHECK-NEXT: x24{{[ ]+[0-9a-f]{16}[ ]}}x25{{[ ]+[0-9a-f]{16}[ ]}}x26{{[ ]+[0-9a-f]{16}[ ]}} + // CHECK-SAME: x27 0000000000004444 + // CHECK-NEXT: x28{{[ ]+[0-9a-f]{16}[ ]}}x29{{[ ]+[0-9a-f]{16}[ ]}}x30{{[ ]+[0-9a-f]{16}$}} +}