Index: lib/interception/CMakeLists.txt =================================================================== --- lib/interception/CMakeLists.txt +++ lib/interception/CMakeLists.txt @@ -17,3 +17,7 @@ ARCHS ${SANITIZER_COMMON_SUPPORTED_ARCH} SOURCES ${INTERCEPTION_SOURCES} CFLAGS ${INTERCEPTION_CFLAGS}) + +if(COMPILER_RT_INCLUDE_TESTS) + add_subdirectory(tests) +endif() Index: lib/interception/interception_win.cc =================================================================== --- lib/interception/interception_win.cc +++ lib/interception/interception_win.cc @@ -201,6 +201,7 @@ size_t cursor = 0; while (cursor < size) { switch (code[cursor]) { + case '\x50': // push eax case '\x51': // push ecx case '\x52': // push edx case '\x53': // push ebx @@ -341,7 +342,7 @@ } static void **InterestingDLLsAvailable() { - const char *InterestingDLLs[] = { + static const char *InterestingDLLs[] = { "kernel32.dll", "msvcr110.dll", // VS2012 "msvcr120.dll", // VS2013 Index: lib/interception/tests/CMakeLists.txt =================================================================== --- /dev/null +++ lib/interception/tests/CMakeLists.txt @@ -0,0 +1,147 @@ +include(CompilerRTCompile) + +clang_compiler_add_cxx_check() + +filter_available_targets(INTERCEPTION_UNITTEST_SUPPORTED_ARCH x86_64 i386 mips64 mips64el) + +set(INTERCEPTION_UNITTESTS + interception_linux_test.cc + interception_test_main.cc + interception_win_test.cc +) + +set(INTERCEPTION_TEST_HEADERS) + +set(INTERCEPTION_TEST_CFLAGS_COMMON + ${COMPILER_RT_UNITTEST_CFLAGS} + ${COMPILER_RT_GTEST_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/include + -I${COMPILER_RT_SOURCE_DIR}/lib + -I${COMPILER_RT_SOURCE_DIR}/lib/interception + -fno-rtti + -O2 + -Werror=sign-compare + -Wno-non-virtual-dtor) + +# -gline-tables-only must be enough for these tests, so use it if possible. +if(COMPILER_RT_TEST_COMPILER_ID MATCHES "Clang") + list(APPEND INTERCEPTION_TEST_CFLAGS_COMMON -gline-tables-only) +else() + list(APPEND INTERCEPTION_TEST_CFLAGS_COMMON -g) +endif() +if(MSVC) + list(APPEND INTERCEPTION_TEST_CFLAGS_COMMON -gcodeview) +endif() +list(APPEND INTERCEPTION_TEST_LINK_FLAGS_COMMON -g) + +if(NOT MSVC) + list(APPEND INTERCEPTION_TEST_LINK_FLAGS_COMMON --driver-mode=g++) +endif() + +if(ANDROID) + list(APPEND INTERCEPTION_TEST_LINK_FLAGS_COMMON -pie) +endif() + +set(INTERCEPTION_TEST_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBLOG log INTERCEPTION_TEST_LINK_LIBS) +# NDK r10 requires -latomic almost always. +append_list_if(ANDROID atomic INTERCEPTION_TEST_LINK_LIBS) + +append_list_if(COMPILER_RT_HAS_LIBDL -ldl INTERCEPTION_TEST_LINK_FLAGS_COMMON) +append_list_if(COMPILER_RT_HAS_LIBRT -lrt INTERCEPTION_TEST_LINK_FLAGS_COMMON) +append_list_if(COMPILER_RT_HAS_LIBPTHREAD -pthread INTERCEPTION_TEST_LINK_FLAGS_COMMON) +# x86_64 FreeBSD 9.2 additionally requires libc++ to build the tests. Also, +# 'libm' shall be specified explicitly to build i386 tests. +if(CMAKE_SYSTEM MATCHES "FreeBSD-9.2-RELEASE") + list(APPEND INTERCEPTION_TEST_LINK_FLAGS_COMMON "-lc++ -lm") +endif() + +include_directories(..) +include_directories(../..) + +# Adds static library which contains interception object file +# (universal binary on Mac and arch-specific object files on Linux). +macro(add_interceptor_lib library) + add_library(${library} STATIC ${ARGN}) + set_target_properties(${library} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +endmacro() + +function(get_interception_lib_for_arch arch lib lib_name) + if(APPLE) + set(tgt_name "RTInterception.test.osx") + else() + set(tgt_name "RTInterception.test.${arch}") + endif() + set(${lib} "${tgt_name}" PARENT_SCOPE) + if(CMAKE_CONFIGURATION_TYPES) + set(configuration_path "${CMAKE_CFG_INTDIR}/") + else() + set(configuration_path "") + endif() + if(NOT MSVC) + set(${lib_name} "${configuration_path}lib${tgt_name}.a" PARENT_SCOPE) + else() + set(${lib_name} "${configuration_path}${tgt_name}.lib" PARENT_SCOPE) + endif() +endfunction() + +# Interception unit tests testsuite. +add_custom_target(InterceptionUnitTests) +set_target_properties(InterceptionUnitTests PROPERTIES + FOLDER "Compiler-RT Tests") + +# Adds interception tests for architecture. +macro(add_interception_tests_for_arch arch) + get_target_flags_for_arch(${arch} TARGET_FLAGS) + set(INTERCEPTION_TEST_SOURCES ${INTERCEPTION_UNITTESTS} + ${COMPILER_RT_GTEST_SOURCE}) + set(INTERCEPTION_TEST_COMPILE_DEPS ${INTERCEPTION_TEST_HEADERS}) + if(NOT COMPILER_RT_STANDALONE_BUILD) + list(APPEND INTERCEPTION_TEST_COMPILE_DEPS gtest) + endif() + set(INTERCEPTION_TEST_OBJECTS) + foreach(source ${INTERCEPTION_TEST_SOURCES}) + get_filename_component(basename ${source} NAME) + if(CMAKE_CONFIGURATION_TYPES) + set(output_obj "${CMAKE_CFG_INTDIR}/${basename}.${arch}.o") + else() + set(output_obj "${basename}.${arch}.o") + endif() + clang_compile(${output_obj} ${source} + CFLAGS ${INTERCEPTION_TEST_CFLAGS_COMMON} ${TARGET_FLAGS} + DEPS ${INTERCEPTION_TEST_COMPILE_DEPS}) + list(APPEND INTERCEPTION_TEST_OBJECTS ${output_obj}) + endforeach() + get_interception_lib_for_arch(${arch} INTERCEPTION_COMMON_LIB + INTERCEPTION_COMMON_LIB_NAME) + # Add unittest target. + set(INTERCEPTION_TEST_NAME "Interception-${arch}-Test") + add_compiler_rt_test(InterceptionUnitTests ${INTERCEPTION_TEST_NAME} + OBJECTS ${INTERCEPTION_TEST_OBJECTS} + ${INTERCEPTION_COMMON_LIB_NAME} + DEPS ${INTERCEPTION_TEST_OBJECTS} ${INTERCEPTION_COMMON_LIB} + LINK_FLAGS ${INTERCEPTION_TEST_LINK_FLAGS_COMMON} + ${TARGET_FLAGS}) + + +endmacro() + +if(COMPILER_RT_CAN_EXECUTE_TESTS AND NOT ANDROID) + # We use just-built clang to build interception unittests, so we must + # be sure that produced binaries would work. + if(APPLE) + add_interceptor_lib("RTInterception.test.osx" + $) + else() + foreach(arch ${INTERCEPTION_UNITTEST_SUPPORTED_ARCH}) + add_interceptor_lib("RTInterception.test.${arch}" + $) + endforeach() + endif() + foreach(arch ${INTERCEPTION_UNITTEST_SUPPORTED_ARCH}) + add_interception_tests_for_arch(${arch}) + endforeach() +endif() + + Index: lib/interception/tests/interception_linux_test.cc =================================================================== --- /dev/null +++ lib/interception/tests/interception_linux_test.cc @@ -0,0 +1,65 @@ +//===-- interception_linux_test.cc ----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer/AddressSanitizer runtime. +// Tests for interception_linux.h. +// +//===----------------------------------------------------------------------===// +#include "interception/interception.h" + +#include "gtest/gtest.h" + +// Too slow for debug build +#if !SANITIZER_DEBUG +#if SANITIZER_LINUX + +static int InterceptorFunctionCalled; + +DECLARE_REAL(int, isdigit, int); + +INTERCEPTOR(int, isdigit, int d) { + ++InterceptorFunctionCalled; + return d >= '0' && d <= '9'; +} + +namespace __interception { + +TEST(Interception, GetRealFunctionAddress) { + uptr expected_malloc_address = (uptr)(void*)&malloc; + uptr malloc_address = 0; + EXPECT_TRUE(GetRealFunctionAddress("malloc", &malloc_address, 0, 0)); + EXPECT_EQ(expected_malloc_address, malloc_address); + + uptr dummy_address = 0; + EXPECT_TRUE( + GetRealFunctionAddress("dummy_doesnt_exist__", &dummy_address, 0, 0)); + EXPECT_EQ(0U, dummy_address); +} + +TEST(Interception, Basic) { + ASSERT_TRUE(INTERCEPT_FUNCTION(isdigit)); + + // After interception, the counter should be incremented. + InterceptorFunctionCalled = 0; + EXPECT_NE(0, isdigit('1')); + EXPECT_EQ(1, InterceptorFunctionCalled); + EXPECT_EQ(0, isdigit('a')); + EXPECT_EQ(2, InterceptorFunctionCalled); + + // Calling the REAL function should not affect the counter. + InterceptorFunctionCalled = 0; + EXPECT_NE(0, REAL(isdigit)('1')); + EXPECT_EQ(0, REAL(isdigit)('a')); + EXPECT_EQ(0, InterceptorFunctionCalled); +} + +} // namespace __interception + +#endif // SANITIZER_LINUX +#endif // #if !SANITIZER_DEBUG Index: lib/interception/tests/interception_test_main.cc =================================================================== --- /dev/null +++ lib/interception/tests/interception_test_main.cc @@ -0,0 +1,22 @@ +//===-- interception_test_main.cc------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of AddressSanitizer, an address sanity checker. +// +// Testing the machinery for providing replacements/wrappers for system +// functions. +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +int main(int argc, char **argv) { + testing::GTEST_FLAG(death_test_style) = "threadsafe"; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} Index: lib/interception/tests/interception_win_test.cc =================================================================== --- /dev/null +++ lib/interception/tests/interception_win_test.cc @@ -0,0 +1,142 @@ +//===-- interception_win_test.cc ------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer/AddressSanitizer runtime. +// Tests for interception_win.h. +// +//===----------------------------------------------------------------------===// +#include "interception/interception.h" + +#include "gtest/gtest.h" + +// Too slow for debug build +#if !SANITIZER_DEBUG +#if SANITIZER_WINDOWS + +#define WIN32_LEAN_AND_MEAN +#include + +namespace { + +typedef int (*IdentityFunction)(int); + +#if !SANITIZER_WINDOWS64 +u8 kIdentityCodeWithPrologue[] = { + 0x55, // push ebp + 0x8B, 0xEC, // mov ebp,esp + 0x8B, 0x45, 0x08, // mov eax,dword ptr [ebp + 8] + 0x5D, // pop ebp + 0xC3, // ret +}; + +u8 kIdentityCodeWithPushPop[] = { + 0x55, // push ebp + 0x8B, 0xEC, // mov ebp,esp + 0x53, // push ebx + 0x50, // push eax + 0x58, // pop eax + 0x8B, 0x45, 0x08, // mov eax,dword ptr [ebp + 8] + 0x5B, // pop ebx + 0x5D, // pop ebp + 0xC3, // ret +}; + +#endif + +// A buffer holding the dynamically generated code under test. +u8* ActiveCode; +size_t ActiveCodeLength = 4096; + +bool LoadActiveCode(u8* Code, size_t CodeLength, uptr* EntryPoint) { + if (ActiveCode == nullptr) { + ActiveCode = + (u8*)::VirtualAlloc(nullptr, ActiveCodeLength, MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE); + if (ActiveCode == nullptr) return false; + } + + size_t Position = 0; + *EntryPoint = (uptr)&ActiveCode[0]; + + // Copy the function body. + for (size_t i = 0; i < CodeLength; ++i) + ActiveCode[Position++] = Code[i]; + + return true; +} + +int InterceptorFunctionCalled; + +NOINLINE int InterceptorFunction(int x) { + ++InterceptorFunctionCalled; + return x; +} + +} // namespace + +namespace __interception { + +// Tests for interception_win.h +TEST(Interception, InternalGetProcAddress) { + HMODULE ntdll_handle = ::GetModuleHandle("ntdll"); + ASSERT_NE(nullptr, ntdll_handle); + uptr DbgPrint_expected = (uptr)::GetProcAddress(ntdll_handle, "DbgPrint"); + uptr isdigit_expected = (uptr)::GetProcAddress(ntdll_handle, "isdigit"); + uptr DbgPrint_adddress = InternalGetProcAddress(ntdll_handle, "DbgPrint"); + uptr isdigit_address = InternalGetProcAddress(ntdll_handle, "isdigit"); + + EXPECT_EQ(DbgPrint_expected, DbgPrint_adddress); + EXPECT_EQ(isdigit_expected, isdigit_address); + EXPECT_NE(DbgPrint_adddress, isdigit_address); +} + +void TestIdentityFunctionPatching(u8* IdentityCode, size_t IdentityCodeLength) { + uptr IdentityAddress; + ASSERT_TRUE( + LoadActiveCode(IdentityCode, IdentityCodeLength, &IdentityAddress)); + IdentityFunction Identity = (IdentityFunction)IdentityAddress; + + // Validate behavior before dynamic patching. + InterceptorFunctionCalled = 0; + EXPECT_EQ(0, Identity(0)); + EXPECT_EQ(42, Identity(42)); + EXPECT_EQ(0, InterceptorFunctionCalled); + + // Patch the function. + uptr RealIdentityAddress = 0; + EXPECT_TRUE(OverrideFunction(IdentityAddress, (uptr)&InterceptorFunction, + &RealIdentityAddress)); + IdentityFunction RealIdentity = (IdentityFunction)RealIdentityAddress; + + // Calling the redirected function. + InterceptorFunctionCalled = 0; + EXPECT_EQ(0, Identity(0)); + EXPECT_EQ(42, Identity(42)); + EXPECT_EQ(2, InterceptorFunctionCalled); + + // Calling the real function. + InterceptorFunctionCalled = 0; + EXPECT_EQ(0, RealIdentity(0)); + EXPECT_EQ(42, RealIdentity(42)); + EXPECT_EQ(0, InterceptorFunctionCalled); +} + +#if !SANITIZER_WINDOWS64 +TEST(Interception, OverrideFunction) { + TestIdentityFunctionPatching(kIdentityCodeWithPrologue, + sizeof(kIdentityCodeWithPrologue)); + TestIdentityFunctionPatching(kIdentityCodeWithPushPop, + sizeof(kIdentityCodeWithPushPop)); +} +#endif + +} // namespace __interception + +#endif // SANITIZER_WINDOWS +#endif // #if !SANITIZER_DEBUG