| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/zircon-internal/unique-backtrace.h> |
| |
| #include <cstdint> |
| |
| #include <zxtest/zxtest.h> |
| |
| #ifdef __Fuchsia__ |
| #include <lib/elf-psabi/sp.h> |
| #include <lib/zircon-internal/default_stack_size.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/exception.h> |
| #include <lib/zx/handle.h> |
| #include <lib/zx/process.h> |
| #include <lib/zx/thread.h> |
| #include <zircon/syscalls/debug.h> |
| #include <zircon/syscalls/exception.h> |
| |
| #include <memory> |
| #include <string_view> |
| #endif // __Fuchsia__ |
| |
| namespace { |
| |
| // The BUILD.gn rule defines ICF_WORKS to false when using various kinds of |
| // compiler instrumentation. Some of these cause identical functions in the |
| // source to become nonidentical code. So the baseline verification that ICF |
| // happens when expected can't be relied on in these builds. |
| constexpr bool kIcfExpected = ICF_WORKS; |
| |
| // Since it's never inlined, this will always get the actual return address. |
| // However, it's up to its caller to make sure that the return address is the |
| // PC in the actual caller rather than the caller's caller (or on up) via |
| // either inlining or tail-call optimization. |
| [[gnu::noinline]] uintptr_t RecordCaller() { |
| return reinterpret_cast<uintptr_t>(__builtin_return_address(0)); |
| } |
| |
| // Even if inlined away, this prevents the compiler from thinking it knows the |
| // value. Thus it can't do tail-call optimization for the function call that |
| // yields the argument value. |
| uintptr_t Launder(uintptr_t value) { |
| // Clang doesn't really understand GCC constraints that give it multiple |
| // options to choose from, so it generates lousy code for "=g" or even "=rm". |
| __asm__("" : "=r"(value) : "0"(value)); |
| return value; |
| } |
| |
| // Hence these four calls will get their own true PC address and never their |
| // caller's address by dint of a tail-call-optimized jump to RecordCaller(). |
| // Since they can't be inlined, they should always be appropriate candidates |
| // for ICF (or the LTO equivalent). The first pair are fully identical (unless |
| // modified by compiler instrumentation; see kIcfExpected, above), so they |
| // should be folded at link time into returning the same PC value at runtime. |
| // The second pair use the API under test to prevent that happening, so they |
| // should always have distinct PC values to return at runtime. |
| |
| [[gnu::noinline]] uintptr_t IcfExpected1() { return Launder(RecordCaller()); } |
| |
| [[gnu::noinline]] uintptr_t IcfExpected2() { return Launder(RecordCaller()); } |
| |
| [[gnu::noinline]] uintptr_t IcfPrevented1() { |
| ENSURE_UNIQUE_BACKTRACE(); |
| return Launder(RecordCaller()); |
| } |
| |
| [[gnu::noinline]] uintptr_t IcfPrevented2() { |
| ENSURE_UNIQUE_BACKTRACE(); |
| return Launder(RecordCaller()); |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, IcfExpected) { |
| uintptr_t caller1 = IcfExpected1(); |
| uintptr_t caller2 = IcfExpected2(); |
| if (kIcfExpected) { |
| EXPECT_EQ(caller1, caller2); |
| } |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, IcfPrevented) { |
| uintptr_t caller1 = IcfPrevented1(); |
| uintptr_t caller2 = IcfPrevented2(); |
| EXPECT_NE(caller1, caller2); |
| } |
| |
| // ICF also works for indirect duplication: once two callees have been folded |
| // together, the callers become identical enough to be folded together |
| // themselves. This can be prevented in either (or both) of two ways: |
| // |
| // * Prevent the indirect deduplication "indirectly" by preventing the |
| // deduplication of the callees. Two callers with identical code but |
| // different relocations (call targets) cannot be folded together. |
| // |
| // * Prevent the indirect deduplication "directly" by preventing the |
| // deduplication of the callers. Even if the callees of the two callers get |
| // folded together, the callers themselves won't be. |
| |
| [[gnu::noinline]] uintptr_t IndirectIcfExpected1() { return Launder(IcfExpected1()); } |
| |
| [[gnu::noinline]] uintptr_t IndirectIcfExpected2() { return Launder(IcfExpected2()); } |
| |
| [[gnu::noinline]] uintptr_t IndirectIcfPreventedIndirectly1() { return Launder(IcfPrevented1()); } |
| |
| [[gnu::noinline]] uintptr_t IndirectIcfPreventedIndirectly2() { return Launder(IcfPrevented2()); } |
| |
| [[gnu::noinline]] uintptr_t IndirectIcfPreventedDirectly1() { |
| ENSURE_UNIQUE_BACKTRACE(); |
| IcfExpected1(); |
| return Launder(RecordCaller()); |
| } |
| |
| [[gnu::noinline]] uintptr_t IndirectIcfPreventedDirectly2() { |
| ENSURE_UNIQUE_BACKTRACE(); |
| IcfExpected2(); |
| return Launder(RecordCaller()); |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, IndirectIcfExpected) { |
| uintptr_t caller1 = IndirectIcfExpected1(); |
| uintptr_t caller2 = IndirectIcfExpected2(); |
| if (kIcfExpected) { |
| EXPECT_EQ(caller1, caller2); |
| } |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, IndirectIcfPreventedIndirectly) { |
| uintptr_t caller1 = IndirectIcfPreventedIndirectly1(); |
| uintptr_t caller2 = IndirectIcfPreventedIndirectly2(); |
| EXPECT_NE(caller1, caller2); |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, IndirectIcfPreventedDirectly) { |
| uintptr_t caller1 = IndirectIcfPreventedDirectly1(); |
| uintptr_t caller2 = IndirectIcfPreventedDirectly2(); |
| EXPECT_NE(caller1, caller2); |
| } |
| |
| #ifdef __Fuchsia__ |
| |
| // kPcRegister is the PC member in the thread register state. |
| // kTrapExceptionType is the type of exception that __builtin_trap() causes. |
| |
| #if defined(__aarch64__) |
| |
| constexpr auto kPcRegister = &zx_thread_state_general_regs_t::pc; |
| constexpr zx_excp_type_t kTrapExceptionType = ZX_EXCP_SW_BREAKPOINT; |
| |
| #elif defined(__x86_64__) |
| |
| constexpr auto kPcRegister = &zx_thread_state_general_regs_t::rip; |
| constexpr zx_excp_type_t kTrapExceptionType = ZX_EXCP_UNDEFINED_INSTRUCTION; |
| |
| #else |
| #error "what machine?" |
| #endif |
| |
| // To test the crashing cases, we'll spawn a raw Zircon thread with no C |
| // library assistance so there are no hidden data structures to clean up after |
| // the thread is killed. |
| |
| void CatchCrash(void (*crash_function)(), uintptr_t& crash_pc) { |
| constexpr size_t kCrashThreadStackSize = ZIRCON_DEFAULT_STACK_SIZE; |
| constexpr std::string_view kCrashThreadName = "zircon-internal crash test"; |
| |
| zx::thread crash_thread; |
| ASSERT_OK(zx::thread::create(*zx::process::self(), kCrashThreadName.data(), |
| kCrashThreadName.size(), 0, &crash_thread)); |
| |
| // Set up to receive thread exceptions for the new thread. |
| zx::channel exception_channel; |
| ASSERT_OK(crash_thread.create_exception_channel(0, &exception_channel)); |
| |
| // Start it running with a stack and PC at the crash function's entry point. |
| std::unique_ptr<std::byte[]> crash_thread_stack = |
| std::make_unique<std::byte[]>(kCrashThreadStackSize); |
| const uintptr_t pc = reinterpret_cast<uintptr_t>(crash_function); |
| const uintptr_t sp = compute_initial_stack_pointer( |
| reinterpret_cast<uintptr_t>(crash_thread_stack.get()), kCrashThreadStackSize); |
| ASSERT_OK(crash_thread.start(pc, sp, 0, 0)); |
| |
| // Wait for the exception message and/or thread death. |
| zx_wait_item_t wait_items[] = { |
| {.handle = exception_channel.get(), .waitfor = ZX_CHANNEL_READABLE}, |
| {.handle = crash_thread.get(), .waitfor = ZX_THREAD_TERMINATED}, |
| }; |
| const zx_wait_item_t& wait_channel = wait_items[0]; |
| const zx_wait_item_t& wait_thread = wait_items[1]; |
| ASSERT_OK(zx::handle::wait_many(wait_items, std::size(wait_items), zx::time::infinite())); |
| |
| // The exception should happen first while the thread is still alive. |
| ASSERT_TRUE(wait_channel.pending & ZX_CHANNEL_READABLE); |
| ASSERT_FALSE(wait_thread.pending & ZX_THREAD_TERMINATED); |
| |
| // Read the exception message. |
| zx::exception exc; |
| zx_exception_info_t exc_info; |
| uint32_t nbytes = 0, nhandles = 0; |
| ASSERT_OK(exception_channel.read(0, &exc_info, exc.reset_and_get_address(), sizeof(exc_info), 1, |
| &nbytes, &nhandles)); |
| ASSERT_EQ(sizeof(exc_info), nbytes); |
| ASSERT_EQ(1u, nhandles); |
| |
| // When the exception handle is closed (by the zx::exception destructor at |
| // the end of the function), the thread will resume from the exception. Set |
| // it up to "resume" by doing an immediate thread exit. This should make it |
| // safe to assume its stack will never be used again from here on out. (The |
| // stack will also be freed by a destructor at the end of the function.) |
| constexpr uint32_t kExceptionState = ZX_EXCEPTION_STATE_THREAD_EXIT; |
| ASSERT_OK(exc.set_property(ZX_PROP_EXCEPTION_STATE, &kExceptionState, sizeof(kExceptionState))); |
| |
| // Check it was the exception we expect for __builtin_trap(). |
| ASSERT_EQ(kTrapExceptionType, exc_info.type); |
| |
| // Now fetch the thread's register state when it hit the __builtin_trap(). |
| zx_thread_state_general_regs_t regs = {}; |
| ASSERT_OK(crash_thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs))); |
| |
| // Extract the PC of the crash site. |
| crash_pc = regs.*kPcRegister; |
| ASSERT_NE(0, crash_pc); |
| } |
| |
| // The crashing entry points can't use anything but the basic stack. |
| |
| #ifdef __clang__ |
| #define BASIC_ABI [[clang::no_sanitize("all")]] |
| #else |
| #define BASIC_ABI // We don't use anything in GCC that needs to be avoided. |
| #endif |
| |
| BASIC_ABI [[noreturn]] void CrashWithIcfExpected1() { __builtin_trap(); } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIcfExpected2() { __builtin_trap(); } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIcfPrevented1() { CRASH_WITH_UNIQUE_BACKTRACE(); } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIcfPrevented2() { CRASH_WITH_UNIQUE_BACKTRACE(); } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, CrashWithIcfExpected) { |
| uintptr_t crash1 = 0, crash2 = 0; |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIcfExpected1, crash1)); |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIcfExpected2, crash2)); |
| if (kIcfExpected) { |
| EXPECT_EQ(crash1, crash2); |
| } |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, CrashWithIcfPrevented) { |
| uintptr_t crash1 = 0, crash2 = 0; |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIcfPrevented1, crash1)); |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIcfPrevented2, crash2)); |
| EXPECT_NE(crash1, crash2); |
| } |
| |
| BASIC_ABI [[gnu::noinline]] uintptr_t BasicAbiRecordCaller() { |
| return reinterpret_cast<uintptr_t>(__builtin_return_address(0)); |
| } |
| |
| BASIC_ABI uintptr_t BasicAbiLaunder(uintptr_t value) { |
| __asm__("" : "=r"(value) : "0"(value)); |
| return value; |
| } |
| |
| BASIC_ABI [[gnu::noinline]] uintptr_t BasicAbiIcfExpected1() { |
| return BasicAbiLaunder(BasicAbiRecordCaller()); |
| } |
| |
| BASIC_ABI [[gnu::noinline]] uintptr_t BasicAbiIcfExpected2() { |
| return BasicAbiLaunder(BasicAbiRecordCaller()); |
| } |
| |
| BASIC_ABI [[gnu::noinline]] uintptr_t BasicAbiIndirectIcfExpected1() { |
| return BasicAbiLaunder(BasicAbiIcfExpected1()); |
| } |
| |
| BASIC_ABI [[gnu::noinline]] uintptr_t BasicAbiIndirectIcfExpected2() { |
| return BasicAbiLaunder(BasicAbiIcfExpected2()); |
| } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIndirectIcfExpected1() { |
| BasicAbiIndirectIcfExpected1(); |
| __builtin_trap(); |
| } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIndirectIcfExpected2() { |
| BasicAbiIndirectIcfExpected2(); |
| __builtin_trap(); |
| } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIndirectIcfPreventedIndirectly1() { CrashWithIcfPrevented1(); } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIndirectIcfPreventedIndirectly2() { CrashWithIcfPrevented2(); } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIndirectIcfPreventedDirectly1() { |
| BasicAbiIndirectIcfExpected1(); |
| CRASH_WITH_UNIQUE_BACKTRACE(); |
| } |
| |
| BASIC_ABI [[noreturn]] void CrashWithIndirectIcfPreventedDirectly2() { |
| BasicAbiIndirectIcfExpected2(); |
| CRASH_WITH_UNIQUE_BACKTRACE(); |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, BasicAbiIcfExpected) { |
| uintptr_t caller1 = BasicAbiIcfExpected1(); |
| uintptr_t caller2 = BasicAbiIcfExpected2(); |
| if (kIcfExpected) { |
| EXPECT_EQ(caller1, caller2); |
| } |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, BasicAbiIndirectIcfExpected) { |
| uintptr_t caller1 = BasicAbiIndirectIcfExpected1(); |
| uintptr_t caller2 = BasicAbiIndirectIcfExpected2(); |
| if (kIcfExpected) { |
| EXPECT_EQ(caller1, caller2); |
| } |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, CrashWithIndirectIcfExpected) { |
| uintptr_t crash1 = 0, crash2 = 0; |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIndirectIcfExpected1, crash1)); |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIndirectIcfExpected2, crash2)); |
| if (kIcfExpected) { |
| EXPECT_EQ(crash1, crash2); |
| } |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, CrashWithIndirectIcfPreventedDirectly) { |
| uintptr_t crash1 = 0, crash2 = 0; |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIndirectIcfPreventedDirectly1, crash1)); |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIndirectIcfPreventedDirectly2, crash2)); |
| EXPECT_NE(crash1, crash2); |
| } |
| |
| TEST(ZirconInternalUniqueBacktraceTests, CrashWithIndirectIcfPreventedIndirectly) { |
| uintptr_t crash1 = 0, crash2 = 0; |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIndirectIcfPreventedIndirectly1, crash1)); |
| ASSERT_NO_FATAL_FAILURE(CatchCrash(CrashWithIndirectIcfPreventedIndirectly2, crash2)); |
| EXPECT_NE(crash1, crash2); |
| } |
| |
| #endif // __Fuchsia__ |
| |
| } // namespace |